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:
M | LICENSE | | | 2 | +- |
M | Makefile | | | 6 | +++++- |
M | stagit-index.c | | | 48 | +++++++++++++++++++++++++++++++++++++++++++++--- |
M | stagit.c | | | 117 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------- |
M | style.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(" <<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>>\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;
+ }
+}