commit 8684421e547f08888751ed14042b8063eadc1e45
parent 546c6d82185ea286e7365c7abe137be2ef23045d
Author: Emma Weaver <emma@waeaves.com>
Date: Tue, 19 May 2026 15:42:26 -0400
Implemented nesting folders & line/byte counts
Diffstat:
| M | TODO | 20 | +++++++++++++------- |
| M | stagit.c | 861 | ++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- |
2 files changed, 576 insertions(+), 305 deletions(-)
diff --git a/TODO b/TODO
@@ -1,26 +1,32 @@
-STUFF I WANT TO CHANGE ABOUT STAGIT-
(might rename it to "statigit" also.)
+[ ] Separate out stagit.c into clean, standalone modules.
+ - but how to handle includes/headers...
+
[ ] Redesign options / I/O
- Usage: stagit [-i] [-d DESTDIR] [-c CACHEDIR] REPO [REPO_2 ... REPO_N]
- generates index page if -i
- puts repos in their own subdirectory if -i or REPO_2
- writes output into DESTDIR (defaults to pwd)
-[ ] handle missing decorations (desc/logo/favicon) better
-
[ ] create config.h file
- reused format strings (such as header)
---NEW FEATURES---
-[ ] add lines of code totals on files page
-[ ] add lines of code on each file's page
[x] include binary files in static site + link to them
-[ ] add support for projects with folders in them
- - show totals "123L + 456B" for each directory
-[ ] tweak paths that start with dots, so that they don't get hidden or 403'd
+[x] add support for projects with folders in them
+[x] backlink on subdirectory pages
+[ ] add lines of code totals on files page
+[x] add lines of code on each file's page
+[x] show line/byte totals for each directory
+[ ] tweak paths that start with dots, so that they don't get hidden or 403'd (?)
+[ ] handle missing decorations (desc/logo/favicon) better
---END NEW FEATURES---
+[ ] Remove globals
+ - RELPATH. wtf even...
+
[ ] Redo caching, to never repeat work
- but it needs to also not increase the complexity.
- it's ok to duplicate storage
diff --git a/stagit.c b/stagit.c
@@ -16,6 +16,7 @@
#include "compat.h"
+#define CPUT(f, c) putc((c), (f))
#define SPUTF(f, s) fputs((s), (f))
#define LENGTH(s) (sizeof(s)/sizeof(*s))
@@ -57,6 +58,16 @@ struct ReferenceInfo {
struct CommitInfo *ci;
};
+struct Weight {
+ size_t bytes;
+ size_t lines;
+};
+
+const char orders[] = { ' ', 'k', 'M', 'G', 'T', 'P', 'E' };
+
+/* GLOBALS */
+// TODO untangle these, pass them around instead...
+
static git_repository *repo;
/* flags + arguments */
@@ -78,7 +89,6 @@ static char *readme;
static long long nlogcommits = -1; /* -1 indicates not used */
/* cache */
-// TODO yuckyyy
static git_oid lastoid;
static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */
static FILE *rcachefp, *wcachefp;
@@ -114,6 +124,36 @@ join_path(char *buf, size_t bufsiz, const char *path, const char *path2)
}
void
+find_license()
+{
+ git_object *obj = NULL;
+ int i;
+ for (i = 0; i < LENGTH(license_files) && !license; i++) {
+ if (
+ !git_revparse_single(&obj, repo, license_files[i]) &&
+ git_object_type(obj) == GIT_OBJ_BLOB
+ )
+ license = license_files[i] + strlen("HEAD:");
+ git_object_free(obj);
+ }
+}
+
+void
+find_readme()
+{
+ git_object *obj = NULL;
+ int i;
+ for (i = 0; i < LENGTH(readme_files) && !readme; i++) {
+ if (
+ !git_revparse_single(&obj, repo, readme_files[i]) &&
+ git_object_type(obj) == GIT_OBJ_BLOB
+ )
+ readme = readme_files[i] + strlen("HEAD:");
+ git_object_free(obj);
+ }
+}
+
+void
delta_info_free(struct DeltaInfo *di)
{
if (!di) return;
@@ -375,17 +415,6 @@ err:
return -1;
}
-FILE *
-efopen(const char *filename, const char *flags)
-{
- FILE *fp;
-
- if (!(fp = fopen(filename, flags)))
- err(1, "fopen: '%s'", filename);
-
- return fp;
-}
-
/* Percent-encode, see RFC3986 section 2.1. */
void
put_percent_encoded(FILE *fp, const char *s, size_t len)
@@ -452,7 +481,7 @@ int
mkdirp(const char *path)
{
char tmp[PATH_MAX], *p;
-
+
if (strlcpy(tmp, path, sizeof(tmp)) >= sizeof(tmp))
errx(1, "path truncated: '%s'", path);
for (p = tmp + (tmp[0] == '/'); *p; p++) {
@@ -463,11 +492,21 @@ mkdirp(const char *path)
return -1;
*p = '/';
}
- if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
- return -1;
return 0;
}
+FILE *
+efopen(const char *filename, const char *flags)
+{
+ FILE *fp;
+
+ mkdirp(filename);
+ if (!(fp = fopen(filename, flags)))
+ err(1, "fopen: '%s'", filename);
+
+ return fp;
+}
+
void
printtimez(FILE *fp, const git_time *intime)
{
@@ -525,6 +564,12 @@ printtimeshort(FILE *fp, const git_time *intime)
fputs(out, fp);
}
+char *
+put_absolute_path(FILE *fp, const char * path)
+{
+ fprintf(fp, "/%s%s", relpath, path);
+}
+
void
put_header(FILE *fp, const char *title)
{
@@ -544,78 +589,98 @@ put_header(FILE *fp, const char *title)
if (description[0])
fputs(" - ", fp);
put_xml(fp, description, strlen(description));
- fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
- fputs("<link rel=\"alternate\" type=\"application/atom+xml\" title=\"", fp);
+ SPUTF(fp, "</title>" "\n");
+
+ SPUTF(fp, "<link rel=\"icon\" type=\"image/png\" href=\"");
+ put_absolute_path(fp, "favicon.png");
+ SPUTF(fp, "\" />" "\n");
+
+ SPUTF(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"");
put_xml(fp, name, strlen(name));
- fprintf(fp, " Atom Feed\" href=\"%satom.xml\" />\n", relpath);
- fputs("<link rel=\"alternate\" type=\"application/atom+xml\" title=\"", fp);
+ SPUTF(fp, " Atom Feed\" href=\"");
+ put_absolute_path(fp, "atom.xml");
+ SPUTF(fp, "\" />" "\n");
+
+ SPUTF(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"");
put_xml(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", fp);
+ SPUTF(fp, " Atom Feed (tags)\" href=\"");
+ put_absolute_path(fp, "tags.xml");
+ SPUTF(fp, "\" />" "\n");
- // TODO handle backlink using config.h
- fprintf(fp, "<p><a href=\"%s\">home</a></p>" "\n", "/files.html");
+ SPUTF(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"");
+ put_absolute_path(fp, "style.css");
+ SPUTF(fp, "\" />" "\n");
+ SPUTF(fp, "</head>" "\n" "<body>" "\n");
- fputs("<table><tr><td>", fp);
- fprintf(
- fp,
- "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>",
- relpath,
- relpath
- );
- fputs("</td><td><h1>", fp);
+ // TODO handle backlink using config.h
+ SPUTF(fp, "<p><a href=\"");
+ put_absolute_path(fp, "files.html");
+ SPUTF(fp, "\">home</a></p>" "\n");
+
+ SPUTF(fp, "<table><tr><td>");
+ fprintf(fp, "<a href=\"");
+ put_absolute_path(fp, ""); // TODO links to site root?
+ SPUTF(fp, "\"><img src=\"");
+ put_absolute_path(fp, "logo.png");
+ SPUTF(fp, "\" alt=\"\" width=\"32\" height=\"32\" /></a>");
+
+ SPUTF(fp, "</td><td><h1>");
put_xml(fp, stripped_name, strlen(stripped_name));
- fputs("</h1><span class=\"desc\">", fp);
+ SPUTF(fp, "</h1><span class=\"desc\">");
put_xml(fp, description, strlen(description));
- fputs("</span></td></tr>", fp);
+ SPUTF(fp, "</span></td></tr>");
if (cloneurl[0]) {
- fputs("<tr class=\"url\"><td></td><td>git clone <a href=\"", fp);
+ SPUTF(fp, "<tr class=\"url\"><td></td><td>git clone <a href=\"");
put_xml(fp, cloneurl, strlen(cloneurl)); /* not percent-encoded */
- fputs("\">", fp);
+ SPUTF(fp, "\">");
put_xml(fp, cloneurl, strlen(cloneurl));
- fputs("</a></td></tr>", fp);
+ SPUTF(fp, "</a></td></tr>");
}
- fputs("<tr><td></td><td>\n", fp);
+ SPUTF(fp, "<tr><td></td><td>" "\n");
- if (readme)
- fprintf(
- fp,
- "<a href=\"%sfile/%s.html\">README</a> | ",
- relpath,
- readme
- );
+ if (readme) {
+ SPUTF(fp, "<a href=\"");
+ put_absolute_path(fp, "files/");
+ fprintf(fp, "%s.html", readme);
+ SPUTF(fp, "\">README</a> | ");
+ }
- fprintf(fp, "<a href=\"%sfiles.html\">Files</a> | ", relpath);
- fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath);
- fprintf(fp, "<a href=\"%srefs.html\">Refs</a>", relpath);
-
- if (submodules)
- fprintf(
- fp,
- " | <a href=\"%sfile/%s.html\">Submodules</a>",
- relpath,
- submodules
- );
+ SPUTF(fp, "<a href=\"");
+ put_absolute_path(fp, "files.html");
+ SPUTF(fp, "\">Files</a> | ");
+
+ SPUTF(fp, "<a href=\"");
+ put_absolute_path(fp, "log.html");
+ SPUTF(fp, "\">Log</a> | ");
+
+ SPUTF(fp, "<a href=\"");
+ put_absolute_path(fp, "refs.html");
+ SPUTF(fp, "\">Refs</a>");
+
+ if (submodules) {
+ SPUTF(fp, " | <a href=\"");
+ put_absolute_path(fp, "files/");
+ fprintf(fp, "%s.html", submodules);
+ SPUTF(fp, "\">Submodules</a>");
+ }
- if (license)
- fprintf(
- fp,
- " | <a href=\"%sfile/%s.html\">LICENSE</a>",
- relpath,
- license
- );
+ if (license) {
+ SPUTF(fp, " | <a href=\"");
+ put_absolute_path(fp, "files/");
+ fprintf(fp, "%s.html", license);
+ SPUTF(fp, "\">LICENSE</a>");
+ }
- fputs("</td></tr></table>" "\n", fp);
- fputs("<hr/>" "\n", fp);
+ SPUTF(fp, "</td></tr></table>" "\n");
+ SPUTF(fp, "<hr/>" "\n");
- fputs("<div id=\"content\">" "\n", fp);
+ SPUTF(fp, "<div id=\"content\">" "\n");
}
void
put_footer(FILE *fp)
{
- fputs("</div>" "\n" "</body>" "\n" "</html>" "\n", fp);
+ SPUTF(fp, "</div>" "\n" "</body>" "\n" "</html>" "\n");
}
size_t
@@ -655,33 +720,37 @@ put_file_html(FILE *fp, const git_blob *blob)
void
print_commit(FILE *fp, struct CommitInfo *ci)
{
- fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n",
- relpath, ci->oid, ci->oid);
+ SPUTF(fp, "<b>commit</b> <a href=\"");
+ put_absolute_path(fp, "commit/");
+ fprintf(fp, "%s.html", ci->oid);
+ fprintf(fp, "\">%s</a>" "\n", ci->oid);
if (ci->parentoid[0])
- fprintf(fp, "<b>parent</b> <a href=\"%scommit/%s.html\">%s</a>\n",
- relpath, ci->parentoid, ci->parentoid);
+ SPUTF(fp, "<b>parent</b> <a href=\"");
+ put_absolute_path(fp, "commit/");
+ fprintf(fp, "%s.html", ci->parentoid);
+ fprintf(fp, "\">%s</a>" "\n", ci->parentoid);
if (ci->author) {
- fputs("<b>Author:</b> ", fp);
+ SPUTF(fp, "<b>Author:</b> ");
put_xml(fp, ci->author->name, strlen(ci->author->name));
- fputs(" <<a href=\"mailto:", fp);
+ SPUTF(fp, " <<a href=\"mailto:");
put_xml(fp, ci->author->email, strlen(ci->author->email)); /* not percent-encoded */
- fputs("\">", fp);
+ SPUTF(fp, "\">");
put_xml(fp, ci->author->email, strlen(ci->author->email));
- fputs("</a>>\n<b>Date:</b> ", fp);
+ SPUTF(fp, "</a>>\n<b>Date:</b> ");
printtime(fp, &(ci->author->when));
- putc('\n', fp);
+ CPUT(fp, '\n');
}
if (ci->msg) {
- putc('\n', fp);
+ CPUT(fp, '\n');
put_xml(fp, ci->msg, strlen(ci->msg));
- putc('\n', fp);
+ CPUT(fp, '\n');
}
}
void
-print_show_file(FILE *fp, struct CommitInfo *ci)
+put_show_file(FILE *fp, struct CommitInfo *ci)
{
const git_diff_delta *delta;
const git_diff_hunk *hunk;
@@ -771,19 +840,26 @@ print_show_file(FILE *fp, struct CommitInfo *ci)
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 id=\"h%zu\" href=\"%sfile/", i, relpath);
+
+ fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"", i);
+
+ put_absolute_path(fp, "files/");
put_percent_encoded(fp, delta->old_file.path, strlen(delta->old_file.path));
- fputs(".html\">", fp);
+ SPUTF(fp, ".html\">");
+
put_xml(fp, delta->old_file.path, strlen(delta->old_file.path));
- fprintf(fp, "</a> b/<a href=\"%sfile/", relpath);
+ SPUTF(fp, "</a> b/<a href=\"");
+
+ put_absolute_path(fp, "files/");
put_percent_encoded(fp, delta->new_file.path, strlen(delta->new_file.path));
- fprintf(fp, ".html\">");
+ SPUTF(fp, ".html\">");
+
put_xml(fp, delta->new_file.path, strlen(delta->new_file.path));
- fprintf(fp, "</a></b>\n");
+ SPUTF(fp, "</a></b>" "\n");
/* check binary data */
if (delta->flags & GIT_DIFF_FLAG_BINARY) {
- fputs("Binary files differ.\n", fp);
+ SPUTF(fp, "Binary files differ." "\n");
continue;
}
@@ -794,7 +870,7 @@ print_show_file(FILE *fp, struct CommitInfo *ci)
fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j);
put_xml(fp, hunk->header, hunk->header_len);
- fputs("</a>", fp);
+ SPUTF(fp, "</a>");
for (k = 0; ; k++) {
if (git_patch_get_line_in_hunk(&line, patch, j, k))
@@ -812,38 +888,41 @@ print_show_file(FILE *fp, struct CommitInfo *ci)
i, j, k, i, j, k
);
else
- putc(' ', fp);
+ CPUT(fp, ' ');
put_xml_line(fp, line->content, line->content_len);
- putc('\n', fp);
+ CPUT(fp, '\n');
if (line->old_lineno == -1 || line->new_lineno == -1)
- fputs("</a>", fp);
+ SPUTF(fp, "</a>");
}
}
}
}
void
-write_log_line(FILE *fp, struct CommitInfo *ci)
+put_log_line(FILE *fp, struct CommitInfo *ci)
{
fputs("<tr><td>", fp);
if (ci->author)
printtimeshort(fp, &(ci->author->when));
fputs("</td><td>", fp);
if (ci->summary) {
- fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid);
+ SPUTF(fp, "<a href=\"");
+ put_absolute_path(fp, "commit/");
+ fprintf(fp, "%s.html", ci->oid);
+ SPUTF(fp, "\">");
put_xml(fp, ci->summary, strlen(ci->summary));
- fputs("</a>", fp);
+ SPUTF(fp, "</a>");
}
- fputs("</td><td>", fp);
+ SPUTF(fp, "</td><td>");
if (ci->author)
put_xml(fp, ci->author->name, strlen(ci->author->name));
- fputs("</td><td class=\"num\" align=\"right\">", fp);
+ SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
fprintf(fp, "%zu", ci->file_count);
- fputs("</td><td class=\"num\" align=\"right\">", fp);
+ SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
fprintf(fp, "+%zu", ci->add_count);
- fputs("</td><td class=\"num\" align=\"right\">", fp);
+ SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
fprintf(fp, "-%zu", ci->del_count);
- fputs("</td></tr>\n", fp);
+ SPUTF(fp, "</td></tr>" "\n");
}
int
@@ -887,13 +966,13 @@ write_log(FILE *fp, const git_oid *oid)
goto err;
if (nlogcommits != 0) {
- write_log_line(fp, ci);
+ put_log_line(fp, ci);
if (nlogcommits > 0)
nlogcommits--;
}
if (cachefile)
- write_log_line(wcachefp, ci);
+ put_log_line(wcachefp, ci);
/* check if file exists if so skip it */
if (r) {
@@ -902,7 +981,7 @@ write_log(FILE *fp, const git_oid *oid)
put_header(fpfile, ci->summary);
fputs("<pre>", fpfile);
- print_show_file(fpfile, ci);
+ put_show_file(fpfile, ci);
fputs("</pre>\n", fpfile);
put_footer(fpfile);
@@ -992,7 +1071,7 @@ write_log_cached(FILE *fp, const git_oid *head)
}
void
-write_log_page(FILE *fp, const git_oid *head)
+put_log_html(FILE *fp, const git_oid *head)
{
relpath = "";
mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO);
@@ -1025,24 +1104,24 @@ print_commit_atom(FILE *fp, struct CommitInfo *ci, const char *tag)
fprintf(fp, "<id>%s</id>\n", ci->oid);
if (ci->author) {
- fputs("<published>", fp);
+ SPUTF(fp, "<published>");
printtimez(fp, &(ci->author->when));
- fputs("</published>\n", fp);
+ SPUTF(fp, "</published>\n");
}
if (ci->committer) {
- fputs("<updated>", fp);
+ SPUTF(fp, "<updated>");
printtimez(fp, &(ci->committer->when));
- fputs("</updated>\n", fp);
+ SPUTF(fp, "</updated>\n");
}
if (ci->summary) {
- fputs("<title>", fp);
+ SPUTF(fp, "<title>");
if (tag && tag[0]) {
- fputs("[", fp);
+ SPUTF(fp, "[");
put_xml(fp, tag, strlen(tag));
- fputs("] ", fp);
+ SPUTF(fp, "] ");
}
put_xml(fp, ci->summary, strlen(ci->summary));
- fputs("</title>\n", fp);
+ SPUTF(fp, "</title>\n");
}
fprintf(
fp,
@@ -1052,31 +1131,31 @@ print_commit_atom(FILE *fp, struct CommitInfo *ci, const char *tag)
);
if (ci->author) {
- fputs("<author>\n<name>", fp);
+ SPUTF(fp, "<author>\n<name>");
put_xml(fp, ci->author->name, strlen(ci->author->name));
- fputs("</name>\n<email>", fp);
+ SPUTF(fp, "</name>\n<email>");
put_xml(fp, ci->author->email, strlen(ci->author->email));
- fputs("</email>\n</author>\n", fp);
+ SPUTF(fp, "</email>\n</author>\n");
}
- fputs("<content>", fp);
+ SPUTF(fp, "<content>");
fprintf(fp, "commit %s\n", ci->oid);
if (ci->parentoid[0])
fprintf(fp, "parent %s\n", ci->parentoid);
if (ci->author) {
- fputs("Author: ", fp);
+ SPUTF(fp, "Author: ");
put_xml(fp, ci->author->name, strlen(ci->author->name));
- fputs(" <", fp);
+ SPUTF(fp, " <");
put_xml(fp, ci->author->email, strlen(ci->author->email));
- fputs(">\nDate: ", fp);
+ SPUTF(fp, ">\nDate: ");
printtime(fp, &(ci->author->when));
- putc('\n', fp);
+ CPUT(fp, '\n');
}
if (ci->msg) {
- putc('\n', fp);
+ CPUT(fp, '\n');
put_xml(fp, ci->msg, strlen(ci->msg));
}
- fputs("\n</content>\n</entry>\n", fp);
+ SPUTF(fp, "\n</content>\n</entry>\n");
}
int
@@ -1089,15 +1168,15 @@ write_atom(FILE *fp, int all)
git_oid id;
size_t i, m = 100; /* last 'm' commits */
- fputs(
+ SPUTF(
+ fp,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>",
- fp
+ "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>"
);
put_xml(fp, stripped_name, strlen(stripped_name));
- fputs(", branch HEAD</title>\n<subtitle>", fp);
+ SPUTF(fp, ", branch HEAD</title>\n<subtitle>");
put_xml(fp, description, strlen(description));
- fputs("</subtitle>\n", fp);
+ SPUTF(fp, "</subtitle>\n");
/* all commits or only tags? */
if (all) {
@@ -1131,13 +1210,13 @@ write_atom(FILE *fp, int all)
}
void
-write_binary_file(const char *filepath, git_blob *blob) {
+write_binary_file(const char *filename, git_blob *blob) {
char dest[PATH_MAX] = "";
const char *raw = git_blob_rawcontent(blob);
int size = git_blob_rawsize(blob);
/* set dest */
- join_path(dest, PATH_MAX, "file", filepath);
+ join_path(dest, PATH_MAX, "files", filename);
/* write the file! */
FILE *fp = efopen(dest, "w");
@@ -1146,43 +1225,61 @@ write_binary_file(const char *filepath, git_blob *blob) {
fclose(fp);
}
+void
+put_size(FILE *fp, size_t size)
+{
+ int order = 0;
+
+ if (size == 0) {
+ fprintf(fp, " - ");
+ return;
+ }
+
+ while (size > 1000) {
+ order++;
+ size /= 1000;
+ }
+
+ fprintf(fp, "%3zu%c", size, orders[order]);
+}
+
size_t
-write_blob(
+put_blob(
git_object *obj,
- const char *fpath, /* absolute path of page */
- const char *entrypath, /* directory of file */
- const char *filename, /* name of file */
- size_t filesize
+ const char *filename,
+ const char *entrypath,
+ const char *entryname,
+ size_t bytes
) {
- char tmp[PATH_MAX] = "", *d;
+ char tmp[PATH_MAX] = "";
+ char *d;
const char *p;
- size_t lc = 0;
+ size_t lines = 0;
FILE *fp;
int is_binary;
- 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;
+ if (strlcpy(tmp, filename, sizeof(tmp)) >= sizeof(tmp))
+ errx(1, "path truncated: '%s'", filename);
- for (p = fpath, tmp[0] = '\0'; *p; p++) {
+ // wtf does this do....
+ for (p = filename, tmp[0] = '\0'; *p; p++) {
if (*p == '/' && strlcat(tmp, "../", sizeof(tmp)) >= sizeof(tmp))
errx(1, "path truncated: '../%s'", tmp);
}
relpath = tmp;
is_binary = git_blob_is_binary((git_blob *)obj);
- const char *file_url = is_binary ? filename : "";
+ const char *file_url = is_binary ? entryname : "";
- fp = efopen(fpath, "w");
- put_header(fp, filename);
+ fp = efopen(filename, "w");
+ put_header(fp, entryname);
fprintf(fp, "<p>");
- put_xml(fp, filename, strlen(filename));
- fprintf(fp, " (%zuB)</p>", filesize); // TODO also show line number (if possible)
+ put_xml(fp, entrypath, strlen(entrypath));
+ CPUT(fp, ' ');
+ put_size(fp, 0);
+ CPUT(fp, ' ');
+ put_size(fp, bytes);
+ SPUTF(fp, "B</p>");
if (is_binary) {
fprintf(
@@ -1196,20 +1293,20 @@ write_blob(
);
write_binary_file(entrypath, (git_blob *) obj);
} else {
- lc = put_file_html(fp, (git_blob *) obj);
+ lines = put_file_html(fp, (git_blob *) obj);
}
put_footer(fp);
- check_file_error(fp, fpath, 'w');
+ check_file_error(fp, filename, 'w');
fclose(fp);
relpath = "";
- return lc;
+ return lines;
}
char
-filemode_type(git_filemode_t m)
+get_filemode_type(git_filemode_t m)
{
if (S_ISREG(m)) return '-';
if (S_ISBLK(m)) return 'b';
@@ -1222,133 +1319,303 @@ filemode_type(git_filemode_t m)
}
const char *
-filemode(git_filemode_t m)
+get_filemode(const git_tree_entry *entry)
{
- static char mode[11];
-
- memset(mode, '-', sizeof(mode) - 1);
- mode[0] = filemode_type(m);
- if (m & S_IRUSR) mode[1] = 'r';
- if (m & S_IWUSR) mode[2] = 'w';
- if (m & S_IXUSR) mode[3] = 'x';
- if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
- if (m & S_IRGRP) mode[4] = 'r';
- if (m & S_IWGRP) mode[5] = 'w';
- if (m & S_IXGRP) mode[6] = 'x';
- if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
- if (m & S_IROTH) mode[7] = 'r';
- if (m & S_IWOTH) mode[8] = 'w';
- if (m & S_IXOTH) mode[9] = 'x';
- if (m & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
-
- mode[10] = '\0';
-
- return mode;
+ git_filemode_t mode;
+ static char ret[11];
+
+ /* used for backlink: .. */
+ if (entry == NULL)
+ return "d---------";
+
+ mode = git_tree_entry_filemode(entry);
+
+ memset(ret, '-', sizeof(ret) - 1);
+ ret[0] = get_filemode_type(mode);
+ if (mode & S_IRUSR) ret[1] = 'r';
+ if (mode & S_IWUSR) ret[2] = 'w';
+ if (mode & S_IXUSR) ret[3] = 'x';
+ if (mode & S_ISUID) ret[3] = (ret[3] == 'x') ? 's' : 'S';
+ if (mode & S_IRGRP) ret[4] = 'r';
+ if (mode & S_IWGRP) ret[5] = 'w';
+ if (mode & S_IXGRP) ret[6] = 'x';
+ if (mode & S_ISGID) ret[6] = (ret[6] == 'x') ? 's' : 'S';
+ if (mode & S_IROTH) ret[7] = 'r';
+ if (mode & S_IWOTH) ret[8] = 'w';
+ if (mode & S_IXOTH) ret[9] = 'x';
+ if (mode & S_ISVTX) ret[9] = (ret[9] == 'x') ? 't' : 'T';
+ ret[10] = '\0';
+
+ return ret;
}
-int
-write_file_list(FILE *fp, git_tree *tree, const char *path)
+struct Weight
+put_filetree_file_line(
+ FILE *fp,
+ git_object *obj,
+ const git_tree_entry *entry,
+ char *filename,
+ char *entrypath,
+ const char *entryname
+) {
+ size_t bytes = git_blob_rawsize((git_blob *)obj);
+ size_t lines = put_blob(obj, filename, entrypath, entryname, bytes);
+ struct Weight ret = { bytes, lines };
+
+ SPUTF(fp, "<tr><td>");
+ SPUTF(fp, get_filemode(entry));
+ SPUTF(fp, "</td><td><a href=\"");
+ put_absolute_path(fp, "");
+ put_percent_encoded(fp, filename, strlen(filename));
+ SPUTF(fp, "\">");
+ put_xml(fp, entryname, strlen(entryname));
+ SPUTF(fp, "</a></td><td class=\"num\" align=\"right\">");
+ put_size(fp, lines);
+ SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
+ put_size(fp, bytes);
+ SPUTF(fp, "</td></tr>\n");
+ return ret;
+}
+
+void
+put_filetree_dir_line(
+ FILE *fp,
+ const git_tree_entry *entry,
+ char *destination,
+ const char *title,
+ size_t lines,
+ size_t bytes
+) {
+ SPUTF(fp, "<tr><td>");
+ SPUTF(fp, get_filemode(entry));
+ SPUTF(fp, "</td><td><a href=\"");
+ put_absolute_path(fp, "");
+ put_percent_encoded(fp, destination, strlen(destination));
+ SPUTF(fp, "\">");
+ put_xml(fp, title, strlen(title));
+ SPUTF(fp, "</a></td><td class=\"num\" align=\"right\">");
+ put_size(fp, lines);
+ SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
+ put_size(fp, bytes);
+ SPUTF(fp, "</td></tr>\n");
+}
+
+void put_filetree_backlink_line(FILE *fp, const char *path) {
+ char previous_path[PATH_MAX];
+ char previous_filename[PATH_MAX];
+ int i, r, last_slash = 0;
+
+ /* figure out previous directory */
+ // TODO how to do this more cleanly...
+ for (i = 0; path[i] != '\0'; i++) {
+ previous_path[i] = path[i];
+ if (path[i] == '/') last_slash = i;
+ }
+ previous_path[last_slash] = '\0';
+
+ if (last_slash == 0) {
+ put_filetree_dir_line(fp, NULL, "files.html", "..", 0, 0);
+ } else {
+ r = snprintf(previous_filename, sizeof(previous_filename), "files/%s.html", previous_path);
+ if (r < 0 || (size_t) r >= sizeof(previous_filename))
+ errx(1, "path truncated: 'files/%s.html'", previous_path);
+ put_filetree_dir_line(fp, NULL, previous_filename, "..", 0, 0);
+ }
+}
+
+/* need this here to allow recursion */
+struct Weight write_filetree(
+ const char *filename,
+ const char *path,
+ git_tree *tree
+);
+
+struct Weight
+put_entry_obj(
+ FILE *fp,
+ git_object *obj,
+ const git_tree_entry *entry,
+ char *entrypath,
+ const char *entryname
+) {
+ struct Weight ret;
+ git_object_t obj_type = git_object_type(obj);
+ char filename[PATH_MAX];
+ int r;
+
+ /* filename = f("files/%s.html", entrypath) */
+ r = snprintf(filename, sizeof(filename), "files/%s.html", entrypath);
+ if (r < 0 || (size_t) r >= sizeof(filename))
+ errx(1, "path truncated: 'files/%s.html'", entrypath);
+
+ switch (obj_type) {
+ case GIT_OBJ_TREE:
+ /* NOTE: recurses */
+ ret = write_filetree(filename, entrypath, (git_tree *) obj);
+ put_filetree_dir_line(
+ fp,
+ entry,
+ filename,
+ entryname,
+ ret.lines,
+ ret.bytes
+ );
+ break;
+ case GIT_OBJ_BLOB:
+ ret = put_filetree_file_line(
+ fp,
+ obj,
+ entry,
+ filename,
+ entrypath,
+ entryname
+ );
+ break;
+ }
+
+ return ret;
+}
+
+void
+put_submodule_obj(
+ FILE *fp,
+ const git_tree_entry *entry,
+ char *entrypath
+) {
+ char oid[8];
+
+ SPUTF(fp, "<tr><td>m---------</td><td><a href=\"");
+ put_absolute_path(fp, "files/.gitmodules.html");
+ SPUTF(fp, "\">");
+ put_xml(fp, entrypath, strlen(entrypath));
+ SPUTF(fp, "</a> @ ");
+ git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry));
+ put_xml(fp, oid, strlen(oid));
+ SPUTF(fp, "</td><td class=\"num\" align=\"right\"></td></tr>" "\n");
+}
+
+void
+put_entry()
{
- const git_tree_entry *entry = NULL;
- git_object *obj = NULL;
- const char *entryname;
- char filepath[PATH_MAX], entrypath[PATH_MAX], oid[8];
- size_t count, i, lc, filesize;
- int r, ret;
+
+}
+
+struct Weight
+put_file_list(FILE *fp, git_tree *tree, const char *path)
+{
+ struct Weight ret = {};
+ size_t count;
+ size_t i;
+
+ /* add line for ".." back-link */
+ if (path[0] != '\0') put_filetree_backlink_line(fp, path);
count = git_tree_entrycount(tree);
for (i = 0; i < count; i++) {
+ const git_tree_entry *entry = NULL;
+ const char *entryname;
+ char entrypath[PATH_MAX];
+
if (
!(entry = git_tree_entry_byindex(tree, i)) ||
!(entryname = git_tree_entry_name(entry))
)
- return -1;
- join_path(entrypath, sizeof(entrypath), path, entryname);
+ return ret;
- r = snprintf(filepath, sizeof(filepath), "file/%s.html", entrypath);
- if (r < 0 || (size_t)r >= sizeof(filepath))
- errx(1, "path truncated: 'file/%s.html'", entrypath);
+ /* entrypath = path + entryname */
+ join_path(entrypath, sizeof(entrypath), path, entryname);
- if (!git_tree_entry_to_object(&obj, repo, entry)) {
- switch (git_object_type(obj)) {
- case GIT_OBJ_BLOB:
- break;
- case GIT_OBJ_TREE:
- // TODO create page for each dir
- // instead of dumping all files into "files.html"
- // or, is that a good idea? hm...
- /* NOTE: recurses */
- ret = write_file_list(fp, (git_tree *)obj, entrypath);
- git_object_free(obj);
- if (ret)
- return ret;
- continue;
- default:
+ {
+ git_object *obj = NULL;
+ if (!git_tree_entry_to_object(&obj, repo, entry)) {
+ struct Weight weight = put_entry_obj(
+ fp,
+ obj,
+ entry,
+ entrypath,
+ entryname
+ );
+ ret.bytes += weight.bytes;
+ ret.lines += weight.lines;
git_object_free(obj);
continue;
}
+ }
- filesize = git_blob_rawsize((git_blob *)obj);
- lc = write_blob(obj, filepath, entrypath, entryname, filesize);
-
- fputs("<tr><td>", fp);
- fputs(filemode(git_tree_entry_filemode(entry)), fp);
- fprintf(fp, "</td><td><a href=\"%s", relpath);
- put_percent_encoded(fp, filepath, strlen(filepath));
- fputs("\">", fp);
- put_xml(fp, entrypath, strlen(entrypath));
- fputs("</a></td><td class=\"num\" align=\"right\">", fp);
- if (lc > 0) fprintf(fp, "%zuL", lc);
- else fprintf(fp, "%zuB", filesize);
- fputs("</td></tr>\n", fp);
- git_object_free(obj);
- } else if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) {
- /* commit object in tree is a submodule */
- fprintf(
- fp,
- "<tr><td>m---------</td><td><a href=\"%sfile/.gitmodules.html\">",
- relpath
- );
- put_xml(fp, entrypath, strlen(entrypath));
- fputs("</a> @ ", fp);
- git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry));
- put_xml(fp, oid, strlen(oid));
- fputs("</td><td class=\"num\" align=\"right\"></td></tr>\n", fp);
+ if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) {
+ put_submodule_obj(fp, entry, entrypath);
+ // TODO ensure that this works with the size stuff correctly!
+ continue;
}
}
- return 0;
+ return ret;
}
-int
-write_files(FILE *fp, const git_oid *id)
+void
+put_filetree_dir_title(FILE *fp, const char *path)
{
- git_tree *tree = NULL;
- git_commit *commit = NULL;
- int ret = -1;
+ put_xml(fp, path, strlen(path));
+}
- fputs(
- "<table id=\"files\"><thead>\n<tr>"
+struct Weight
+write_filetree(const char *filename, const char *path, git_tree *tree)
+{
+ FILE *fp;
+ struct Weight ret;
+
+ fp = efopen(filename, "w");
+ put_header(fp, "Files");
+
+ fprintf(fp, "<p>");
+ put_filetree_dir_title(fp, path);
+ SPUTF(fp, "</p>");
+
+ SPUTF(
+ fp,
+ "<table id=\"files\"><thead>" "\n" "<tr>"
"<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
+ "<td class=\"num\" align=\"right\"><b>L</b></td>"
+ "<td class=\"num\" align=\"right\"><b>B</b></td>"
+ "</tr>" "\n" "</thead><tbody>" "\n"
);
- if (
- !git_commit_lookup(&commit, repo, id) &&
- !git_commit_tree(&tree, commit)
- )
- ret = write_file_list(fp, tree, "");
+ if (tree != NULL)
+ ret = put_file_list(fp, tree, path);
- fputs("</tbody></table>", fp);
+ SPUTF(fp, "</tbody></table>");
- git_commit_free(commit);
- git_tree_free(tree);
+ SPUTF(fp, "<p>Totals: ");
+ put_size(fp, ret.lines);
+ SPUTF(fp, "L ");
+ put_size(fp, ret.bytes);
+ SPUTF(fp, "B</p>");
+
+ put_footer(fp);
+ check_file_error(fp, filename, 'w');
+ fclose(fp);
return ret;
}
+void
+walk_git_tree(const git_oid *id)
+{
+ git_tree *tree = NULL;
+ git_commit *commit = NULL;
+ int has_files;
+
+ if (!id) return;
+
+ has_files = !git_commit_lookup(&commit, repo, id) &&
+ !git_commit_tree(&tree, commit);
+
+ write_filetree("files.html", "", has_files ? tree : NULL);
+
+ git_commit_free(commit);
+ git_tree_free(tree);
+}
+
int
write_refs(FILE *fp)
{
@@ -1410,6 +1677,48 @@ write_refs(FILE *fp)
}
void
+write_log_page(const git_oid *head)
+{
+ const char *filename = "log.html";
+ FILE *fp = efopen(filename, "w");
+ put_log_html(fp, head);
+ check_file_error(fp, filename, 'w');
+ fclose(fp);
+}
+
+void
+write_refs_page()
+{
+ const char *filename = "refs.html";
+ FILE *fp = efopen(filename, "w");
+ put_header(fp, "Refs");
+ write_refs(fp);
+ put_footer(fp);
+ check_file_error(fp, filename, 'w');
+ fclose(fp);
+}
+
+void
+write_feed()
+{
+ const char *filename = "atom.xml";
+ FILE *fp = efopen(filename, "w");
+ write_atom(fp, 1);
+ check_file_error(fp, filename, 'w');
+ fclose(fp);
+}
+
+void
+write_releases_feed()
+{
+ const char *filename = "tags.xml";
+ FILE *fp = efopen(filename, "w");
+ write_atom(fp, 0);
+ check_file_error(fp, filename, 'w');
+ fclose(fp);
+}
+
+void
usage(char *argv0)
{
fprintf(
@@ -1424,10 +1733,10 @@ usage(char *argv0)
int
main(int argc, char *argv[])
{
- git_object *obj = NULL;
const git_oid *head = NULL;
FILE *fp, *fpread;
char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p;
+ git_object *obj = NULL;
int i;
/* parse args */
@@ -1544,25 +1853,8 @@ main(int argc, char *argv[])
cloneurl[strcspn(cloneurl, "\n")] = '\0';
}
- /* check LICENSE */
- for (i = 0; i < LENGTH(license_files) && !license; i++) {
- if (
- !git_revparse_single(&obj, repo, license_files[i]) &&
- git_object_type(obj) == GIT_OBJ_BLOB
- )
- license = license_files[i] + strlen("HEAD:");
- git_object_free(obj);
- }
-
- /* check README */
- for (i = 0; i < LENGTH(readme_files) && !readme; i++) {
- if (
- !git_revparse_single(&obj, repo, readme_files[i]) &&
- git_object_type(obj) == GIT_OBJ_BLOB
- )
- readme = readme_files[i] + strlen("HEAD:");
- git_object_free(obj);
- }
+ find_license();
+ find_readme();
if (
!git_revparse_single(&obj, repo, "HEAD:.gitmodules") &&
@@ -1571,39 +1863,12 @@ main(int argc, char *argv[])
submodules = ".gitmodules";
git_object_free(obj);
- /* log for HEAD */
- fp = efopen("log.html", "w");
- write_log_page(fp, head);
- check_file_error(fp, "log.html", 'w');
- fclose(fp);
-
- /* files for HEAD */
- fp = efopen("files.html", "w");
- put_header(fp, "Files");
- if (head) write_files(fp, head);
- put_footer(fp);
- check_file_error(fp, "files.html", 'w');
- fclose(fp);
-
- /* summary page with branches and tags */
- fp = efopen("refs.html", "w");
- put_header(fp, "Refs");
- write_refs(fp);
- put_footer(fp);
- check_file_error(fp, "refs.html", 'w');
- fclose(fp);
-
- /* Atom feed */
- fp = efopen("atom.xml", "w");
- write_atom(fp, 1);
- check_file_error(fp, "atom.xml", 'w');
- fclose(fp);
-
- /* Atom feed for tags / releases */
- fp = efopen("tags.xml", "w");
- write_atom(fp, 0);
- check_file_error(fp, "tags.xml", 'w');
- fclose(fp);
+ /* write pages! */
+ write_log_page(head);
+ walk_git_tree(head);
+ write_refs_page();
+ write_feed();
+ write_releases_feed();
/* cleanup */
git_repository_free(repo);