stagit

stagit (https://www.codemadness.org/stagit.html) fork
git clone git://bsandro.tech/stagit
Log | Files | Refs | README | LICENSE

commit ec10d76b682eef1b0290d8ccaa7063744e972e77
parent 4e46f231a54a2ccd153403fff134ccaebe87f4e0
Author: bsandro <email@bsandro.tech>
Date:   Thu, 10 Nov 2022 00:57:12 +0200

merge origin/master -> bsandro/all-branches-log

Diffstat:
MLICENSE | 2+-
MMakefile | 6+++++-
Mstagit-index.c | 48+++++++++++++++++++++++++++++++++++++++++++++---
Mstagit.c | 117++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mstyle.css | 48++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 185 insertions(+), 36 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -1,6 +1,6 @@ MIT/X Consortium License -(c) 2015-2021 Hiltjo Posthuma <hiltjo@codemadness.org> +(c) 2015-2022 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"), diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ .POSIX: NAME = stagit -VERSION = 0.9.6 +VERSION = 1.2 # paths PREFIX = /usr/local @@ -16,6 +16,10 @@ STAGIT_CFLAGS = ${LIBGIT_INC} ${CFLAGS} STAGIT_LDFLAGS = ${LIBGIT_LIB} ${LDFLAGS} STAGIT_CPPFLAGS = -D_XOPEN_SOURCE=700 -D_DEFAULT_SOURCE -D_BSD_SOURCE +# Uncomment to enable workaround for older libgit2 which don't support this +# option. This workaround will be removed in the future *pinky promise*. +#STAGIT_CFLAGS += -DGIT_OPT_SET_OWNER_VALIDATION=-1 + SRC = \ stagit.c\ stagit-index.c diff --git a/stagit-index.c b/stagit-index.c @@ -16,6 +16,16 @@ static char description[255] = "Repositories"; static char *name = ""; static char owner[255]; +/* Handle read or write errors for a FILE * stream */ +void +checkfileerror(FILE *fp, const char *name, int mode) +{ + if (mode == 'r' && ferror(fp)) + errx(1, "read error: %s", name); + else if (mode == 'w' && (fflush(fp) || ferror(fp))) + errx(1, "write error: %s", name); +} + void joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) { @@ -28,6 +38,28 @@ joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); } +/* Percent-encode, see RFC3986 section 2.1. */ +void +percentencode(FILE *fp, const char *s, size_t len) +{ + static char tab[] = "0123456789ABCDEF"; + unsigned char uc; + size_t i; + + for (i = 0; *s && i < len; s++, i++) { + uc = *s; + /* NOTE: do not encode '/' for paths or ",-." */ + if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') || + uc == '[' || uc == ']') { + putc('%', fp); + putc(tab[(uc >> 4) & 0x0f], fp); + putc(tab[uc & 0x0f], fp); + } else { + putc(uc, fp); + } + } +} + /* Escape characters below as HTML 2.0 / XML 1.0. */ void xmlencode(FILE *fp, const char *s, size_t len) @@ -118,7 +150,7 @@ writelog(FILE *fp) *p = '\0'; fputs("<tr><td><a href=\"", fp); - xmlencode(fp, stripped_name, strlen(stripped_name)); + percentencode(fp, stripped_name, strlen(stripped_name)); fputs("/log.html\">", fp); xmlencode(fp, stripped_name, strlen(stripped_name)); fputs("</a></td><td>", fp); @@ -147,11 +179,17 @@ main(int argc, char *argv[]) int i, ret = 0; if (argc < 2) { - fprintf(stderr, "%s [repodir...]\n", argv[0]); + fprintf(stderr, "usage: %s [repodir...]\n", argv[0]); return 1; } + /* do not search outside the git repository: + GIT_CONFIG_LEVEL_APP is the highest level currently */ git_libgit2_init(); + for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++) + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, ""); + /* do not require the git repository to be owned by the current user */ + git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0); #ifdef __OpenBSD__ if (pledge("stdio rpath", NULL) == -1) @@ -188,6 +226,7 @@ main(int argc, char *argv[]) if (fp) { if (!fgets(description, sizeof(description), fp)) description[0] = '\0'; + checkfileerror(fp, "description", 'r'); fclose(fp); } @@ -201,8 +240,9 @@ main(int argc, char *argv[]) if (fp) { if (!fgets(owner, sizeof(owner), fp)) owner[0] = '\0'; - owner[strcspn(owner, "\n")] = '\0'; + checkfileerror(fp, "owner", 'r'); fclose(fp); + owner[strcspn(owner, "\n")] = '\0'; } writelog(stdout); } @@ -212,5 +252,7 @@ main(int argc, char *argv[]) git_repository_free(repo); git_libgit2_shutdown(); + checkfileerror(stdout, "<stdout>", 'w'); + return ret; } diff --git a/stagit.c b/stagit.c @@ -71,7 +71,7 @@ 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 */ +static long long nlogcommits = -1; /* -1 indicates not used */ /* cache */ static git_oid lastoid; @@ -79,6 +79,16 @@ static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */ static FILE *rcachefp, *wcachefp; static const char *cachefile; +/* Handle read or write errors for a FILE * stream */ +void +checkfileerror(FILE *fp, const char *name, int mode) +{ + if (mode == 'r' && ferror(fp)) + errx(1, "read error: %s", name); + else if (mode == 'w' && (fflush(fp) || ferror(fp))) + errx(1, "write error: %s", name); +} + void joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) { @@ -359,6 +369,28 @@ efopen(const char *filename, const char *flags) return fp; } +/* Percent-encode, see RFC3986 section 2.1. */ +void +percentencode(FILE *fp, const char *s, size_t len) +{ + static char tab[] = "0123456789ABCDEF"; + unsigned char uc; + size_t i; + + for (i = 0; *s && i < len; s++, i++) { + uc = *s; + /* NOTE: do not encode '/' for paths or ",-." */ + if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') || + uc == '[' || uc == ']') { + putc('%', fp); + putc(tab[(uc >> 4) & 0x0f], fp); + putc(tab[uc & 0x0f], fp); + } else { + putc(uc, fp); + } + } +} + /* Escape characters below as HTML 2.0 / XML 1.0. */ void xmlencode(FILE *fp, const char *s, size_t len) @@ -480,10 +512,12 @@ writeheader(FILE *fp, const char *title) 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=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed (tags)\" href=\"%stags.xml\" />\n", - name, relpath); + fputs("<link rel=\"alternate\" type=\"application/atom+xml\" title=\"", fp); + xmlencode(fp, name, strlen(name)); + fprintf(fp, " Atom Feed\" href=\"%satom.xml\" />\n", relpath); + fputs("<link rel=\"alternate\" type=\"application/atom+xml\" title=\"", fp); + 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); fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>", @@ -495,7 +529,7 @@ writeheader(FILE *fp, const char *title) fputs("</span></td></tr>", fp); if (cloneurl[0]) { fputs("<tr class=\"url\"><td></td><td>git clone <a href=\"", fp); - xmlencode(fp, cloneurl, strlen(cloneurl)); + xmlencode(fp, cloneurl, strlen(cloneurl)); /* not percent-encoded */ fputs("\">", fp); xmlencode(fp, cloneurl, strlen(cloneurl)); fputs("</a></td></tr>", fp); @@ -538,14 +572,15 @@ writeblobhtml(FILE *fp, const git_blob *blob) continue; n++; fprintf(fp, nfmt, n, n, n); - xmlencode(fp, &s[prev], i - prev + 1); + xmlencodeline(fp, &s[prev], i - prev + 1); + putc('\n', fp); prev = i + 1; } /* trailing data */ if ((len - prev) > 0) { n++; fprintf(fp, nfmt, n, n, n); - xmlencode(fp, &s[prev], len - prev); + xmlencodeline(fp, &s[prev], len - prev); } } @@ -568,7 +603,7 @@ printcommit(FILE *fp, struct commitinfo *ci) 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)); + 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); @@ -663,11 +698,11 @@ printshowfile(FILE *fp, struct commitinfo *ci) 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)); + percentencode(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)); + percentencode(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"); @@ -739,6 +774,7 @@ writelog(FILE *fp, const git_oid *oid) git_oid id; char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1]; FILE *fpfile; + size_t remcommits = 0; int r; git_revwalk_new(&w, repo); @@ -759,8 +795,11 @@ writelog(FILE *fp, const git_oid *oid) /* optimization: if there are no log lines to write and the commit file already exists: skip the diffstat */ - if (!nlogcommits && !r) - continue; + if (!nlogcommits) { + remcommits++; + if (!r) + continue; + } if (!(ci = commitinfo_getbyoid(&id))) break; @@ -768,15 +807,10 @@ writelog(FILE *fp, const git_oid *oid) if (commitinfo_getstats(ci) == -1) goto err; - if (nlogcommits < 0) { - writelogline(fp, ci); - } else if (nlogcommits > 0) { + 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 (nlogcommits > 0) + nlogcommits--; } if (cachefile) @@ -791,6 +825,7 @@ writelog(FILE *fp, const git_oid *oid) printshowfile(fpfile, ci); fputs("</pre>\n", fpfile); writefooter(fpfile); + checkfileerror(fpfile, path, 'w'); fclose(fpfile); } err: @@ -798,6 +833,12 @@ err: } git_revwalk_free(w); + if (nlogcommits == 0 && remcommits != 0) { + fprintf(fp, "<tr><td></td><td colspan=\"5\">" + "%zu more commits remaining, fetch the repository" + "</td></tr>\n", remcommits); + } + relpath = ""; return 0; @@ -934,14 +975,13 @@ writeblob(git_object *obj, const char *fpath, const char *filename, size_t files fprintf(fp, " (%zuB)", filesize); fputs("</p><hr/>", fp); - if (git_blob_is_binary((git_blob *)obj)) { + if (git_blob_is_binary((git_blob *)obj)) fputs("<p>Binary file.</p>\n", fp); - } else { + else lc = writeblobhtml(fp, (git_blob *)obj); - if (ferror(fp)) - err(1, "fwrite"); - } + writefooter(fp); + checkfileerror(fp, fpath, 'w'); fclose(fp); relpath = ""; @@ -1036,7 +1076,7 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) 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)); + percentencode(fp, filepath, strlen(filepath)); fputs("\">", fp); xmlencode(fp, entrypath, strlen(entrypath)); fputs("</a></td><td class=\"num\" align=\"right\">", fp); @@ -1145,7 +1185,7 @@ writerefs(FILE *fp) void usage(char *argv0) { - fprintf(stderr, "%s [-c cachefile | -l commits] " + fprintf(stderr, "usage: %s [-c cachefile | -l commits] " "[-u baseurl] repodir\n", argv0); exit(1); } @@ -1191,7 +1231,13 @@ main(int argc, char *argv[]) if (!realpath(repodir, repodirabs)) err(1, "realpath"); + /* do not search outside the git repository: + GIT_CONFIG_LEVEL_APP is the highest level currently */ git_libgit2_init(); + for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++) + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, ""); + /* do not require the git repository to be owned by the current user */ + git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0); #ifdef __OpenBSD__ if (unveil(repodir, "r") == -1) @@ -1243,6 +1289,7 @@ main(int argc, char *argv[]) if (fpread) { if (!fgets(description, sizeof(description), fpread)) description[0] = '\0'; + checkfileerror(fpread, path, 'r'); fclose(fpread); } @@ -1255,8 +1302,9 @@ main(int argc, char *argv[]) if (fpread) { if (!fgets(cloneurl, sizeof(cloneurl), fpread)) cloneurl[0] = '\0'; - cloneurl[strcspn(cloneurl, "\n")] = '\0'; + checkfileerror(fpread, path, 'r'); fclose(fpread); + cloneurl[strcspn(cloneurl, "\n")] = '\0'; } /* check LICENSE */ @@ -1316,13 +1364,15 @@ main(int argc, char *argv[]) while (!feof(rcachefp)) { n = fread(buf, 1, sizeof(buf), rcachefp); if (ferror(rcachefp)) - err(1, "fread"); + break; if (fwrite(buf, 1, n, fp) != n || fwrite(buf, 1, n, wcachefp) != n) - err(1, "fwrite"); + break; } + checkfileerror(rcachefp, cachefile, 'r'); fclose(rcachefp); } + checkfileerror(wcachefp, tmppath, 'w'); fclose(wcachefp); } else { if (head) @@ -1331,6 +1381,7 @@ main(int argc, char *argv[]) fputs("</tbody></table>", fp); writefooter(fp); + checkfileerror(fp, "log.html", 'w'); fclose(fp); /* files for HEAD */ @@ -1339,6 +1390,7 @@ main(int argc, char *argv[]) if (head) writefiles(fp, head); writefooter(fp); + checkfileerror(fp, "files.html", 'w'); fclose(fp); /* summary page with branches and tags */ @@ -1346,16 +1398,19 @@ main(int argc, char *argv[]) writeheader(fp, "Refs"); writerefs(fp); writefooter(fp); + checkfileerror(fp, "refs.html", 'w'); fclose(fp); /* Atom feed */ fp = efopen("atom.xml", "w"); writeatom(fp, 1); + checkfileerror(fp, "atom.xml", 'w'); fclose(fp); /* Atom feed for tags / releases */ fp = efopen("tags.xml", "w"); writeatom(fp, 0); + checkfileerror(fp, "tags.xml", 'w'); fclose(fp); /* rename new cache file on success */ diff --git a/style.css b/style.css @@ -104,3 +104,51 @@ pre a.i:hover, pre a.d:hover { text-decoration: none; } + +@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; + } +}