commit 8968f8cf7ac514463c703e863e953881094ea941
parent b7568c323867513fa17a181ef8b077a8d0bc3057
Author: hhvn <dev@hhvn.uk>
Date: Sat, 22 Jan 2022 11:50:10 +0000
Merge changes from upstream
Diffstat:
5 files changed, 188 insertions(+), 94 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -1,6 +1,6 @@
MIT/X Consortium License
-(c) 2015-2019 Hiltjo Posthuma <hiltjo@codemadness.org>
+(c) 2015-2022 Hiltjo Posthuma <hiltjo@codemadness.org>
(c) 2015-2016 Dimitris Papastamos <sin@2f30.org>
(c) 2020-2021 Hayden Hamilton <dev@hhvn.uk>
diff --git a/stagit-gopher-index.1 b/stagit-gopher-index.1
@@ -1,4 +1,4 @@
-.Dd Jun 23, 2017
+.Dd August 2, 2021
.Dt STAGIT-GOPHER-INDEX 1
.Os
.Sh NAME
@@ -32,6 +32,11 @@ The content of the follow files specifies the meta data for each repository:
.It .git/description or description (bare repos).
description
.El
+.Sh EXAMPLES
+.Bd -literal
+cd gphroot
+stagit-gopher-index path/to/gitrepo1 path/to/gitrepo2 > index.gph
+.Ed
.Sh SEE ALSO
.Xr stagit-gopher 1
.Sh AUTHORS
diff --git a/stagit-gopher-index.c b/stagit-gopher-index.c
@@ -10,6 +10,9 @@
#include <git2.h>
+#define PAD_TRUNCATE_SYMBOL "\xe2\x80\xa6" /* symbol: "ellipsis" */
+#define UTF_INVALID_SYMBOL "\xef\xbf\xbd" /* symbol: "replacement" */
+
static git_repository *repo;
static const char *relpath = "";
@@ -17,36 +20,61 @@ static const char *relpath = "";
static char description[255] = "Repositories";
static char *name = "";
-/* format `len' columns of characters. If string is shorter pad the rest
+/* Format `len' columns of characters. If string is shorter pad the rest
* with characters `pad`. */
int
utf8pad(char *buf, size_t bufsiz, const char *s, size_t len, int pad)
{
- wchar_t w;
+ wchar_t wc;
size_t col = 0, i, slen, siz = 0;
- int rl, wc;
+ int inc, rl, w;
- if (!len)
+ if (!bufsiz)
return -1;
+ if (!len) {
+ buf[0] = '\0';
+ return 0;
+ }
slen = strlen(s);
- for (i = 0; i < slen && col < len + 1; i += rl) {
- if ((rl = mbtowc(&w, &s[i], slen - i < 4 ? slen - i : 4)) <= 0)
+ for (i = 0; i < slen; i += inc) {
+ inc = 1; /* next byte */
+ if ((unsigned char)s[i] < 32)
+ continue;
+
+ rl = mbtowc(&wc, &s[i], slen - i < 4 ? slen - i : 4);
+ inc = rl;
+ if (rl < 0) {
+ mbtowc(NULL, NULL, 0); /* reset state */
+ inc = 1; /* invalid, seek next byte */
+ w = 1; /* replacement char is one width */
+ } else if ((w = wcwidth(wc)) == -1) {
+ continue;
+ }
+
+ if (col + w > len || (col + w == len && s[i + inc])) {
+ if (siz + 4 >= bufsiz)
+ return -1;
+ memcpy(&buf[siz], PAD_TRUNCATE_SYMBOL, sizeof(PAD_TRUNCATE_SYMBOL) - 1);
+ siz += sizeof(PAD_TRUNCATE_SYMBOL) - 1;
+ buf[siz] = '\0';
+ col++;
break;
- if ((wc = wcwidth(w)) == -1)
- wc = 1;
- col += wc;
- if (col >= len && s[i + rl]) {
+ } else if (rl < 0) {
if (siz + 4 >= bufsiz)
return -1;
- memcpy(&buf[siz], "\xe2\x80\xa6", 4);
- return 0;
+ memcpy(&buf[siz], UTF_INVALID_SYMBOL, sizeof(UTF_INVALID_SYMBOL) - 1);
+ siz += sizeof(UTF_INVALID_SYMBOL) - 1;
+ buf[siz] = '\0';
+ col++;
+ continue;
}
- if (siz + rl + 1 >= bufsiz)
+ if (siz + inc + 1 >= bufsiz)
return -1;
- memcpy(&buf[siz], &s[i], rl);
- siz += rl;
+ memcpy(&buf[siz], &s[i], inc);
+ siz += inc;
buf[siz] = '\0';
+ col += w;
}
len -= col;
@@ -75,7 +103,7 @@ gphtext(FILE *fp, const char *s, size_t len)
fputs(" ", fp);
break;
default:
- fputc(*s, fp);
+ putc(*s, fp);
break;
}
}
@@ -99,7 +127,7 @@ gphlink(FILE *fp, const char *s, size_t len)
fputs("\\|", fp);
break;
default:
- fputc(*s, fp);
+ putc(*s, fp);
break;
}
}
@@ -158,7 +186,6 @@ writelog(FILE *fp)
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)) {
@@ -211,7 +238,11 @@ main(int argc, char *argv[])
setlocale(LC_CTYPE, "");
+ /* 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, "");
#ifdef __OpenBSD__
if (pledge("stdio rpath", NULL) == -1)
diff --git a/stagit-gopher.1 b/stagit-gopher.1
@@ -1,4 +1,4 @@
-.Dd July 19, 2020
+.Dd August 2, 2021
.Dt STAGIT-GOPHER 1
.Os
.Sh NAME
@@ -9,6 +9,7 @@
.Op Fl b Ar baseprefix
.Op Fl c Ar cachefile
.Op Fl l Ar commits
+.Op Fl u Ar baseurl
.Ar repodir
.Sh DESCRIPTION
.Nm
@@ -35,6 +36,11 @@ Write a maximum number of
.Ar commits
to the log.gph 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.
+Does not use the prefix from the -b option.
+It should include the gopher type.
+For example: "gopher://codemadness.org/1/git/stagit-gopher/".
.El
.Pp
The options
@@ -90,11 +96,23 @@ The content of the follow files specifies the metadata for each repository:
.It .git/description or description (bare repo).
description
.It .git/url or url (bare repo).
-primary clone url of the repository, for example: git://git.2f30.org/stagit
+primary clone URL of the repository, for example:
+git://git.codemadness.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 index is made.
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+.Bd -literal
+mkdir -p gphroot/gphrepo1 && cd gphroot/gphrepo1
+stagit-gopher path/to/gitrepo1
+# repeat for other repositories.
+.Ed
+.Pp
+To update the gph files when the repository is changed a git post-receive hook
+can be used, see the file example_post-receive.sh for an example.
.Sh SEE ALSO
.Xr stagit-gopher-index 1
.Sh AUTHORS
diff --git a/stagit-gopher.c b/stagit-gopher.c
@@ -18,6 +18,10 @@
#include "compat.h"
+#define LEN(s) (sizeof(s)/sizeof(*s))
+#define PAD_TRUNCATE_SYMBOL "\xe2\x80\xa6" /* symbol: "ellipsis" */
+#define UTF_INVALID_SYMBOL "\xef\xbf\xbd" /* symbol: "replacement" */
+
struct deltainfo {
git_patch *patch;
@@ -58,6 +62,7 @@ struct referenceinfo {
static git_repository *repo;
+static const char *baseurl = ""; /* base URL to make absolute RSS/Atom URI */
static const char *relpath = "";
static const char *repodir;
@@ -70,7 +75,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;
@@ -78,40 +83,61 @@ static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */
static FILE *rcachefp, *wcachefp;
static const char *cachefile;
-/* format `len' columns of characters. If string is shorter pad the rest
+/* Format `len' columns of characters. If string is shorter pad the rest
* with characters `pad`. */
int
utf8pad(char *buf, size_t bufsiz, const char *s, size_t len, int pad)
{
wchar_t wc;
size_t col = 0, i, slen, siz = 0;
- int rl, w;
+ int inc, rl, w;
- if (!len)
+ if (!bufsiz)
return -1;
+ if (!len) {
+ buf[0] = '\0';
+ return 0;
+ }
slen = strlen(s);
- for (i = 0; i < slen; i += rl) {
- if ((rl = mbtowc(&wc, &s[i], slen - i < 4 ? slen - i : 4)) <= 0)
- break;
- if ((w = wcwidth(wc)) == -1)
+ for (i = 0; i < slen; i += inc) {
+ inc = 1; /* next byte */
+ if ((unsigned char)s[i] < 32)
continue;
- if (col + w > len || (col + w == len && s[i + rl])) {
+
+ rl = mbtowc(&wc, &s[i], slen - i < 4 ? slen - i : 4);
+ inc = rl;
+ if (rl < 0) {
+ mbtowc(NULL, NULL, 0); /* reset state */
+ inc = 1; /* invalid, seek next byte */
+ w = 1; /* replacement char is one width */
+ } else if ((w = wcwidth(wc)) == -1) {
+ continue;
+ }
+
+ if (col + w > len || (col + w == len && s[i + inc])) {
if (siz + 4 >= bufsiz)
return -1;
- memcpy(&buf[siz], "\xe2\x80\xa6", 3);
- siz += 3;
- if (col + w == len && w > 1)
- buf[siz++] = pad;
+ memcpy(&buf[siz], PAD_TRUNCATE_SYMBOL, sizeof(PAD_TRUNCATE_SYMBOL) - 1);
+ siz += sizeof(PAD_TRUNCATE_SYMBOL) - 1;
buf[siz] = '\0';
- return 0;
+ col++;
+ break;
+ } else if (rl < 0) {
+ if (siz + 4 >= bufsiz)
+ return -1;
+ memcpy(&buf[siz], UTF_INVALID_SYMBOL, sizeof(UTF_INVALID_SYMBOL) - 1);
+ siz += sizeof(UTF_INVALID_SYMBOL) - 1;
+ buf[siz] = '\0';
+ col++;
+ continue;
}
- if (siz + rl + 1 >= bufsiz)
+ if (siz + inc + 1 >= bufsiz)
return -1;
- memcpy(&buf[siz], &s[i], rl);
- col += w;
- siz += rl;
+ memcpy(&buf[siz], &s[i], inc);
+ siz += inc;
buf[siz] = '\0';
+ col += w;
}
len -= col;
@@ -297,8 +323,7 @@ err:
int
refs_cmp(const void *v1, const void *v2)
{
- struct referenceinfo *r1 = (struct referenceinfo *)v1;
- struct referenceinfo *r2 = (struct referenceinfo *)v2;
+ const struct referenceinfo *r1 = v1, *r2 = v2;
time_t t1, t2;
int r;
@@ -393,12 +418,12 @@ err:
}
FILE *
-efopen(const char *name, const char *flags)
+efopen(const char *filename, const char *flags)
{
FILE *fp;
- if (!(fp = fopen(name, flags)))
- err(1, "fopen: '%s'", name);
+ if (!(fp = fopen(filename, flags)))
+ err(1, "fopen: '%s'", filename);
return fp;
}
@@ -416,7 +441,7 @@ xmlencode(FILE *fp, const char *s, size_t len)
case '\'': fputs("'", fp); break;
case '&': fputs("&", fp); break;
case '"': fputs(""", fp); break;
- default: fputc(*s, fp);
+ default: putc(*s, fp);
}
}
}
@@ -430,12 +455,12 @@ gphtextnl(FILE *fp, const char *s, size_t len)
for (i = 0; s[i] && i < len; i++) {
/* escape with 't' at the start of a line */
if (!n && (s[i] == 't' || s[i] == '['))
- fputc('t', fp);
+ putc('t', fp);
switch (s[i]) {
case '\t': fputs(" ", fp);
case '\r': break;
- default: fputc(s[i], fp);
+ default: putc(s[i], fp);
}
n = (s[i] != '\n');
}
@@ -457,7 +482,7 @@ gphtext(FILE *fp, const char *s, size_t len)
fputs(" ", fp);
break;
default:
- fputc(*s, fp);
+ putc(*s, fp);
break;
}
}
@@ -481,7 +506,7 @@ gphlink(FILE *fp, const char *s, size_t len)
fputs("\\|", fp);
break;
default:
- fputc(*s, fp);
+ putc(*s, fp);
break;
}
}
@@ -557,7 +582,7 @@ printtimeshort(FILE *fp, const git_time *intime)
void
writeheader(FILE *fp, const char *title)
{
- fputc('t', fp);
+ putc('t', fp);
gphtext(fp, title, strlen(title));
if (title[0] && strippedname[0])
fputs(" - ", fp);
@@ -593,16 +618,16 @@ writefooter(FILE *fp)
{
}
-int
+size_t
writeblobgph(FILE *fp, const git_blob *blob)
{
- size_t n = 0, i, j, prev;
- const char *nfmt = "%6d ";
+ size_t n = 0, i, j, len, prev;
+ const char *nfmt = "%6zu ";
const char *s = git_blob_rawcontent(blob);
- git_off_t len = git_blob_rawsize(blob);
+ len = git_blob_rawsize(blob);
if (len > 0) {
- for (i = 0, prev = 0; i < (size_t)len; i++) {
+ for (i = 0, prev = 0; i < len; i++) {
if (s[i] != '\n')
continue;
n++;
@@ -611,7 +636,7 @@ writeblobgph(FILE *fp, const git_blob *blob)
switch (s[j]) {
case '\r': break;
case '\t': fputs(" ", fp); break;
- default: fputc(s[j], fp);
+ default: putc(s[j], fp);
}
}
prev = i + 1;
@@ -624,7 +649,7 @@ writeblobgph(FILE *fp, const git_blob *blob)
switch (s[j]) {
case '\r': break;
case '\t': fputs(" ", fp); break;
- default: fputc(s[j], fp);
+ default: putc(s[j], fp);
}
}
}
@@ -653,12 +678,12 @@ printcommit(FILE *fp, struct commitinfo *ci)
fputs("|server|port]\n", fp);
fputs("Date: ", fp);
printtime(fp, &(ci->author->when));
- fputc('\n', fp);
+ putc('\n', fp);
}
if (ci->msg) {
- fputc('\n', fp);
+ putc('\n', fp);
gphtextnl(fp, ci->msg, strlen(ci->msg));
- fputc('\n', fp);
+ putc('\n', fp);
}
}
@@ -760,9 +785,9 @@ printshowfile(FILE *fp, struct commitinfo *ci)
if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
break;
- fputc('t', fp);
+ putc('t', fp);
gphtext(fp, hunk->header, hunk->header_len);
- fputc('\n', fp);
+ putc('\n', fp);
for (k = 0; ; k++) {
if (git_patch_get_line_in_hunk(&line, patch, j, k))
@@ -774,7 +799,7 @@ printshowfile(FILE *fp, struct commitinfo *ci)
else
fputs(" ", fp);
gphtext(fp, line->content, line->content_len);
- fputc('\n', fp);
+ putc('\n', fp);
}
}
}
@@ -807,11 +832,11 @@ 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);
git_revwalk_push(w, oid);
- git_revwalk_simplify_first_parent(w);
while (!git_revwalk_next(&id, w)) {
if (cachefile && !memcmp(&id, &lastoid, sizeof(id)))
@@ -825,19 +850,19 @@ 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;
- if (nlogcommits < 0) {
- writelogline(fp, ci);
- } else if (nlogcommits > 0) {
+ if (nlogcommits != 0) {
writelogline(fp, ci);
- nlogcommits--;
- if (!nlogcommits && ci->parentoid[0])
- fprintf(fp, "%16.16s More commits remaining [...]\n", "");
+ if (nlogcommits > 0)
+ nlogcommits--;
}
if (cachefile)
@@ -860,6 +885,12 @@ err:
}
git_revwalk_free(w);
+ if (nlogcommits == 0 && remcommits != 0) {
+ fprintf(fp, "%16.16s "
+ "%zu more commits remaining, fetch the repository\n",
+ "", remcommits);
+ }
+
return 0;
}
@@ -889,8 +920,8 @@ printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag)
xmlencode(fp, ci->summary, strlen(ci->summary));
fputs("</title>\n", fp);
}
- fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"commit/%s.gph\" />\n",
- ci->oid);
+ fprintf(fp, "<link rel=\"alternate\" href=\"%scommit/%s.gph\" />\n",
+ baseurl, ci->oid);
if (ci->author) {
fputs("<author>\n<name>", fp);
@@ -911,10 +942,10 @@ printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag)
xmlencode(fp, ci->author->email, strlen(ci->author->email));
fputs(">\nDate: ", fp);
printtime(fp, &(ci->author->when));
- fputc('\n', fp);
+ putc('\n', fp);
}
if (ci->msg) {
- fputc('\n', fp);
+ putc('\n', fp);
xmlencode(fp, ci->msg, strlen(ci->msg));
}
fputs("\n</content>\n</entry>\n", fp);
@@ -941,7 +972,6 @@ writeatom(FILE *fp, int all)
if (all) {
git_revwalk_new(&w, repo);
git_revwalk_push_head(w);
- git_revwalk_simplify_first_parent(w);
for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
if (!(ci = commitinfo_getbyoid(&id)))
break;
@@ -967,11 +997,11 @@ writeatom(FILE *fp, int all)
return 0;
}
-int
-writeblob(git_object *obj, const char *fpath, const char *filename, git_off_t filesize)
+size_t
+writeblob(git_object *obj, const char *fpath, const char *filename, size_t filesize)
{
char tmp[PATH_MAX] = "", *d;
- int lc = 0;
+ size_t lc = 0;
FILE *fp;
if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
@@ -983,9 +1013,9 @@ writeblob(git_object *obj, const char *fpath, const char *filename, git_off_t fi
fp = efopen(fpath, "w");
writeheader(fp, filename);
- fputc('t', fp);
+ putc('t', fp);
gphtext(fp, filename, strlen(filename));
- fprintf(fp, " (%juB)\n", (uintmax_t)filesize);
+ fprintf(fp, " (%zuB)\n", filesize);
fputs("---\n", fp);
if (git_blob_is_binary((git_blob *)obj)) {
@@ -1048,11 +1078,10 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
{
const git_tree_entry *entry = NULL;
git_object *obj = NULL;
- git_off_t filesize;
const char *entryname;
- char buf[256], filepath[PATH_MAX], entrypath[PATH_MAX];
- size_t count, i;
- int lc, r, ret;
+ char buf[256], filepath[PATH_MAX], entrypath[PATH_MAX], oid[8];
+ size_t count, i, lc, filesize;
+ int r, ret;
count = git_tree_entrycount(tree);
for (i = 0; i < count; i++) {
@@ -1093,9 +1122,9 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
gphlink(fp, buf, strlen(buf));
fputs(" ", fp);
if (lc > 0)
- fprintf(fp, "%7dL", lc);
+ fprintf(fp, "%7zuL", lc);
else
- fprintf(fp, "%7juB", (uintmax_t)filesize);
+ fprintf(fp, "%7zuB", filesize);
fprintf(fp, "|%s/", relpath);
gphlink(fp, filepath, strlen(filepath));
fputs("|server|port]\n", fp);
@@ -1103,8 +1132,10 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
} else if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) {
/* commit object in tree is a submodule */
fputs("[1|m--------- ", fp);
- utf8pad(buf, sizeof(buf), entrypath, 50, ' ');
- gphlink(fp, buf, strlen(buf));
+ gphlink(fp, entrypath, strlen(entrypath));
+ fputs(" @ ", fp);
+ git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry));
+ gphlink(fp, oid, strlen(oid));
fprintf(fp, "|%s/file/.gitmodules.gph|server|port]\n", relpath);
/* NOTE: linecount omitted */
}
@@ -1198,7 +1229,8 @@ writerefs(FILE *fp)
void
usage(char *argv0)
{
- fprintf(stderr, "%s [-b baseprefix] [-c cachefile | -l commits] repodir\n", argv0);
+ fprintf(stderr, "%s [-b baseprefix] [-c cachefile | -l commits] "
+ "[-u baseurl] repodir\n", argv0);
exit(1);
}
@@ -1237,6 +1269,10 @@ main(int argc, char *argv[])
if (argv[i][0] == '\0' || *p != '\0' ||
nlogcommits <= 0 || errno)
usage(argv[0]);
+ } else if (argv[i][1] == 'u') {
+ if (i + 1 >= argc)
+ usage(argv[0]);
+ baseurl = argv[++i];
}
}
if (!repodir)
@@ -1245,7 +1281,11 @@ 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, "");
#ifdef __OpenBSD__
if (unveil(repodir, "r") == -1)
@@ -1314,7 +1354,7 @@ main(int argc, char *argv[])
}
/* check LICENSE */
- for (i = 0; i < sizeof(licensefiles) / sizeof(*licensefiles) && !license; i++) {
+ for (i = 0; i < LEN(licensefiles) && !license; i++) {
if (!git_revparse_single(&obj, repo, licensefiles[i]) &&
git_object_type(obj) == GIT_OBJ_BLOB)
license = licensefiles[i] + strlen("HEAD:");
@@ -1322,7 +1362,7 @@ main(int argc, char *argv[])
}
/* check README */
- for (i = 0; i < sizeof(readmefiles) / sizeof(*readmefiles) && !readme; i++) {
+ for (i = 0; i < LEN(readmefiles) && !readme; i++) {
if (!git_revparse_single(&obj, repo, readmefiles[i]) &&
git_object_type(obj) == GIT_OBJ_BLOB)
readme = readmefiles[i] + strlen("HEAD:");