#include <git2.h>
#include "compat.h"
-#include "config.h"
struct deltainfo {
git_patch *patch;
size_t ndeltas;
};
+/* summary length (bytes) in the log */
+static const unsigned summarylen = 70;
+/* display line count or file size in file tree index */
+static const int showlinecount = 1;
+
static git_repository *repo;
static const char *relpath = "";
static const char *repodir;
static char *name = "";
-static char *stripped_name;
+static char *strippedname = "";
static char description[255];
static char cloneurl[1024];
static int haslicense, hasreadme, hassubmodules;
static FILE *rcachefp, *wcachefp;
static const char *cachefile;
+#ifndef USE_PLEDGE
+#define pledge(p1,p2) 0
+#endif
+
+void
+joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
+{
+ int r;
+
+ r = snprintf(buf, bufsiz, "%s%s%s",
+ path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
+ if (r == -1 || (size_t)r >= bufsiz)
+ errx(1, "path truncated: '%s%s%s'",
+ path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
+}
+
void
deltainfo_free(struct deltainfo *di)
{
err(1, "calloc");
if (git_patch_from_diff(&patch, ci->diff, i)) {
git_patch_free(patch);
+ free(di);
goto err;
}
di->patch = patch;
delta = git_patch_get_delta(patch);
- /* check binary data */
+ /* skip stats for binary data */
if (delta->flags & GIT_DIFF_FLAG_BINARY)
continue;
git_tree_free(ci->commit_tree);
git_tree_free(ci->parent_tree);
git_commit_free(ci->commit);
+ git_commit_free(ci->parent);
+ free(ci);
}
struct commitinfo *
err:
commitinfo_free(ci);
- free(ci);
return NULL;
}
switch(*s) {
case '<': fputs("<", fp); break;
case '>': fputs(">", fp); break;
- case '\'': fputs("'", fp); break;
+ case '\'': fputs("'", fp); break;
case '&': fputs("&", fp); break;
case '"': fputs(""", fp); break;
default: fputc(*s, fp);
}
}
-/* Some implementations of dirname(3) return a pointer to a static
- * internal buffer (OpenBSD). Others modify the contents of `path` (POSIX).
- * This is a wrapper function that is compatible with both versions.
- * The program will error out if dirname(3) failed, this can only happen
- * with the OpenBSD version. */
-char *
-xdirname(const char *path)
-{
- char *p, *b;
-
- if (!(p = strdup(path)))
- err(1, "strdup");
- if (!(b = dirname(p)))
- err(1, "dirname");
- if (!(b = strdup(b)))
- err(1, "strdup");
- free(p);
-
- return b;
-}
-
int
mkdirp(const char *path)
{
{
struct tm *intm;
time_t t;
- int offset, sign = '+';
char out[32];
- offset = intime->offset * 60;
- t = (time_t)intime->time + offset;
+ t = (time_t)intime->time + (intime->offset * 60);
if (!(intm = gmtime(&t)))
return;
strftime(out, sizeof(out), "%a %b %e %H:%M:%S", intm);
- if (offset < 0) {
- offset = -offset;
- sign = '-';
- }
- fprintf(fp, "%s %c%02d%02d", out, sign, offset / 60, offset % 60);
+ if (intime->offset < 0)
+ fprintf(fp, "%s -%02d%02d", out,
+ -(intime->offset) / 60, -(intime->offset) % 60);
+ else
+ fprintf(fp, "%s +%02d%02d", out,
+ intime->offset / 60, intime->offset % 60);
}
void
fputs(out, fp);
}
-int
+void
writeheader(FILE *fp, const char *title)
{
fputs("<!DOCTYPE html>\n"
- "<html dir=\"ltr\" lang=\"en\">\n<head>\n"
+ "<html>\n<head>\n"
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
- "<meta http-equiv=\"Content-Language\" content=\"en\" />\n<title>", fp);
+ "<title>", fp);
xmlencode(fp, title, strlen(title));
- if (title[0] && stripped_name[0])
+ if (title[0] && strippedname[0])
fputs(" - ", fp);
- xmlencode(fp, stripped_name, strlen(stripped_name));
+ xmlencode(fp, strippedname, strlen(strippedname));
if (description[0])
fputs(" - ", fp);
xmlencode(fp, description, strlen(description));
fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>",
relpath, relpath);
fputs("</td><td><h1>", fp);
- xmlencode(fp, stripped_name, strlen(stripped_name));
+ xmlencode(fp, strippedname, strlen(strippedname));
fputs("</h1><span class=\"desc\">", fp);
xmlencode(fp, description, strlen(description));
fputs("</span></td></tr>", fp);
if (haslicense)
fprintf(fp, " | <a href=\"%sfile/LICENSE.html\">LICENSE</a>", relpath);
fputs("</td></tr></table>\n<hr/>\n<div id=\"content\">\n", fp);
-
- return 0;
}
-int
+void
writefooter(FILE *fp)
{
- return !fputs("</div>\n</body>\n</html>\n", fp);
+ fputs("</div>\n</body>\n</html>\n", fp);
}
int
writeblobhtml(FILE *fp, const git_blob *blob)
{
- off_t i;
- size_t n = 0;
- char *nfmt = "<a href=\"#l%d\" id=\"l%d\">%d</a>\n";
+ size_t n = 0, i, prev;
+ const char *nfmt = "<a href=\"#l%d\" class=\"line\" id=\"l%d\">%6d</a> ";
const char *s = git_blob_rawcontent(blob);
git_off_t len = git_blob_rawsize(blob);
- fputs("<table id=\"blob\"><tr><td class=\"num\"><pre>\n", fp);
+ fputs("<pre id=\"blob\">\n", fp);
- if (len) {
- n++;
- fprintf(fp, nfmt, n, n, n);
- for (i = 0; i < len - 1; i++) {
- if (s[i] == '\n') {
- n++;
- fprintf(fp, nfmt, n, n, n);
- }
+ if (len > 0) {
+ for (i = 0, prev = 0; i < (size_t)len; i++) {
+ if (s[i] != '\n')
+ continue;
+ n++;
+ fprintf(fp, nfmt, n, n, n);
+ xmlencode(fp, &s[prev], i - prev + 1);
+ prev = i + 1;
+ }
+ /* trailing data */
+ if ((i - prev) > 0) {
+ n++;
+ fprintf(fp, nfmt, n, n, n);
+ xmlencode(fp, &s[prev], len - prev);
}
}
- fputs("</pre></td><td><pre>\n", fp);
- xmlencode(fp, s, (size_t)len);
- fputs("</pre></td></tr></table>\n", fp);
+ fputs("</pre>\n", fp);
return n;
}
ci->ndeltas > 1000 ||
ci->addcount > 100000 ||
ci->delcount > 100000) {
- fprintf(fp, "(diff is too large, output suppressed)");
+ fputs("Diff is too large, output suppressed.\n", fp);
return;
}
fputs("<b>Diffstat:</b>\n<table>", fp);
for (i = 0; i < ci->ndeltas; i++) {
delta = git_patch_get_delta(ci->deltas[i]->patch);
- fputs("<tr><td>", fp);
+ fprintf(fp, "<tr><td><a href=\"#h%zu\">", i);
xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
if (strcmp(delta->old_file.path, delta->new_file.path)) {
fputs(" -> ", fp);
memset(&linestr, '+', add);
memset(&linestr[add], '-', del);
- fprintf(fp, "</td><td> | </td><td class=\"num\">%zu</td><td><span class=\"i\">",
+ fprintf(fp, "</a></td><td> | </td><td class=\"num\">%zu</td><td><span class=\"i\">",
ci->deltas[i]->addcount + ci->deltas[i]->delcount);
fwrite(&linestr, 1, add, fp);
fputs("</span><span class=\"d\">", fp);
fwrite(&linestr[add], 1, del, fp);
fputs("</span></td></tr>\n", fp);
}
- fprintf(fp, "</table>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
+ fprintf(fp, "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
ci->filecount, ci->filecount == 1 ? "" : "s",
ci->addcount, ci->addcount == 1 ? "" : "s",
ci->delcount, ci->delcount == 1 ? "" : "s");
for (i = 0; i < ci->ndeltas; i++) {
patch = ci->deltas[i]->patch;
delta = git_patch_get_delta(patch);
- fprintf(fp, "<b>diff --git a/<a href=\"%sfile/%s.html\">%s</a> b/<a href=\"%sfile/%s.html\">%s</a></b>\n",
- relpath, delta->old_file.path, delta->old_file.path,
+ fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"%sfile/%s.html\">%s</a> b/<a href=\"%sfile/%s.html\">%s</a></b>\n",
+ i, relpath, delta->old_file.path, delta->old_file.path,
relpath, delta->new_file.path, delta->new_file.path);
/* check binary data */
if (delta->flags & GIT_DIFF_FLAG_BINARY) {
- fputs("Binary files differ\n", fp);
+ fputs("Binary files differ.\n", fp);
continue;
}
fputs("</td><td>", fp);
if (ci->author)
xmlencode(fp, ci->author->name, strlen(ci->author->name));
- fputs("</td><td class=\"num\">", fp);
+ fputs("</td><td class=\"num\" align=\"right\">", fp);
fprintf(fp, "%zu", ci->filecount);
- fputs("</td><td class=\"num\">", fp);
+ fputs("</td><td class=\"num\" align=\"right\">", fp);
fprintf(fp, "+%zu", ci->addcount);
- fputs("</td><td class=\"num\">", fp);
+ fputs("</td><td class=\"num\" align=\"right\">", fp);
fprintf(fp, "-%zu", ci->delcount);
fputs("</td></tr>\n", fp);
}
fputc('\n', fp);
xmlencode(fp, ci->msg, strlen(ci->msg));
}
- fputs("\n</content>\n", fp);
-
- fputs("</entry>\n", fp);
+ fputs("\n</content>\n</entry>\n", fp);
}
int
fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
- xmlencode(fp, stripped_name, strlen(stripped_name));
+ xmlencode(fp, strippedname, strlen(strippedname));
fputs(", branch HEAD</title>\n<subtitle>", fp);
xmlencode(fp, description, strlen(description));
fputs("</subtitle>\n", fp);
}
git_revwalk_free(w);
- fputs("</feed>", fp);
+ fputs("</feed>\n", fp);
return 0;
}
int lc = 0;
FILE *fp;
- d = xdirname(fpath);
- if (mkdirp(d)) {
- free(d);
+ if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
+ errx(1, "path truncated: '%s'", fpath);
+ if (!(d = dirname(tmp)))
+ err(1, "dirname");
+ if (mkdirp(d))
return -1;
- }
- free(d);
- p = fpath;
- while (*p) {
+ for (p = fpath, tmp[0] = '\0'; *p; p++) {
if (*p == '/' && strlcat(tmp, "../", sizeof(tmp)) >= sizeof(tmp))
errx(1, "path truncated: '../%s'", tmp);
- p++;
}
relpath = tmp;
fputs("</p><hr/>", fp);
if (git_blob_is_binary((git_blob *)obj)) {
- fputs("<p>Binary file</p>\n", fp);
+ fputs("<p>Binary file.</p>\n", fp);
} else {
lc = writeblobhtml(fp, (git_blob *)obj);
if (ferror(fp))
}
int
-writefilestree(FILE *fp, git_tree *tree, const char *branch, const char *path)
+writefilestree(FILE *fp, git_tree *tree, const char *path)
{
const git_tree_entry *entry = NULL;
git_submodule *module = NULL;
if (!(entry = git_tree_entry_byindex(tree, i)) ||
!(entryname = git_tree_entry_name(entry)))
return -1;
- r = snprintf(entrypath, sizeof(entrypath), "%s%s%s",
- path, path[0] ? "/" : "", entryname);
- if (r == -1 || (size_t)r >= sizeof(entrypath))
- errx(1, "path truncated: '%s%s%s'",
- path, path[0] ? "/" : "", entryname);
-
- r = snprintf(filepath, sizeof(filepath), "file/%s%s%s.html",
- path, path[0] ? "/" : "", entryname);
+ joinpath(entrypath, sizeof(entrypath), path, entryname);
+
+ r = snprintf(filepath, sizeof(filepath), "file/%s.html",
+ entrypath);
if (r == -1 || (size_t)r >= sizeof(filepath))
- errx(1, "path truncated: 'file/%s%s%s.html'",
- path, path[0] ? "/" : "", entryname);
+ errx(1, "path truncated: 'file/%s.html'", entrypath);
if (!git_tree_entry_to_object(&obj, repo, entry)) {
switch (git_object_type(obj)) {
break;
case GIT_OBJ_TREE:
/* NOTE: recurses */
- ret = writefilestree(fp, (git_tree *)obj, branch,
+ ret = writefilestree(fp, (git_tree *)obj,
entrypath);
git_object_free(obj);
if (ret)
fputs(filemode(git_tree_entry_filemode(entry)), fp);
fprintf(fp, "</td><td><a href=\"%s%s\">", relpath, filepath);
xmlencode(fp, entrypath, strlen(entrypath));
- fputs("</a></td><td class=\"num\">", fp);
+ fputs("</a></td><td class=\"num\" align=\"right\">", fp);
if (showlinecount && lc > 0)
fprintf(fp, "%dL", lc);
else
fprintf(fp, "%juB", (uintmax_t)filesize);
fputs("</td></tr>\n", fp);
+ git_object_free(obj);
} else if (!git_submodule_lookup(&module, repo, entryname)) {
fprintf(fp, "<tr><td>m---------</td><td><a href=\"%sfile/.gitmodules.html\">",
relpath);
xmlencode(fp, entrypath, strlen(entrypath));
git_submodule_free(module);
- fputs("</a></td><td class=\"num\"></td></tr>\n", fp);
+ fputs("</a></td><td class=\"num\" align=\"right\"></td></tr>\n", fp);
}
}
}
int
-writefiles(FILE *fp, const git_oid *id, const char *branch)
+writefiles(FILE *fp, const git_oid *id)
{
git_tree *tree = NULL;
git_commit *commit = NULL;
int ret = -1;
fputs("<table id=\"files\"><thead>\n<tr>"
- "<td>Mode</td><td>Name</td><td class=\"num\">Size</td>"
+ "<td><b>Mode</b></td><td><b>Name</b></td>"
+ "<td class=\"num\" align=\"right\"><b>Size</b></td>"
"</tr>\n</thead><tbody>\n", fp);
- if (git_commit_lookup(&commit, repo, id) ||
- git_commit_tree(&tree, commit))
- goto err;
- ret = writefilestree(fp, tree, branch, "");
+ if (!git_commit_lookup(&commit, repo, id) &&
+ !git_commit_tree(&tree, commit))
+ ret = writefilestree(fp, tree, "");
-err:
fputs("</tbody></table>", fp);
git_commit_free(commit);
{
git_reference *r1 = (*(git_reference **)v1);
git_reference *r2 = (*(git_reference **)v2);
- int t1, t2;
-
- t1 = git_reference_is_branch(r1);
- t2 = git_reference_is_branch(r2);
+ int r;
- if (t1 != t2)
- return t1 - t2;
+ if ((r = git_reference_is_branch(r1) - git_reference_is_branch(r2)))
+ return r;
return strcmp(git_reference_shorthand(r1),
git_reference_shorthand(r2));
default:
continue;
}
- if (!(id = git_reference_target(r)))
- goto err;
- if (git_reference_peel(&obj, r, GIT_OBJ_ANY))
+ if (!git_reference_target(r) ||
+ git_reference_peel(&obj, r, GIT_OBJ_ANY))
goto err;
if (!(id = git_object_id(obj)))
goto err;
/* print header if it has an entry (first). */
if (++count == 1) {
- fprintf(fp, "<h2>%s</h2><table id=\"%s\"><thead>\n<tr><td>Name</td>"
- "<td>Last commit date</td><td>Author</td>\n</tr>\n</thead><tbody>\n",
- titles[j], ids[j]);
+ fprintf(fp, "<h2>%s</h2><table id=\"%s\">"
+ "<thead>\n<tr><td><b>Name</b></td>"
+ "<td><b>Last commit date</b></td>"
+ "<td><b>Author</b></td>\n</tr>\n"
+ "</thead><tbody>\n",
+ titles[j], ids[j]);
}
relpath = "";
return 0;
}
-void
-joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
-{
- int r;
-
- r = snprintf(buf, bufsiz, "%s%s%s",
- repodir, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
- if (r == -1 || (size_t)r >= bufsiz)
- errx(1, "path truncated: '%s%s%s'",
- path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
-}
-
void
usage(char *argv0)
{
size_t n;
int i, fd;
+ if (pledge("stdio rpath wpath cpath", NULL) == -1)
+ err(1, "pledge");
+
for (i = 1; i < argc; i++) {
if (argv[i][0] != '-') {
if (repodir)
}
/* find HEAD */
- if (git_revparse_single(&obj, repo, "HEAD"))
- return 1;
- head = git_object_id(obj);
+ if (!git_revparse_single(&obj, repo, "HEAD"))
+ head = git_object_id(obj);
git_object_free(obj);
+ /* don't cache if there is no HEAD */
+ if (!head)
+ cachefile = NULL;
+
/* use directory name as name */
if ((name = strrchr(repodirabs, '/')))
name++;
name = "";
/* strip .git suffix */
- if (!(stripped_name = strdup(name)))
+ if (!(strippedname = strdup(name)))
err(1, "strdup");
- if ((p = strrchr(stripped_name, '.')))
+ if ((p = strrchr(strippedname, '.')))
if (!strcmp(p, ".git"))
*p = '\0';
}
/* check LICENSE */
- haslicense = !git_revparse_single(&obj, repo, "HEAD:LICENSE");
+ haslicense = (!git_revparse_single(&obj, repo, "HEAD:LICENSE") &&
+ git_object_type(obj) == GIT_OBJ_BLOB);
git_object_free(obj);
+
/* check README */
- hasreadme = !git_revparse_single(&obj, repo, "HEAD:README");
+ hasreadme = (!git_revparse_single(&obj, repo, "HEAD:README") &&
+ git_object_type(obj) == GIT_OBJ_BLOB);
git_object_free(obj);
- hassubmodules = !git_revparse_single(&obj, repo, "HEAD:.gitmodules");
+
+ hassubmodules = (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") &&
+ git_object_type(obj) == GIT_OBJ_BLOB);
git_object_free(obj);
/* log for HEAD */
relpath = "";
mkdir("commit", 0755);
writeheader(fp, "Log");
- fputs("<table id=\"log\"><thead>\n<tr><td>Date</td><td>Commit message</td>"
- "<td>Author</td><td class=\"num\">Files</td><td class=\"num\">+</td>"
- "<td class=\"num\">-</td></tr>\n</thead><tbody>\n", fp);
+ fputs("<table id=\"log\"><thead>\n<tr><td><b>Date</b></td>"
+ "<td><b>Commit message</b></td>"
+ "<td><b>Author</b></td><td class=\"num\" align=\"right\"><b>Files</b></td>"
+ "<td class=\"num\" align=\"right\"><b>+</b></td>"
+ "<td class=\"num\" align=\"right\"><b>-</b></td></tr>\n</thead><tbody>\n", fp);
if (cachefile) {
/* read from cache file (does not need to exist) */
n = fread(buf, 1, sizeof(buf), rcachefp);
if (ferror(rcachefp))
err(1, "fread");
- if (fwrite(buf, 1, n, fp) != n)
- err(1, "fwrite");
- if (fwrite(buf, 1, n, wcachefp) != n)
+ if (fwrite(buf, 1, n, fp) != n ||
+ fwrite(buf, 1, n, wcachefp) != n)
err(1, "fwrite");
}
fclose(rcachefp);
}
fclose(wcachefp);
} else {
- writelog(fp, head);
+ if (head)
+ writelog(fp, head);
}
fputs("</tbody></table>", fp);
/* files for HEAD */
fp = efopen("files.html", "w");
writeheader(fp, "Files");
- writefiles(fp, head, "HEAD");
+ if (head)
+ writefiles(fp, head);
writefooter(fp);
fclose(fp);