Compare commits

...

28 commits

Author SHA1 Message Date
xfnw a8e1a69829 fix memory leak on realloc falure 2022-06-30 19:12:31 -04:00
xfnw 271b68a1b5 include simpler example post-update 2022-06-30 15:58:24 -04:00
xfnw 8157440847 Merge remote-tracking branch 'upstream/master' 2022-06-30 15:37:14 -04:00
Hiltjo Posthuma 2890451154 Revert "remain compatible with slightly older libgit versions for now"
This reverts commit 70541c5e2c.

Reported by Anton:
The last commit[1] is not correct as GIT_OPT_SET_OWNER_VALIDATION is not
a preprocessor directive but rather an enum. Causing the branch to never
be entered.
2022-05-27 21:29:14 +02:00
Hiltjo Posthuma 70541c5e2c remain compatible with slightly older libgit versions for now 2022-05-24 14:07:27 +02:00
Anton Lindqvist 1357ad5181 Allow git to run on an other user repository
Reported by Anton:

"Recent versions of libgit2 broke stagit for me due to the added opt-out
GIT_OPT_SET_OWNER_VALIDATION configuration knob. My repositories are owned by
root:vcs and I run stagit as another user which happens to be in vcs group but
not the owner of the repository. Disabling the validation makes stagit work as
expected again."

Some notes:

When using regular git it also provides a knob. This is due to a security
concern in some cases, which is not applicable to stagit.

	git log somerepo

	fatal: unsafe repository ('somerepo' is owned by someone else)
	To add an exception for this directory, call:

	        git config --global --add safe.directory somerepo

See also / related:
- https://github.blog/2022-04-12-git-security-vulnerability-announced/
2022-05-24 11:09:05 +02:00
Hiltjo Posthuma a8a5e9c3b3 bump version to 1.1 2022-04-02 17:35:47 +02:00
Hiltjo Posthuma d0e36eb6ab improve stream read and write error handling 2022-03-19 12:51:40 +01:00
Hiltjo Posthuma 7c419a8bac add dark mode support for the example stylesheet 2022-03-19 12:23:16 +01:00
xfnw 0eb570ebae Merge with upstream 2022-01-23 13:34:00 -05:00
Hiltjo Posthuma 037d2c7053 bump LICENSE year 2022-01-03 12:22:57 +01:00
Hiltjo Posthuma 4d19863b06 libgit2 config opts: set the search to an empty path
Otherwise this would search outside the unveiled paths and cause an unveil
violation.

Reported by Anton Lindqvist, thanks!
2022-01-03 12:22:52 +01:00
Hiltjo Posthuma df2a31c67a do not percent-encode: ',' or '-' or '.' it looks ugly 2021-12-14 20:52:18 +01:00
Hiltjo Posthuma cd5814fded bump version to 1.0 2021-11-30 18:13:20 +01:00
Quentin Rameau 67e5e6c5e7 Print the number of remaining commits 2021-11-16 18:18:32 +01:00
Hiltjo Posthuma 5f78d89d59 ignore '\r' in writing the blob aswell
Follow-up on commit 295e4b8cb9 which changed it
for diffs.
2021-11-16 14:24:30 +01:00
Hiltjo Posthuma 6eeefd2087 percent encode characters in path names
Paths could contain characters like # (fragment), '?', control-characters, etc.
2021-11-16 14:16:46 +01:00
Hiltjo Posthuma 961cf0f9d8 encode the name, it could contain XML entities
Like ", which would unquote the attribute value. Crazy but true.
2021-11-16 11:44:23 +01:00
Hiltjo Posthuma 1b6a24c893 man pages: add EXAMPLES section 2021-08-03 19:22:50 +02:00
Hiltjo Posthuma 61be8f5328 small typo fixes and url -> URL 2021-07-31 01:09:45 +02:00
Hiltjo Posthuma 57f84d0fd1 bump version to 0.9.6 2021-05-27 12:41:43 +02:00
Hiltjo Posthuma 45394004a3 man page: codemadness is the primary server. make logo brandless (not 2f30) 2021-05-18 11:42:41 +02:00
Quentin Rameau ddc581bd90 README: improve a bit the usage examples 2021-05-18 10:42:21 +02:00
Hiltjo Posthuma c827ab1b1d do not simplify the history by first-parent
Reference:
https://libgit2.org/libgit2/#HEAD/group/revwalk/git_revwalk_simplify_first_parent

