git home / emma home
logo

sgw

Static git web. Emma's fork of stagit.
git clone https://git.y1.nz/archives/sgw.tar.gz
README | Files | Log | Refs | LICENSE

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:
MTODO20+++++++++++++-------
Mstagit.c861++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
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(" &lt;<a href=\"mailto:", fp); + SPUTF(fp, " &lt;<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>&gt;\n<b>Date:</b> ", fp); + SPUTF(fp, "</a>&gt;\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(" &lt;", fp); + SPUTF(fp, " &lt;"); put_xml(fp, ci->author->email, strlen(ci->author->email)); - fputs("&gt;\nDate: ", fp); + SPUTF(fp, "&gt;\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);

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.