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 022c85fdcf44b9b8723ce6983c62d585e2563c9d
parent 4d3348560faf732f9304a05a269b4d33df930988
Author: Emma Weaver <emma@waeaves.com>
Date:   Fri, 15 May 2026 23:59:08 -0400

initial refactoring, visual improvements

Diffstat:
A.gitignore6++++++
ATODO33+++++++++++++++++++++++++++++++++
Dfavicon.png0
Dlogo.png0
Mstagit.c906+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mstyle.css103+++++++++++++++++++++++++++++++++----------------------------------------------
6 files changed, 603 insertions(+), 445 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,6 @@ +*.o +**.swp +stagit +stagit-index +test + diff --git a/TODO b/TODO @@ -0,0 +1,33 @@ +STUFF I WANT TO CHANGE ABOUT THIS + +[ ] 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) + +[ ] add warnings on missing decorations + - flag that skips repos with incomplete metadata + - (possible mechanism to hide private repos) + +[ ] create config.h file + - reused format strings (such as header) + +[ ] handle projects with large numbers of files better + - show "{total LoC}L + {total binary B}B" for each directory + - (VERY IMPORTANT to still punish large projects) + + - NEW FEATURES +[ ] add lines of code totals on files page +[ ] add lines of code to top of each file's page +[ ] allow download of arbitrary binary files +[ ] display images on their file page (png, jpg, webp, which others?) +[ ] / add support for projects with folders in them +[ ] remove leading dot from links, so they can be served more easily + - END NEW FEATURES + +[ ] Redo caching, to never repeat work + - but it needs to also not increase the complexity. + - it's ok to duplicate storage + - perhaps: save every reusable intermediate into a file. + then, check for changes and reuse if valid. diff --git a/favicon.png b/favicon.png Binary files differ. diff --git a/logo.png b/logo.png Binary files differ. diff --git a/stagit.c b/stagit.c @@ -16,16 +16,16 @@ #include "compat.h" -#define LEN(s) (sizeof(s)/sizeof(*s)) +#define LEN(s) (sizeof(s)/sizeof(*s)) -struct deltainfo { +struct DeltaInfo { git_patch *patch; - size_t addcount; - size_t delcount; + size_t add_count; + size_t del_count; }; -struct commitinfo { +struct CommitInfo { const git_oid *id; char oid[GIT_OID_HEXSZ + 1]; @@ -33,47 +33,51 @@ struct commitinfo { const git_signature *author; const git_signature *committer; - const char *summary; - const char *msg; + const char *summary; + const char *msg; - git_diff *diff; + git_diff *diff; git_commit *commit; git_commit *parent; - git_tree *commit_tree; - git_tree *parent_tree; + git_tree *commit_tree; + git_tree *parent_tree; - size_t addcount; - size_t delcount; - size_t filecount; + size_t add_count; + size_t del_count; + size_t file_count; - struct deltainfo **deltas; + struct DeltaInfo **deltas; size_t ndeltas; }; /* reference and associated data for sorting */ -struct referenceinfo { +struct ReferenceInfo { struct git_reference *ref; - struct commitinfo *ci; + struct CommitInfo *ci; }; static git_repository *repo; +/* flags + arguments */ static const char *baseurl = ""; /* base URL to make absolute RSS/Atom URI */ static const char *relpath = ""; static const char *repodir; static char *name = ""; -static char *strippedname = ""; +static char *stripped_name = ""; static char description[255]; static char cloneurl[1024]; static char *submodules; -static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:LICENSE.txt", "HEAD:COPYING" }; +static char *license_files[] = { + "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:LICENSE.txt", "HEAD:COPYING" +}; static char *license; -static char *readmefiles[] = { "HEAD:README", "HEAD:README.md" }; +static char *readme_files[] = { "HEAD:README", "HEAD:README.md" }; 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; @@ -81,7 +85,7 @@ static const char *cachefile; /* Handle read or write errors for a FILE * stream */ void -checkfileerror(FILE *fp, const char *name, int mode) +check_file_error(FILE *fp, const char *name, int mode) { if (mode == 'r' && ferror(fp)) errx(1, "read error: %s", name); @@ -90,31 +94,38 @@ checkfileerror(FILE *fp, const char *name, int mode) } void -joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) +join_path(char *buf, size_t bufsiz, const char *path, const char *path2) { int r; + char * separator = ""; + if(path[0] && path[strlen(path) - 1] != '/') + separator = "/"; - r = snprintf(buf, bufsiz, "%s%s%s", - path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); + r = snprintf(buf, bufsiz, "%s%s%s", path, separator, path2); if (r < 0 || (size_t)r >= bufsiz) - errx(1, "path truncated: '%s%s%s'", - path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); + errx( + 1, + "path truncated: '%s%s%s'", + path, + separator, + path2 + ); } void -deltainfo_free(struct deltainfo *di) +delta_info_free(struct DeltaInfo *di) { - if (!di) - return; + if (!di) return; + git_patch_free(di->patch); memset(di, 0, sizeof(*di)); free(di); } int -commitinfo_getstats(struct commitinfo *ci) +commit_info_get_stats(struct CommitInfo *ci) { - struct deltainfo *di; + struct DeltaInfo *di; git_diff_options opts; git_diff_find_options fopts; const git_diff_delta *delta; @@ -135,28 +146,29 @@ commitinfo_getstats(struct commitinfo *ci) git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION); opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH | - GIT_DIFF_IGNORE_SUBMODULES | - GIT_DIFF_INCLUDE_TYPECHANGE; + GIT_DIFF_IGNORE_SUBMODULES | + GIT_DIFF_INCLUDE_TYPECHANGE; if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts)) goto err; if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION)) goto err; /* find renames and copies, exact matches (no heuristic) for renames. */ - fopts.flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES | - GIT_DIFF_FIND_EXACT_MATCH_ONLY; + fopts.flags |= GIT_DIFF_FIND_RENAMES | + GIT_DIFF_FIND_COPIES | + GIT_DIFF_FIND_EXACT_MATCH_ONLY; if (git_diff_find_similar(ci->diff, &fopts)) goto err; ndeltas = git_diff_num_deltas(ci->diff); - if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *)))) + if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct DeltaInfo *)))) err(1, "calloc"); for (i = 0; i < ndeltas; i++) { if (git_patch_from_diff(&patch, ci->diff, i)) goto err; - if (!(di = calloc(1, sizeof(struct deltainfo)))) + if (!(di = calloc(1, sizeof(struct DeltaInfo)))) err(1, "calloc"); di->patch = patch; ci->deltas[i] = di; @@ -175,17 +187,17 @@ commitinfo_getstats(struct commitinfo *ci) if (git_patch_get_line_in_hunk(&line, patch, j, k)) break; if (line->old_lineno == -1) { - di->addcount++; - ci->addcount++; + di->add_count++; + ci->add_count++; } else if (line->new_lineno == -1) { - di->delcount++; - ci->delcount++; + di->del_count++; + ci->del_count++; } } } } ci->ndeltas = i; - ci->filecount = i; + ci->file_count = i; return 0; @@ -201,19 +213,19 @@ err: if (ci->deltas) for (i = 0; i < ci->ndeltas; i++) - deltainfo_free(ci->deltas[i]); + delta_info_free(ci->deltas[i]); free(ci->deltas); ci->deltas = NULL; ci->ndeltas = 0; - ci->addcount = 0; - ci->delcount = 0; - ci->filecount = 0; + ci->add_count = 0; + ci->del_count = 0; + ci->file_count = 0; return -1; } void -commitinfo_free(struct commitinfo *ci) +commit_info_free(struct CommitInfo *ci) { size_t i; @@ -221,7 +233,7 @@ commitinfo_free(struct commitinfo *ci) return; if (ci->deltas) for (i = 0; i < ci->ndeltas; i++) - deltainfo_free(ci->deltas[i]); + delta_info_free(ci->deltas[i]); free(ci->deltas); git_diff_free(ci->diff); @@ -233,12 +245,12 @@ commitinfo_free(struct commitinfo *ci) free(ci); } -struct commitinfo * -commitinfo_getbyoid(const git_oid *id) +struct CommitInfo * +commit_info_get_by_oid(const git_oid *id) { - struct commitinfo *ci; + struct CommitInfo *ci; - if (!(ci = calloc(1, sizeof(struct commitinfo)))) + if (!(ci = calloc(1, sizeof(struct CommitInfo)))) err(1, "calloc"); if (git_commit_lookup(&(ci->commit), repo, id)) @@ -256,7 +268,7 @@ commitinfo_getbyoid(const git_oid *id) return ci; err: - commitinfo_free(ci); + commit_info_free(ci); return NULL; } @@ -264,7 +276,7 @@ err: int refs_cmp(const void *v1, const void *v2) { - const struct referenceinfo *r1 = v1, *r2 = v2; + const struct ReferenceInfo *r1 = v1, *r2 = v2; time_t t1, t2; int r; @@ -276,15 +288,17 @@ refs_cmp(const void *v1, const void *v2) if ((r = t1 > t2 ? -1 : (t1 == t2 ? 0 : 1))) return r; - return strcmp(git_reference_shorthand(r1->ref), - git_reference_shorthand(r2->ref)); + return strcmp( + git_reference_shorthand(r1->ref), + git_reference_shorthand(r2->ref) + ); } int -getrefs(struct referenceinfo **pris, size_t *prefcount) +get_refs(struct ReferenceInfo **pris, size_t *prefcount) { - struct referenceinfo *ris = NULL; - struct commitinfo *ci = NULL; + struct ReferenceInfo *ris = NULL; + struct CommitInfo *ci = NULL; git_reference_iterator *it = NULL; const git_oid *id = NULL; git_object *obj = NULL; @@ -316,12 +330,14 @@ getrefs(struct referenceinfo **pris, size_t *prefcount) default: continue; } - if (!git_reference_target(r) || - 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; - if (!(ci = commitinfo_getbyoid(id))) + if (!(ci = commit_info_get_by_oid(id))) break; if (!(ris = reallocarray(ris, refcount + 1, sizeof(*ris)))) @@ -348,9 +364,9 @@ getrefs(struct referenceinfo **pris, size_t *prefcount) err: git_object_free(obj); git_reference_free(dref); - commitinfo_free(ci); + commit_info_free(ci); for (i = 0; i < refcount; i++) { - commitinfo_free(ris[i].ci); + commit_info_free(ris[i].ci); git_reference_free(ris[i].ref); } free(ris); @@ -371,7 +387,7 @@ efopen(const char *filename, const char *flags) /* Percent-encode, see RFC3986 section 2.1. */ void -percentencode(FILE *fp, const char *s, size_t len) +put_percent_encoded(FILE *fp, const char *s, size_t len) { static char tab[] = "0123456789ABCDEF"; unsigned char uc; @@ -380,8 +396,13 @@ percentencode(FILE *fp, const char *s, size_t len) 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 == ']') { + if ( + uc < ',' || + uc >= 127 || + (uc >= ':' && uc <= '@') || + uc == '[' || + uc == ']' + ) { putc('%', fp); putc(tab[(uc >> 4) & 0x0f], fp); putc(tab[uc & 0x0f], fp); @@ -393,39 +414,36 @@ percentencode(FILE *fp, const char *s, size_t len) /* Escape characters below as HTML 2.0 / XML 1.0. */ void -xmlencode(FILE *fp, const char *s, size_t len) +put_xml_char(FILE *fp, const char *s) { - size_t i; - - for (i = 0; *s && i < len; s++, i++) { - switch(*s) { - case '<': fputs("&lt;", fp); break; - case '>': fputs("&gt;", fp); break; - case '\'': fputs("&#39;", fp); break; - case '&': fputs("&amp;", fp); break; - case '"': fputs("&quot;", fp); break; - default: putc(*s, fp); - } + switch(*s) { + case '<': fputs("&lt;", fp); break; + case '>': fputs("&gt;", fp); break; + case '\'': fputs("&#39;", fp); break; + case '&': fputs("&amp;", fp); break; + case '"': fputs("&quot;", fp); break; + default: putc(*s, fp); } + } -/* Escape characters below as HTML 2.0 / XML 1.0, ignore printing '\r', '\n' */ void -xmlencodeline(FILE *fp, const char *s, size_t len) +put_xml(FILE *fp, const char *s, size_t len) { - size_t i; + size_t i = 0; + for (; *s && i < len; s++, i++) + put_xml_char(fp, s); +} - for (i = 0; *s && i < len; s++, i++) { - switch(*s) { - case '<': fputs("&lt;", fp); break; - case '>': fputs("&gt;", fp); break; - case '\'': fputs("&#39;", fp); break; - case '&': fputs("&amp;", fp); break; - case '"': fputs("&quot;", fp); break; - case '\r': break; /* ignore CR */ - case '\n': break; /* ignore LF */ - default: putc(*s, fp); - } +/* terminate on, but don't print '\r' or '\n' */ +void +put_xml_line(FILE *fp, const char *s, size_t len) +{ + size_t i = 0; + for (; *s && i < len; s++, i++) { + if (*s == '\r' || *s == '\n') + break; + put_xml_char(fp, s); } } @@ -474,12 +492,22 @@ printtime(FILE *fp, const git_time *intime) if (!(intm = gmtime(&t))) return; strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm); - 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); + 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 @@ -497,74 +525,107 @@ printtimeshort(FILE *fp, const git_time *intime) } void -writeheader(FILE *fp, const char *title) +write_header(FILE *fp, const char *title) { - fputs("<!DOCTYPE html>\n" - "<html>\n<head>\n" + fputs( + "<!DOCTYPE html>\n" + "<html>" "\n" + "<head>" "\n" "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n" - "<title>", fp); - xmlencode(fp, title, strlen(title)); - if (title[0] && strippedname[0]) + "<title>", + fp + ); + put_xml(fp, title, strlen(title)); + if (title[0] && stripped_name[0]) fputs(" - ", fp); - xmlencode(fp, strippedname, strlen(strippedname)); + put_xml(fp, stripped_name, strlen(stripped_name)); if (description[0]) fputs(" - ", fp); - xmlencode(fp, description, strlen(description)); + 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); - xmlencode(fp, name, strlen(name)); + 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); - xmlencode(fp, name, strlen(name)); + 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<table><tr><td>", fp); - fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>", - relpath, relpath); + fputs("</head>" "\n" "<body>" "\n", fp); + + // TODO handle backlink using config.h + fprintf(fp, "<p><a href=\"%s\">home</a></p>" "\n", "/files.html"); + + 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); - xmlencode(fp, strippedname, strlen(strippedname)); + put_xml(fp, stripped_name, strlen(stripped_name)); fputs("</h1><span class=\"desc\">", fp); - xmlencode(fp, description, strlen(description)); + put_xml(fp, description, strlen(description)); 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)); /* not percent-encoded */ + put_xml(fp, cloneurl, strlen(cloneurl)); /* not percent-encoded */ fputs("\">", fp); - xmlencode(fp, cloneurl, strlen(cloneurl)); + put_xml(fp, cloneurl, strlen(cloneurl)); fputs("</a></td></tr>", fp); } fputs("<tr><td></td><td>\n", fp); - fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath); + + if (readme) + fprintf( + fp, + "<a href=\"%sfile/%s.html\">README</a> | ", + relpath, + readme + ); + 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); - if (readme) - fprintf(fp, " | <a href=\"%sfile/%s.html\">README</a>", - relpath, readme); + fprintf( + fp, + " | <a href=\"%sfile/%s.html\">Submodules</a>", + relpath, + submodules + ); + if (license) - fprintf(fp, " | <a href=\"%sfile/%s.html\">LICENSE</a>", - relpath, license); - fputs("</td></tr></table>\n<hr/>\n<div id=\"content\">\n", fp); + fprintf( + fp, + " | <a href=\"%sfile/%s.html\">LICENSE</a>", + relpath, + license + ); + + fputs("</td></tr></table>" "\n", fp); + fputs("<hr/>" "\n", fp); + + fputs("<div id=\"content\">" "\n", fp); } void -writefooter(FILE *fp) +write_footer(FILE *fp) { - fputs("</div>\n</body>\n</html>\n", fp); + fputs("</div>" "\n" "</body>" "\n" "</html>" "\n", fp); } size_t -writeblobhtml(FILE *fp, const git_blob *blob) +write_blob_html(FILE *fp, const git_blob *blob) { size_t n = 0, i, len, prev; - const char *nfmt = "<a href=\"#l%zu\" class=\"line\" id=\"l%zu\">%7zu</a> "; + const char *nfmt = "<a href=\"#l%zu\" class=\"line\" id=\"l%zu\">%7zu</a>"; const char *s = git_blob_rawcontent(blob); len = git_blob_rawsize(blob); - fputs("<pre id=\"blob\">\n", fp); + fputs("<pre id=\"blob\">" "\n", fp); if (len > 0) { for (i = 0, prev = 0; i < len; i++) { @@ -572,25 +633,25 @@ writeblobhtml(FILE *fp, const git_blob *blob) continue; n++; fprintf(fp, nfmt, n, n, n); - xmlencodeline(fp, &s[prev], i - prev + 1); - putc('\n', fp); + put_xml_line(fp, &s[prev], i - prev + 1); + fputs("\n", fp); prev = i + 1; } /* trailing data */ if ((len - prev) > 0) { n++; fprintf(fp, nfmt, n, n, n); - xmlencodeline(fp, &s[prev], len - prev); + put_xml_line(fp, &s[prev], len - prev); } } - fputs("</pre>\n", fp); + fputs("</pre>" "\n", fp); return n; } void -printcommit(FILE *fp, struct commitinfo *ci) +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); @@ -601,24 +662,24 @@ printcommit(FILE *fp, struct commitinfo *ci) if (ci->author) { fputs("<b>Author:</b> ", fp); - xmlencode(fp, ci->author->name, strlen(ci->author->name)); + put_xml(fp, ci->author->name, strlen(ci->author->name)); fputs(" &lt;<a href=\"mailto:", fp); - xmlencode(fp, ci->author->email, strlen(ci->author->email)); /* not percent-encoded */ + put_xml(fp, ci->author->email, strlen(ci->author->email)); /* not percent-encoded */ fputs("\">", fp); - xmlencode(fp, ci->author->email, strlen(ci->author->email)); + put_xml(fp, ci->author->email, strlen(ci->author->email)); fputs("</a>&gt;\n<b>Date:</b> ", fp); printtime(fp, &(ci->author->when)); putc('\n', fp); } if (ci->msg) { putc('\n', fp); - xmlencode(fp, ci->msg, strlen(ci->msg)); + put_xml(fp, ci->msg, strlen(ci->msg)); putc('\n', fp); } } void -printshowfile(FILE *fp, struct commitinfo *ci) +print_show_file(FILE *fp, struct CommitInfo *ci) { const git_diff_delta *delta; const git_diff_hunk *hunk; @@ -628,32 +689,35 @@ printshowfile(FILE *fp, struct commitinfo *ci) char linestr[80]; int c; - printcommit(fp, ci); + print_commit(fp, ci); if (!ci->deltas) return; - if (ci->filecount > 1000 || - ci->ndeltas > 1000 || - ci->addcount > 100000 || - ci->delcount > 100000) { + if ( + ci->file_count > 1000 || + ci->ndeltas > 1000 || + ci->add_count > 100000 || + ci->del_count > 100000 + ) { fputs("Diff is too large, output suppressed.\n", fp); return; } /* diff stat */ - fputs("<b>Diffstat:</b>\n<table>", fp); + fputs("<b>Diffstat:</b>" "\n", fp); + fputs("<table id=\"diffstat\">", fp); for (i = 0; i < ci->ndeltas; i++) { delta = git_patch_get_delta(ci->deltas[i]->patch); switch (delta->status) { - case GIT_DELTA_ADDED: c = 'A'; break; - case GIT_DELTA_COPIED: c = 'C'; break; - case GIT_DELTA_DELETED: c = 'D'; break; - case GIT_DELTA_MODIFIED: c = 'M'; break; - case GIT_DELTA_RENAMED: c = 'R'; break; + case GIT_DELTA_ADDED: c = 'A'; break; + case GIT_DELTA_COPIED: c = 'C'; break; + case GIT_DELTA_DELETED: c = 'D'; break; + case GIT_DELTA_MODIFIED: c = 'M'; break; + case GIT_DELTA_RENAMED: c = 'R'; break; case GIT_DELTA_TYPECHANGE: c = 'T'; break; - default: c = ' '; break; + default: c = ' '; break; } if (c == ' ') fprintf(fp, "<tr><td>%c", c); @@ -661,14 +725,14 @@ printshowfile(FILE *fp, struct commitinfo *ci) fprintf(fp, "<tr><td class=\"%c\">%c", c, c); fprintf(fp, "</td><td><a href=\"#h%zu\">", i); - xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path)); + put_xml(fp, delta->old_file.path, strlen(delta->old_file.path)); if (strcmp(delta->old_file.path, delta->new_file.path)) { fputs(" -&gt; ", fp); - xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path)); + put_xml(fp, delta->new_file.path, strlen(delta->new_file.path)); } - add = ci->deltas[i]->addcount; - del = ci->deltas[i]->delcount; + add = ci->deltas[i]->add_count; + del = ci->deltas[i]->del_count; changed = add + del; total = sizeof(linestr) - 2; if (changed > total) { @@ -680,17 +744,25 @@ printshowfile(FILE *fp, struct commitinfo *ci) memset(&linestr, '+', add); memset(&linestr[add], '-', del); - fprintf(fp, "</a></td><td> | </td><td class=\"num\">%zu</td><td><span class=\"i\">", - ci->deltas[i]->addcount + ci->deltas[i]->delcount); + fprintf( + fp, + "</a></td>" + // "<td> | </td>" + "<td class=\"num\">%zu</td><td><span class=\"i\">", + ci->deltas[i]->add_count + ci->deltas[i]->del_count + ); 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></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"); + fprintf( + fp, + "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n", + ci->file_count, ci->file_count == 1 ? "" : "s", + ci->add_count, ci->add_count == 1 ? "" : "s", + ci->del_count, ci->del_count == 1 ? "" : "s" + ); fputs("<hr/>", fp); @@ -698,13 +770,13 @@ 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); - percentencode(fp, delta->old_file.path, strlen(delta->old_file.path)); + put_percent_encoded(fp, delta->old_file.path, strlen(delta->old_file.path)); fputs(".html\">", fp); - xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path)); + put_xml(fp, delta->old_file.path, strlen(delta->old_file.path)); fprintf(fp, "</a> b/<a href=\"%sfile/", relpath); - percentencode(fp, delta->new_file.path, strlen(delta->new_file.path)); + put_percent_encoded(fp, delta->new_file.path, strlen(delta->new_file.path)); fprintf(fp, ".html\">"); - xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path)); + put_xml(fp, delta->new_file.path, strlen(delta->new_file.path)); fprintf(fp, "</a></b>\n"); /* check binary data */ @@ -719,7 +791,7 @@ printshowfile(FILE *fp, struct commitinfo *ci) break; fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j); - xmlencode(fp, hunk->header, hunk->header_len); + put_xml(fp, hunk->header, hunk->header_len); fputs("</a>", fp); for (k = 0; ; k++) { @@ -733,7 +805,7 @@ printshowfile(FILE *fp, struct commitinfo *ci) i, j, k, i, j, k); else putc(' ', fp); - xmlencodeline(fp, line->content, line->content_len); + put_xml_line(fp, line->content, line->content_len); putc('\n', fp); if (line->old_lineno == -1 || line->new_lineno == -1) fputs("</a>", fp); @@ -743,7 +815,7 @@ printshowfile(FILE *fp, struct commitinfo *ci) } void -writelogline(FILE *fp, struct commitinfo *ci) +write_log_line(FILE *fp, struct CommitInfo *ci) { fputs("<tr><td>", fp); if (ci->author) @@ -751,25 +823,25 @@ writelogline(FILE *fp, struct commitinfo *ci) fputs("</td><td>", fp); if (ci->summary) { fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid); - xmlencode(fp, ci->summary, strlen(ci->summary)); + put_xml(fp, ci->summary, strlen(ci->summary)); fputs("</a>", fp); } fputs("</td><td>", fp); if (ci->author) - xmlencode(fp, ci->author->name, strlen(ci->author->name)); + put_xml(fp, ci->author->name, strlen(ci->author->name)); fputs("</td><td class=\"num\" align=\"right\">", fp); - fprintf(fp, "%zu", ci->filecount); + fprintf(fp, "%zu", ci->file_count); fputs("</td><td class=\"num\" align=\"right\">", fp); - fprintf(fp, "+%zu", ci->addcount); + fprintf(fp, "+%zu", ci->add_count); fputs("</td><td class=\"num\" align=\"right\">", fp); - fprintf(fp, "-%zu", ci->delcount); + fprintf(fp, "-%zu", ci->del_count); fputs("</td></tr>\n", fp); } int -writelog(FILE *fp, const git_oid *oid) +write_log(FILE *fp, const git_oid *oid) { - struct commitinfo *ci; + struct CommitInfo *ci; git_revwalk *w = NULL; git_oid id; char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1]; @@ -800,51 +872,146 @@ writelog(FILE *fp, const git_oid *oid) continue; } - if (!(ci = commitinfo_getbyoid(&id))) + if (!(ci = commit_info_get_by_oid(&id))) break; /* diffstat: for stagit HTML required for the log.html line */ - if (commitinfo_getstats(ci) == -1) + if (commit_info_get_stats(ci) == -1) goto err; if (nlogcommits != 0) { - writelogline(fp, ci); + write_log_line(fp, ci); if (nlogcommits > 0) nlogcommits--; } if (cachefile) - writelogline(wcachefp, ci); + write_log_line(wcachefp, ci); /* check if file exists if so skip it */ if (r) { relpath = "../"; fpfile = efopen(path, "w"); - writeheader(fpfile, ci->summary); + write_header(fpfile, ci->summary); + fputs("<pre>", fpfile); - printshowfile(fpfile, ci); + print_show_file(fpfile, ci); fputs("</pre>\n", fpfile); - writefooter(fpfile); - checkfileerror(fpfile, path, 'w'); + + write_footer(fpfile); + check_file_error(fpfile, path, 'w'); fclose(fpfile); } err: - commitinfo_free(ci); + commit_info_free(ci); } 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); - } + 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; } +// TODO fix this ungodly mess. Yuck! void -printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag) +write_log_cached(FILE *fp, const git_oid *head) +{ + char tmppath[64] = "cache.XXXXXXXXXXXX"; + char buf[BUFSIZ]; + mode_t mask; + int fd, n; + + /* read from cache file (does not need to exist) */ + if ((rcachefp = fopen(cachefile, "r"))) { + if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp)) + errx(1, "%s: no object id", cachefile); + if (git_oid_fromstr(&lastoid, lastoidstr)) + errx(1, "%s: invalid object id", cachefile); + } + + /* write log to (temporary) cache */ + if ((fd = mkstemp(tmppath)) == -1) + err(1, "mkstemp"); + if (!(wcachefp = fdopen(fd, "w"))) + err(1, "fdopen: '%s'", tmppath); + /* write last commit id (HEAD) */ + git_oid_tostr(buf, sizeof(buf), head); + fprintf(wcachefp, "%s\n", buf); + + write_log(fp, head); + + if (rcachefp) { + /* append previous log to log.html and the new cache */ + while (!feof(rcachefp)) { + n = fread(buf, 1, sizeof(buf), rcachefp); + if (ferror(rcachefp)) + break; + if ( + fwrite(buf, 1, n, fp) != n || + fwrite(buf, 1, n, wcachefp) != n + ) + break; + } + check_file_error(rcachefp, cachefile, 'r'); + fclose(rcachefp); + } + check_file_error(wcachefp, tmppath, 'w'); + fclose(wcachefp); + + // TODO previously, this would check that the log page was written + // successfully before renaming the cachefile... I'm probably going + // to blow this whole caching mess away so as long as it runs for + // now, it shouldn't matter. + + if (rename(tmppath, cachefile)) + err(1, "rename: '%s' to '%s'", tmppath, cachefile); + umask((mask = umask(0))); + if ( + chmod( + cachefile, + (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask + ) + ) + err(1, "chmod: '%s'", cachefile); +} + +void +write_log_page(FILE *fp, const git_oid *head) +{ + relpath = ""; + mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO); + write_header(fp, "Log"); + 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 (head && !cachefile) write_log(fp, head); + if (head && cachefile) write_log_cached(fp, head); + + fputs("</tbody></table>", fp); + write_footer(fp); +} + +void +print_commit_atom(FILE *fp, struct CommitInfo *ci, const char *tag) { fputs("<entry>\n", fp); @@ -863,20 +1030,24 @@ printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag) fputs("<title>", fp); if (tag && tag[0]) { fputs("[", fp); - xmlencode(fp, tag, strlen(tag)); + put_xml(fp, tag, strlen(tag)); fputs("] ", fp); } - xmlencode(fp, ci->summary, strlen(ci->summary)); + put_xml(fp, ci->summary, strlen(ci->summary)); fputs("</title>\n", fp); } - fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"%scommit/%s.html\" />\n", - baseurl, ci->oid); + fprintf( + fp, + "<link rel=\"alternate\" type=\"text/html\" href=\"%scommit/%s.html\" />\n", + baseurl, + ci->oid + ); if (ci->author) { fputs("<author>\n<name>", fp); - xmlencode(fp, ci->author->name, strlen(ci->author->name)); + put_xml(fp, ci->author->name, strlen(ci->author->name)); fputs("</name>\n<email>", fp); - xmlencode(fp, ci->author->email, strlen(ci->author->email)); + put_xml(fp, ci->author->email, strlen(ci->author->email)); fputs("</email>\n</author>\n", fp); } @@ -886,35 +1057,38 @@ printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag) fprintf(fp, "parent %s\n", ci->parentoid); if (ci->author) { fputs("Author: ", fp); - xmlencode(fp, ci->author->name, strlen(ci->author->name)); + put_xml(fp, ci->author->name, strlen(ci->author->name)); fputs(" &lt;", fp); - xmlencode(fp, ci->author->email, strlen(ci->author->email)); + put_xml(fp, ci->author->email, strlen(ci->author->email)); fputs("&gt;\nDate: ", fp); printtime(fp, &(ci->author->when)); putc('\n', fp); } if (ci->msg) { putc('\n', fp); - xmlencode(fp, ci->msg, strlen(ci->msg)); + put_xml(fp, ci->msg, strlen(ci->msg)); } fputs("\n</content>\n</entry>\n", fp); } int -writeatom(FILE *fp, int all) +write_atom(FILE *fp, int all) { - struct referenceinfo *ris = NULL; + struct ReferenceInfo *ris = NULL; size_t refcount = 0; - struct commitinfo *ci; + struct CommitInfo *ci; git_revwalk *w = NULL; git_oid id; size_t i, m = 100; /* last 'm' commits */ - fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp); - xmlencode(fp, strippedname, strlen(strippedname)); + fputs( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", + fp + ); + put_xml(fp, stripped_name, strlen(stripped_name)); fputs(", branch HEAD</title>\n<subtitle>", fp); - xmlencode(fp, description, strlen(description)); + put_xml(fp, description, strlen(description)); fputs("</subtitle>\n", fp); /* all commits or only tags? */ @@ -922,20 +1096,22 @@ writeatom(FILE *fp, int all) git_revwalk_new(&w, repo); git_revwalk_push_head(w); for (i = 0; i < m && !git_revwalk_next(&id, w); i++) { - if (!(ci = commitinfo_getbyoid(&id))) + if (!(ci = commit_info_get_by_oid(&id))) break; - printcommitatom(fp, ci, ""); - commitinfo_free(ci); + print_commit_atom(fp, ci, ""); + commit_info_free(ci); } git_revwalk_free(w); - } else if (getrefs(&ris, &refcount) != -1) { + } else if (get_refs(&ris, &refcount) != -1) { /* references: tags */ for (i = 0; i < refcount; i++) { if (git_reference_is_tag(ris[i].ref)) - printcommitatom(fp, ris[i].ci, - git_reference_shorthand(ris[i].ref)); + print_commit_atom( + fp, ris[i].ci, + git_reference_shorthand(ris[i].ref) + ); - commitinfo_free(ris[i].ci); + commit_info_free(ris[i].ci); git_reference_free(ris[i].ref); } free(ris); @@ -947,7 +1123,7 @@ writeatom(FILE *fp, int all) } size_t -writeblob(git_object *obj, const char *fpath, const char *filename, size_t filesize) +write_blob(git_object *obj, const char *fpath, const char *filename, size_t filesize) { char tmp[PATH_MAX] = "", *d; const char *p; @@ -968,19 +1144,20 @@ writeblob(git_object *obj, const char *fpath, const char *filename, size_t files relpath = tmp; fp = efopen(fpath, "w"); - writeheader(fp, filename); + write_header(fp, filename); fputs("<p> ", fp); - xmlencode(fp, filename, strlen(filename)); + put_xml(fp, filename, strlen(filename)); fprintf(fp, " (%zuB)", filesize); - fputs("</p><hr/>", fp); + fputs("</p>", fp); if (git_blob_is_binary((git_blob *)obj)) + // TODO check and display image fputs("<p>Binary file.</p>\n", fp); else - lc = writeblobhtml(fp, (git_blob *)obj); + lc = write_blob_html(fp, (git_blob *)obj); - writefooter(fp); - checkfileerror(fp, fpath, 'w'); + write_footer(fp); + check_file_error(fp, fpath, 'w'); fclose(fp); relpath = ""; @@ -996,22 +1173,14 @@ filemode(git_filemode_t m) memset(mode, '-', sizeof(mode) - 1); mode[10] = '\0'; - if (S_ISREG(m)) - mode[0] = '-'; - else if (S_ISBLK(m)) - mode[0] = 'b'; - else if (S_ISCHR(m)) - mode[0] = 'c'; - else if (S_ISDIR(m)) - mode[0] = 'd'; - else if (S_ISFIFO(m)) - mode[0] = 'p'; - else if (S_ISLNK(m)) - mode[0] = 'l'; - else if (S_ISSOCK(m)) - mode[0] = 's'; - else - mode[0] = '?'; + if (S_ISREG(m)) mode[0] = '-'; + else if (S_ISBLK(m)) mode[0] = 'b'; + else if (S_ISCHR(m)) mode[0] = 'c'; + else if (S_ISDIR(m)) mode[0] = 'd'; + else if (S_ISFIFO(m)) mode[0] = 'p'; + else if (S_ISLNK(m)) mode[0] = 'l'; + else if (S_ISSOCK(m)) mode[0] = 's'; + else mode[0] = '?'; if (m & S_IRUSR) mode[1] = 'r'; if (m & S_IWUSR) mode[2] = 'w'; @@ -1031,7 +1200,7 @@ filemode(git_filemode_t m) } int -writefilestree(FILE *fp, git_tree *tree, const char *path) +write_file_list(FILE *fp, git_tree *tree, const char *path) { const git_tree_entry *entry = NULL; git_object *obj = NULL; @@ -1042,13 +1211,14 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) count = git_tree_entrycount(tree); for (i = 0; i < count; i++) { - if (!(entry = git_tree_entry_byindex(tree, i)) || - !(entryname = git_tree_entry_name(entry))) + if ( + !(entry = git_tree_entry_byindex(tree, i)) || + !(entryname = git_tree_entry_name(entry)) + ) return -1; - joinpath(entrypath, sizeof(entrypath), path, entryname); + join_path(entrypath, sizeof(entrypath), path, entryname); - r = snprintf(filepath, sizeof(filepath), "file/%s.html", - entrypath); + 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); @@ -1058,8 +1228,10 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) break; case GIT_OBJ_TREE: /* NOTE: recurses */ - ret = writefilestree(fp, (git_tree *)obj, - entrypath); + // TODO create page for each dir + // instead of dumping all files into "files.html" + // or, is that a good idea? hm... + ret = write_file_list( fp, (git_tree *)obj, entrypath); git_object_free(obj); if (ret) return ret; @@ -1070,29 +1242,30 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) } filesize = git_blob_rawsize((git_blob *)obj); - lc = writeblob(obj, filepath, entryname, filesize); + lc = write_blob(obj, filepath, entryname, filesize); fputs("<tr><td>", fp); fputs(filemode(git_tree_entry_filemode(entry)), fp); fprintf(fp, "</td><td><a href=\"%s", relpath); - percentencode(fp, filepath, strlen(filepath)); + put_percent_encoded(fp, filepath, strlen(filepath)); fputs("\">", fp); - xmlencode(fp, entrypath, strlen(entrypath)); + 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); + 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); - xmlencode(fp, entrypath, strlen(entrypath)); + 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)); - xmlencode(fp, oid, strlen(oid)); + put_xml(fp, oid, strlen(oid)); fputs("</td><td class=\"num\" align=\"right\"></td></tr>\n", fp); } } @@ -1101,20 +1274,25 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) } int -writefiles(FILE *fp, const git_oid *id) +write_files(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><b>Mode</b></td><td><b>Name</b></td>" - "<td class=\"num\" align=\"right\"><b>Size</b></td>" - "</tr>\n</thead><tbody>\n", fp); + fputs( + "<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 + ); - if (!git_commit_lookup(&commit, repo, id) && - !git_commit_tree(&tree, commit)) - ret = writefilestree(fp, tree, ""); + if ( + !git_commit_lookup(&commit, repo, id) && + !git_commit_tree(&tree, commit) + ) + ret = write_file_list(fp, tree, ""); fputs("</tbody></table>", fp); @@ -1125,16 +1303,16 @@ writefiles(FILE *fp, const git_oid *id) } int -writerefs(FILE *fp) +write_refs(FILE *fp) { - struct referenceinfo *ris = NULL; - struct commitinfo *ci; + struct ReferenceInfo *ris = NULL; + struct CommitInfo *ci; size_t count, i, j, refcount; const char *titles[] = { "Branches", "Tags" }; const char *ids[] = { "branches", "tags" }; const char *s; - if (getrefs(&ris, &refcount) == -1) + if (get_refs(&ris, &refcount) == -1) return -1; for (i = 0, j = 0, count = 0; i < refcount; i++) { @@ -1146,26 +1324,29 @@ writerefs(FILE *fp) } /* print header if it has an entry (first). */ - if (++count == 1) { - 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]); - } + if (++count == 1) + 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] + ); ci = ris[i].ci; s = git_reference_shorthand(ris[i].ref); fputs("<tr><td>", fp); - xmlencode(fp, s, strlen(s)); + put_xml(fp, s, strlen(s)); fputs("</td><td>", fp); if (ci->author) printtimeshort(fp, &(ci->author->when)); fputs("</td><td>", fp); if (ci->author) - xmlencode(fp, ci->author->name, strlen(ci->author->name)); + put_xml(fp, ci->author->name, strlen(ci->author->name)); fputs("</td></tr>\n", fp); } /* table footer */ @@ -1173,7 +1354,7 @@ writerefs(FILE *fp) fputs("</tbody></table><br/>\n", fp); for (i = 0; i < refcount; i++) { - commitinfo_free(ris[i].ci); + commit_info_free(ris[i].ci); git_reference_free(ris[i].ref); } free(ris); @@ -1184,8 +1365,12 @@ writerefs(FILE *fp) void usage(char *argv0) { - fprintf(stderr, "usage: %s [-c cachefile | -l commits] " - "[-u baseurl] repodir\n", argv0); + fprintf( + stderr, + "usage: %s [-c cachefile | -l commits] " + "[-u baseurl] repodir\n", + argv0 + ); exit(1); } @@ -1194,13 +1379,11 @@ main(int argc, char *argv[]) { git_object *obj = NULL; const git_oid *head = NULL; - mode_t mask; FILE *fp, *fpread; char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p; - char tmppath[64] = "cache.XXXXXXXXXXXX", buf[BUFSIZ]; - size_t n; - int i, fd; + int i; + /* parse args */ for (i = 1; i < argc; i++) { if (argv[i][0] != '-') { if (repodir) @@ -1215,8 +1398,12 @@ main(int argc, char *argv[]) usage(argv[0]); errno = 0; nlogcommits = strtoll(argv[++i], &p, 10); - if (argv[i][0] == '\0' || *p != '\0' || - nlogcommits <= 0 || errno) + if ( + argv[i][0] == '\0' || + *p != '\0' || + nlogcommits <= 0 || + errno + ) usage(argv[0]); } else if (argv[i][1] == 'u') { if (i + 1 >= argc) @@ -1255,8 +1442,14 @@ main(int argc, char *argv[]) } #endif - if (git_repository_open_ext(&repo, repodir, - GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) { + if ( + git_repository_open_ext( + &repo, + repodir, + GIT_REPOSITORY_OPEN_NO_SEARCH, + NULL + ) < 0 + ) { fprintf(stderr, "%s: cannot open repository\n", argv[0]); return 1; } @@ -1267,161 +1460,104 @@ main(int argc, char *argv[]) git_object_free(obj); /* use directory name as name */ - if ((name = strrchr(repodirabs, '/'))) - name++; - else - name = ""; + if ((name = strrchr(repodirabs, '/'))) name++; + else name = ""; /* strip .git suffix */ - if (!(strippedname = strdup(name))) + if (!(stripped_name = strdup(name))) err(1, "strdup"); - if ((p = strrchr(strippedname, '.'))) + if ((p = strrchr(stripped_name, '.'))) if (!strcmp(p, ".git")) *p = '\0'; /* read description or .git/description */ - joinpath(path, sizeof(path), repodir, "description"); + join_path(path, sizeof(path), repodir, "description"); if (!(fpread = fopen(path, "r"))) { - joinpath(path, sizeof(path), repodir, ".git/description"); + join_path(path, sizeof(path), repodir, ".git/description"); fpread = fopen(path, "r"); } if (fpread) { if (!fgets(description, sizeof(description), fpread)) description[0] = '\0'; - checkfileerror(fpread, path, 'r'); + check_file_error(fpread, path, 'r'); fclose(fpread); } /* read url or .git/url */ - joinpath(path, sizeof(path), repodir, "url"); + join_path(path, sizeof(path), repodir, "url"); if (!(fpread = fopen(path, "r"))) { - joinpath(path, sizeof(path), repodir, ".git/url"); + join_path(path, sizeof(path), repodir, ".git/url"); fpread = fopen(path, "r"); } if (fpread) { if (!fgets(cloneurl, sizeof(cloneurl), fpread)) cloneurl[0] = '\0'; - checkfileerror(fpread, path, 'r'); + check_file_error(fpread, path, 'r'); fclose(fpread); cloneurl[strcspn(cloneurl, "\n")] = '\0'; } /* check LICENSE */ - for (i = 0; i < LEN(licensefiles) && !license; i++) { - if (!git_revparse_single(&obj, repo, licensefiles[i]) && - git_object_type(obj) == GIT_OBJ_BLOB) - license = licensefiles[i] + strlen("HEAD:"); + for (i = 0; i < LEN(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 < LEN(readmefiles) && !readme; i++) { - if (!git_revparse_single(&obj, repo, readmefiles[i]) && - git_object_type(obj) == GIT_OBJ_BLOB) - readme = readmefiles[i] + strlen("HEAD:"); + for (i = 0; i < LEN(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); } - if (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") && - git_object_type(obj) == GIT_OBJ_BLOB) + if ( + !git_revparse_single(&obj, repo, "HEAD:.gitmodules") && + git_object_type(obj) == GIT_OBJ_BLOB + ) submodules = ".gitmodules"; git_object_free(obj); /* log for HEAD */ fp = efopen("log.html", "w"); - relpath = ""; - mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO); - writeheader(fp, "Log"); - 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 && head) { - /* read from cache file (does not need to exist) */ - if ((rcachefp = fopen(cachefile, "r"))) { - if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp)) - errx(1, "%s: no object id", cachefile); - if (git_oid_fromstr(&lastoid, lastoidstr)) - errx(1, "%s: invalid object id", cachefile); - } - - /* write log to (temporary) cache */ - if ((fd = mkstemp(tmppath)) == -1) - err(1, "mkstemp"); - if (!(wcachefp = fdopen(fd, "w"))) - err(1, "fdopen: '%s'", tmppath); - /* write last commit id (HEAD) */ - git_oid_tostr(buf, sizeof(buf), head); - fprintf(wcachefp, "%s\n", buf); - - writelog(fp, head); - - if (rcachefp) { - /* append previous log to log.html and the new cache */ - while (!feof(rcachefp)) { - n = fread(buf, 1, sizeof(buf), rcachefp); - if (ferror(rcachefp)) - break; - if (fwrite(buf, 1, n, fp) != n || - fwrite(buf, 1, n, wcachefp) != n) - break; - } - checkfileerror(rcachefp, cachefile, 'r'); - fclose(rcachefp); - } - checkfileerror(wcachefp, tmppath, 'w'); - fclose(wcachefp); - } else { - if (head) - writelog(fp, head); - } - - fputs("</tbody></table>", fp); - writefooter(fp); - checkfileerror(fp, "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"); - writeheader(fp, "Files"); - if (head) - writefiles(fp, head); - writefooter(fp); - checkfileerror(fp, "files.html", 'w'); + write_header(fp, "Files"); + if (head) write_files(fp, head); + write_footer(fp); + check_file_error(fp, "files.html", 'w'); fclose(fp); /* summary page with branches and tags */ fp = efopen("refs.html", "w"); - writeheader(fp, "Refs"); - writerefs(fp); - writefooter(fp); - checkfileerror(fp, "refs.html", 'w'); + write_header(fp, "Refs"); + write_refs(fp); + write_footer(fp); + check_file_error(fp, "refs.html", 'w'); fclose(fp); /* Atom feed */ fp = efopen("atom.xml", "w"); - writeatom(fp, 1); - checkfileerror(fp, "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"); - writeatom(fp, 0); - checkfileerror(fp, "tags.xml", 'w'); + write_atom(fp, 0); + check_file_error(fp, "tags.xml", 'w'); fclose(fp); - /* rename new cache file on success */ - if (cachefile && head) { - if (rename(tmppath, cachefile)) - err(1, "rename: '%s' to '%s'", tmppath, cachefile); - umask((mask = umask(0))); - if (chmod(cachefile, - (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask)) - err(1, "chmod: '%s'", cachefile); - } - /* cleanup */ git_repository_free(repo); git_libgit2_shutdown(); diff --git a/style.css b/style.css @@ -1,6 +1,5 @@ body { color: #000; - background-color: #fff; font-family: monospace; } @@ -17,8 +16,18 @@ img { border: 0; } +a { + /* color: #f000bb; */ + color: #e00086; +} + +a:visited { + /* color: #cd00f0; */ + color: #9700b3; +} + a:target { - background-color: #ccc; + background-color: #ddf; } a.d, @@ -28,12 +37,9 @@ a.line { text-decoration: none; } -#blob a { - color: #555; -} - #blob a:hover { - color: blue; + background-color: #fee; + color: #000; text-decoration: none; } @@ -47,7 +53,7 @@ table td { #content table td { vertical-align: top; - white-space: nowrap; + white-space: pre-wrap; } #branches tr:hover td, @@ -55,7 +61,7 @@ table td { #index tr:hover td, #log tr:hover td, #files tr:hover td { - background-color: #eee; + background-color: #fee; } #index tr td:nth-child(2), @@ -65,6 +71,30 @@ table td { white-space: normal; } +#diffstat tr td:nth-child(2) { + border-right: 1px solid black; + margin-right: 5px; + padding-right: 5px; +} + +table#diffstat { + border-collapse: collapse; +} + +#blob a { + text-align: right; + border-right: 1px solid black; + margin-right: 5px; + padding-right: 5px; + user-select: none; +} + +/* +table#blob { + border-collapse: collapse; +} +*/ + td.num { text-align: right; } @@ -81,22 +111,23 @@ hr { pre { font-family: monospace; + margin: 0px; } pre a.h { - color: #00a; + color: #0072aa; /* blue */ } .A, span.i, pre a.i { - color: #070; + color: #00ba53; /* green */ } .D, span.d, pre a.d { - color: #e00; + color: #ee3200; /* red */ } pre a.h:hover, @@ -104,51 +135,3 @@ pre a.i:hover, pre a.d:hover { text-decoration: none; } - -@media (prefers-color-scheme: dark) { - body { - background-color: #000; - color: #bdbdbd; - } - hr { - border-color: #222; - } - a { - color: #56c8ff; - } - a:target { - background-color: #222; - } - .desc { - color: #aaa; - } - #blob a { - color: #555; - } - #blob a:target { - color: #eee; - } - #blob a:hover { - color: #56c8ff; - } - pre a.h { - color: #00cdcd; - } - .A, - span.i, - pre a.i { - color: #00cd00; - } - .D, - span.d, - pre a.d { - color: #cd0000; - } - #branches tr:hover td, - #tags tr:hover td, - #index tr:hover td, - #log tr:hover td, - #files tr:hover td { - background-color: #111; - } -}

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