Noticed on merge commits on:
https://git.simple-cc.org/scc/

Reported by quinq, thanks!
2021-05-05 19:15:58 +02:00
Hiltjo Posthuma 727e02be6c tiny comment change 2021-03-25 18:17:34 +01:00
Hiltjo Posthuma 295e4b8cb9 add function to print a single line, ignoring \r and \n
This can happen when there is no newline at end of file in the diff which is
served by libgit2 as:

"\n\ No newline at end of file\n".
2021-03-25 18:13:13 +01:00
Hiltjo Posthuma 995f7d5c5d add meta viewport on stagit-index too
Patch by Oscar Benedito, thanks!
2021-03-19 11:29:53 +01:00
Hiltjo Posthuma f464058501 bump version to 0.9.5 2021-03-14 16:23:58 +01:00
5 changed files with 148 additions and 36 deletions

View file

@ -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"),

View file

@ -1,7 +1,7 @@
.POSIX:
NAME = stagit
VERSION = 0.9.6
VERSION = 1.1
# paths
PREFIX = /usr/local

9
post-update Executable file
View file

@ -0,0 +1,9 @@
#!/bin/sh
git update-server-info
stagit .
ln -sf log.html index.html
cd ..
stagit-index */ > index.html

View file

@ -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);
@ -151,7 +183,13 @@ main(int argc, char *argv[])
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;
}

125
stagit.c
View file

@ -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)
{
@ -314,8 +324,14 @@ getrefs(struct referenceinfo **pris, size_t *prefcount)
if (!(ci = commitinfo_getbyoid(id)))
break;
if (!(ris = reallocarray(ris, refcount + 1, sizeof(*ris))))
err(1, "realloc");
{
struct referenceinfo *newris;
if (!(newris = reallocarray(ris, refcount + 1, sizeof(*ris)))) {
free(ris);
err(1, "realloc");
}
ris = newris;
}
ris[refcount].ci = ci;
ris[refcount].ref = r;
refcount++;
@ -359,6 +375,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 +518,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=\"%s../favicon.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=\"%s../style.css\" />\n", relpath);
fputs("</head>\n<body>\n<table><tr><td>", fp);
fprintf(fp, "<a href=\"../%s\"><img src=\"%s../logo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>",
@ -495,7 +535,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 +578,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 +609,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 +704,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 +780,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);
@ -758,8 +800,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;
@ -767,15 +812,10 @@ writelog(FILE *fp, const git_oid *oid)
if (commitinfo_getstats(ci) == -1)
goto err;
if (nlogcommits < 0) {
if (nlogcommits != 0) {
writelogline(fp, ci);
} else 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)
@ -790,6 +830,7 @@ writelog(FILE *fp, const git_oid *oid)
printshowfile(fpfile, ci);
fputs("</pre>\n", fpfile);
writefooter(fpfile);
checkfileerror(fpfile, path, 'w');
fclose(fpfile);
}
err:
@ -797,6 +838,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;
@ -933,14 +980,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 = "";
@ -1035,7 +1081,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);
@ -1190,7 +1236,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)
@ -1242,6 +1294,7 @@ main(int argc, char *argv[])
if (fpread) {
if (!fgets(description, sizeof(description), fpread))
description[0] = '\0';
checkfileerror(fpread, path, 'r');
fclose(fpread);
}
@ -1254,8 +1307,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 */
@ -1315,13 +1369,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)
@ -1330,6 +1386,7 @@ main(int argc, char *argv[])
fputs("</tbody></table>", fp);
writefooter(fp);
checkfileerror(fp, "log.html", 'w');
fclose(fp);
/* files for HEAD */
@ -1338,6 +1395,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 */
@ -1345,16 +1403,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 */