git.y1.nz

sgw

Static git web (fork of stagit)
download: https://git.y1.nz/archives/sgw.tar.gz
README | Files | Log | Refs | LICENSE

main.c

      1 #include <sys/stat.h>
      2 #include <sys/types.h>
      3 
      4 #include <err.h>
      5 #include <errno.h>
      6 #include <libgen.h>
      7 #include <stdint.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <time.h>
     12 #include <unistd.h>
     13 #include <limits.h>
     14 
     15 #include <git2.h>
     16 
     17 #include "compat.h"
     18 #include "opt.h"
     19 #include "files.h"
     20 #include "repo.h"
     21 
     22 #define CPUT(f, c)  putc((c), (f))
     23 #define put_xml(fp, s)  put_xml_len((fp), (s), strlen(s))
     24 #define SPUTF(f, s)  fputs((s), (f))
     25 
     26 #include "config.h"
     27 
     28 struct DeltaInfo {
     29 	git_patch *patch;
     30 
     31 	size_t add_count;
     32 	size_t del_count;
     33 };
     34 
     35 struct CommitInfo {
     36 	const git_oid *id;
     37 
     38 	char oid[GIT_OID_HEXSZ + 1];
     39 	char parentoid[GIT_OID_HEXSZ + 1];
     40 
     41 	const git_signature *author;
     42 	const git_signature *committer;
     43 	const char *summary;
     44 	const char *msg;
     45 
     46 	git_diff *diff;
     47 	git_commit *commit;
     48 	git_commit *parent;
     49 	git_tree *commit_tree;
     50 	git_tree *parent_tree;
     51 
     52 	size_t add_count;
     53 	size_t del_count;
     54 	size_t file_count;
     55 
     56 	struct DeltaInfo **deltas;
     57 	size_t ndeltas;
     58 };
     59 
     60 /* reference and associated data for sorting */
     61 struct ReferenceInfo {
     62 	struct git_reference *ref;
     63 	struct CommitInfo *ci;
     64 };
     65 
     66 struct Weight {
     67 	size_t bytes;
     68 	size_t lines;
     69 };
     70 
     71 /* miscellaneous constants */
     72 static const char orders[] = { ' ', 'k', 'M', 'G', 'T', 'P', 'E' };
     73 static const char tab[] = "0123456789ABCDEF";
     74 
     75 /* GLOBALS */
     76 static Opt opt;
     77 static RepoData *data;
     78 static FILE *index_fp;
     79 static char index_fname[PATH_MAX];
     80 
     81 // TODO reorganize so that this ugliness isn't necessary...
     82 int put_index_log(FILE *fp, RepoData *data);
     83 void put_index_header(FILE *fp);
     84 void put_index_footer(FILE *fp);
     85 
     86 void
     87 get_page_path(char *buf, size_t buf_size, const char *dir, char *name)
     88 {
     89 	int index = 0, chars = 0;
     90 
     91 	// TODO tweak this so that it correctly places slashes between names
     92 	chars = snprintf(buf + index, buf_size - index, "%s", dir);
     93 	if (chars < 0 || (size_t) (index += chars) >= buf_size)
     94 		goto page_path_err;
     95 
     96 	if (chars != 0) buf[index++] = '/';
     97 
     98 	chars = snprintf(buf + index, buf_size - index, PAGE_DEST);
     99 	if (chars < 0 || (size_t) (index += chars) >= buf_size)
    100 		goto page_path_err;
    101 
    102 	if (chars != 0) buf[index++] = '/';
    103 
    104 	chars = snprintf(buf + index, buf_size - index, "%s", name);
    105 	if (chars < 0 || (size_t) (index += chars) >= buf_size)
    106 		goto page_path_err;
    107 
    108 	return;
    109 
    110 page_path_err:
    111 	errx(1, "page path truncated! '%s' / '%s'" "\n", dir, name);
    112 }
    113 
    114 void
    115 get_index_page_path(char *buf, size_t buf_size, const char *dir, char *name)
    116 {
    117 	int index = 0, chars = 0;
    118 
    119 	// TODO tweak this so that it correctly places slashes between names
    120 	chars = snprintf(buf + index, buf_size - index, "%s", dir);
    121 	if (chars < 0 || (size_t) (index += chars) >= buf_size)
    122 		goto page_path_err;
    123 
    124 	const char *format = dir[0] == '\0' ? "%s" : "/%s";
    125 	chars = snprintf(buf + index, buf_size - index, format, name);
    126 	if (chars < 0 || (size_t) (index += chars) >= buf_size)
    127 		goto page_path_err;
    128 
    129 	return;
    130 
    131 page_path_err:
    132 	errx(1, "page path truncated! '%s' / '%s'" "\n", dir, name);
    133 }
    134 
    135 void
    136 delta_info_free(struct DeltaInfo *di)
    137 {
    138 	if (!di) return;
    139 
    140 	git_patch_free(di->patch);
    141 	memset(di, 0, sizeof(*di));
    142 	free(di);
    143 }
    144 
    145 int
    146 commit_info_get_stats(struct CommitInfo *ci)
    147 {
    148 	struct DeltaInfo *di;
    149 	git_diff_options opts;
    150 	git_diff_find_options fopts;
    151 	const git_diff_delta *delta;
    152 	const git_diff_hunk *hunk;
    153 	const git_diff_line *line;
    154 	git_patch *patch = NULL;
    155 	size_t ndeltas, nhunks, nhunklines;
    156 	size_t i, j, k;
    157 
    158 	if (git_tree_lookup(&(ci->commit_tree), data->repo, git_commit_tree_id(ci->commit)))
    159 		goto err;
    160 	if (!git_commit_parent(&(ci->parent), ci->commit, 0)) {
    161 		if (git_tree_lookup(&(ci->parent_tree), data->repo, git_commit_tree_id(ci->parent))) {
    162 			ci->parent = NULL;
    163 			ci->parent_tree = NULL;
    164 		}
    165 	}
    166 
    167 	git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
    168 	opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH |
    169 		GIT_DIFF_IGNORE_SUBMODULES |
    170 		GIT_DIFF_INCLUDE_TYPECHANGE;
    171 	if (git_diff_tree_to_tree(&(ci->diff), data->repo, ci->parent_tree, ci->commit_tree, &opts))
    172 		goto err;
    173 
    174 	if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION))
    175 		goto err;
    176 	/* find renames and copies, exact matches (no heuristic) for renames. */
    177 	fopts.flags |= GIT_DIFF_FIND_RENAMES |
    178 		GIT_DIFF_FIND_COPIES |
    179 		GIT_DIFF_FIND_EXACT_MATCH_ONLY;
    180 	if (git_diff_find_similar(ci->diff, &fopts))
    181 		goto err;
    182 
    183 	ndeltas = git_diff_num_deltas(ci->diff);
    184 	if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct DeltaInfo *))))
    185 		err(1, "calloc");
    186 
    187 	for (i = 0; i < ndeltas; i++) {
    188 		if (git_patch_from_diff(&patch, ci->diff, i))
    189 			goto err;
    190 
    191 		if (!(di = calloc(1, sizeof(struct DeltaInfo))))
    192 			err(1, "calloc");
    193 		di->patch = patch;
    194 		ci->deltas[i] = di;
    195 
    196 		delta = git_patch_get_delta(patch);
    197 
    198 		/* skip stats for binary data */
    199 		if (delta->flags & GIT_DIFF_FLAG_BINARY)
    200 			continue;
    201 
    202 		nhunks = git_patch_num_hunks(patch);
    203 		for (j = 0; j < nhunks; j++) {
    204 			if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
    205 				break;
    206 			for (k = 0; ; k++) {
    207 				if (git_patch_get_line_in_hunk(&line, patch, j, k))
    208 					break;
    209 				if (line->old_lineno == -1) {
    210 					di->add_count++;
    211 					ci->add_count++;
    212 				} else if (line->new_lineno == -1) {
    213 					di->del_count++;
    214 					ci->del_count++;
    215 				}
    216 			}
    217 		}
    218 	}
    219 	ci->ndeltas = i;
    220 	ci->file_count = i;
    221 
    222 	return 0;
    223 
    224 err:
    225 	git_diff_free(ci->diff);
    226 	ci->diff = NULL;
    227 	git_tree_free(ci->commit_tree);
    228 	ci->commit_tree = NULL;
    229 	git_tree_free(ci->parent_tree);
    230 	ci->parent_tree = NULL;
    231 	git_commit_free(ci->parent);
    232 	ci->parent = NULL;
    233 
    234 	if (ci->deltas)
    235 		for (i = 0; i < ci->ndeltas; i++)
    236 			delta_info_free(ci->deltas[i]);
    237 	free(ci->deltas);
    238 	ci->deltas = NULL;
    239 	ci->ndeltas = 0;
    240 	ci->add_count = 0;
    241 	ci->del_count = 0;
    242 	ci->file_count = 0;
    243 
    244 	return -1;
    245 }
    246 
    247 void
    248 commit_info_free(struct CommitInfo *ci)
    249 {
    250 	size_t i;
    251 
    252 	if (!ci)
    253 		return;
    254 	if (ci->deltas)
    255 		for (i = 0; i < ci->ndeltas; i++)
    256 			delta_info_free(ci->deltas[i]);
    257 
    258 	free(ci->deltas);
    259 	git_diff_free(ci->diff);
    260 	git_tree_free(ci->commit_tree);
    261 	git_tree_free(ci->parent_tree);
    262 	git_commit_free(ci->commit);
    263 	git_commit_free(ci->parent);
    264 	memset(ci, 0, sizeof(*ci));
    265 	free(ci);
    266 }
    267 
    268 struct CommitInfo *
    269 commit_info_get_by_oid(const git_oid *id)
    270 {
    271 	struct CommitInfo *ci;
    272 
    273 	if (!(ci = calloc(1, sizeof(struct CommitInfo))))
    274 		err(1, "calloc");
    275 
    276 	if (git_commit_lookup(&(ci->commit), data->repo, id))
    277 		goto err;
    278 	ci->id = id;
    279 
    280 	git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
    281 	git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
    282 
    283 	ci->author = git_commit_author(ci->commit);
    284 	ci->committer = git_commit_committer(ci->commit);
    285 	ci->summary = git_commit_summary(ci->commit);
    286 	ci->msg = git_commit_message(ci->commit);
    287 
    288 	return ci;
    289 
    290 err:
    291 	commit_info_free(ci);
    292 
    293 	return NULL;
    294 }
    295 
    296 int
    297 refs_cmp(const void *v1, const void *v2)
    298 {
    299 	const struct ReferenceInfo *r1 = v1, *r2 = v2;
    300 	time_t t1, t2;
    301 	int r;
    302 
    303 	if ((r = git_reference_is_tag(r1->ref) - git_reference_is_tag(r2->ref)))
    304 		return r;
    305 
    306 	t1 = r1->ci->author ? r1->ci->author->when.time : 0;
    307 	t2 = r2->ci->author ? r2->ci->author->when.time : 0;
    308 	if ((r = t1 > t2 ? -1 : (t1 == t2 ? 0 : 1)))
    309 		return r;
    310 
    311 	return strcmp(
    312 		git_reference_shorthand(r1->ref),
    313 		git_reference_shorthand(r2->ref)
    314 	);
    315 }
    316 
    317 int
    318 get_refs(struct ReferenceInfo **pris, size_t *prefcount)
    319 {
    320 	struct ReferenceInfo *ris = NULL;
    321 	struct CommitInfo *ci = NULL;
    322 	git_reference_iterator *it = NULL;
    323 	const git_oid *id = NULL;
    324 	git_object *obj = NULL;
    325 	git_reference *dref = NULL, *r, *ref = NULL;
    326 	size_t i, refcount;
    327 
    328 	*pris = NULL;
    329 	*prefcount = 0;
    330 
    331 	if (git_reference_iterator_new(&it, data->repo))
    332 		return -1;
    333 
    334 	for (refcount = 0; !git_reference_next(&ref, it); ) {
    335 		if (!git_reference_is_branch(ref) && !git_reference_is_tag(ref)) {
    336 			git_reference_free(ref);
    337 			ref = NULL;
    338 			continue;
    339 		}
    340 
    341 		switch (git_reference_type(ref)) {
    342 		case GIT_REF_SYMBOLIC:
    343 			if (git_reference_resolve(&dref, ref))
    344 				goto err;
    345 			r = dref;
    346 			break;
    347 		case GIT_REF_OID:
    348 			r = ref;
    349 			break;
    350 		default:
    351 			continue;
    352 		}
    353 		if (
    354 			!git_reference_target(r) ||
    355 			git_reference_peel(&obj, r, GIT_OBJ_ANY)
    356 		)
    357 			goto err;
    358 		if (!(id = git_object_id(obj)))
    359 			goto err;
    360 		if (!(ci = commit_info_get_by_oid(id)))
    361 			break;
    362 
    363 		if (!(ris = reallocarray(ris, refcount + 1, sizeof(*ris))))
    364 			err(1, "realloc");
    365 		ris[refcount].ci = ci;
    366 		ris[refcount].ref = r;
    367 		refcount++;
    368 
    369 		git_object_free(obj);
    370 		obj = NULL;
    371 		git_reference_free(dref);
    372 		dref = NULL;
    373 	}
    374 	git_reference_iterator_free(it);
    375 
    376 	/* sort by type, date then shorthand name */
    377 	qsort(ris, refcount, sizeof(*ris), refs_cmp);
    378 
    379 	*pris = ris;
    380 	*prefcount = refcount;
    381 
    382 	return 0;
    383 
    384 err:
    385 	git_object_free(obj);
    386 	git_reference_free(dref);
    387 	commit_info_free(ci);
    388 	for (i = 0; i < refcount; i++) {
    389 		commit_info_free(ris[i].ci);
    390 		git_reference_free(ris[i].ref);
    391 	}
    392 	free(ris);
    393 
    394 	return -1;
    395 }
    396 
    397 /* Percent-encode, see RFC3986 section 2.1. */
    398 void
    399 put_percent_char(FILE *fp, unsigned char uc)
    400 {
    401 	/* NOTE: do not encode '/' for paths or ",-." */
    402 	if (
    403 		uc < ',' ||
    404 		uc >= 127 ||
    405 		(uc >= ':' && uc <= '@') ||
    406 		uc == '[' ||
    407 		uc == ']'
    408 	) {
    409 		CPUT(fp, '%');
    410 		CPUT(fp, tab[(uc >> 4) & 0x0f]);
    411 		CPUT(fp, tab[uc & 0x0f]);
    412 		return;
    413 	}
    414 
    415 	CPUT(fp, uc);
    416 }
    417 
    418 void
    419 put_percent_encoded(FILE *fp, const char *s, size_t len)
    420 {
    421 	size_t i;
    422 
    423 	for (i = 0; *s && i < len; s++, i++)
    424 		put_percent_char(fp, *s);
    425 }
    426 
    427 /* Escape characters below as HTML 2.0 / XML 1.0. */
    428 void
    429 put_xml_char(FILE *fp, const char *s)
    430 {
    431 	switch(*s) {
    432 		case '<': fputs("&lt;", fp); break;
    433 		case '>': fputs("&gt;", fp); break;
    434 		case '\'': fputs("&#39;", fp); break;
    435 		case '&': fputs("&amp;", fp); break;
    436 		case '"': fputs("&quot;", fp); break;
    437 		default: putc(*s, fp);
    438 	}
    439 }
    440 
    441 void
    442 put_xml_len(FILE *fp, const char *s, size_t len)
    443 {
    444 	size_t i = 0;
    445 	for (; *s && i < len; s++, i++)
    446 		put_xml_char(fp, s);
    447 }
    448 
    449 /* terminate on, but don't print '\r' or '\n' */
    450 void
    451 put_xml_line(FILE *fp, const char *s, size_t len)
    452 {
    453 	size_t i = 0;
    454 	for (; *s && i < len; s++, i++) {
    455 		if (*s == '\r' || *s == '\n')
    456 			break;
    457 		put_xml_char(fp, s);
    458 	}
    459 }
    460 
    461 void
    462 printtimez(FILE *fp, const git_time *intime)
    463 {
    464 	struct tm *intm;
    465 	time_t t;
    466 	char out[32];
    467 
    468 	t = (time_t)intime->time;
    469 	if (!(intm = gmtime(&t)))
    470 		return;
    471 	strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm);
    472 	fputs(out, fp);
    473 }
    474 
    475 void
    476 printtime(FILE *fp, const git_time *intime)
    477 {
    478 	struct tm *intm;
    479 	time_t t;
    480 	char out[32];
    481 
    482 	t = (time_t)intime->time + (intime->offset * 60);
    483 	if (!(intm = gmtime(&t)))
    484 		return;
    485 	strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm);
    486 	if (intime->offset < 0) {
    487 		fprintf(
    488 			fp,
    489 			"%s -%02d%02d",
    490 			out,
    491 			-(intime->offset) / 60,
    492 			-(intime->offset) % 60
    493 		);
    494 	} else {
    495 		fprintf(
    496 			fp,
    497 			"%s +%02d%02d", out,
    498 			intime->offset / 60,
    499 			intime->offset % 60
    500 		);
    501 	}
    502 }
    503 
    504 void
    505 printtimeshort(FILE *fp, const git_time *intime)
    506 {
    507 	struct tm *intm;
    508 	time_t t;
    509 	char out[32];
    510 
    511 	t = (time_t)intime->time;
    512 	if (!(intm = gmtime(&t)))
    513 		return;
    514 	strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
    515 	fputs(out, fp);
    516 }
    517 
    518 void
    519 put_absolute_path(FILE *fp, const char * path)
    520 {
    521 	if (opt.base_url[0] != '\0')
    522 		fprintf(fp, "/%s", opt.base_url);
    523 	CPUT(fp, '/');
    524 	fprintf(fp, PAGE_DEST); /* should have no leading/trailing slashes */
    525 	fprintf(fp, "/%s", path);
    526 }
    527 
    528 void
    529 put_header(FILE *fp, const char *title)
    530 {
    531 	fputs(
    532 		"<!DOCTYPE html>\n"
    533 		"<html>" "\n"
    534 		"<head>" "\n"
    535 		"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
    536 		"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
    537 		"<title>",
    538 		fp
    539 	);
    540 	put_xml(fp, title);
    541 	if (title[0] && data->repo_name[0])
    542 		fputs(" - ", fp);
    543 	put_xml(fp, data->repo_name);
    544 	if (data->description[0])
    545 		fputs(" - ", fp);
    546 	put_xml(fp, data->description);
    547 	SPUTF(fp, "</title>" "\n");
    548 
    549 	SPUTF(fp, "<link rel=\"icon\" type=\"image/png\" href=\"");
    550 	put_absolute_path(fp, "favicon.png");
    551 	SPUTF(fp, "\" />" "\n");
    552 
    553 	SPUTF(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"");
    554 	put_xml(fp, data->name);
    555 	SPUTF(fp, " Atom Feed\" href=\"");
    556 	put_absolute_path(fp, "atom.xml");
    557 	SPUTF(fp, "\" />" "\n");
    558 
    559 	SPUTF(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"");
    560 	put_xml(fp, data->name);
    561 	SPUTF(fp, " Atom Feed (tags)\" href=\"");
    562 	put_absolute_path(fp, "tags.xml");
    563 	SPUTF(fp, "\" />" "\n");
    564 
    565 	SPUTF(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"");
    566 	fprintf(fp, STYLE_HREF);
    567 	SPUTF(fp, "\" />" "\n");
    568 	SPUTF(fp, "</head>" "\n" "<body>" "\n");
    569 	
    570 	fprintf(fp, PAGE_HEADER);
    571 
    572 	SPUTF(fp, "<table>" "<tr>" "<td><h1>");
    573 	put_xml(fp, data->repo_name);
    574 	SPUTF(fp, "</h1></td>" "<td><span class=\"desc\">");
    575 	put_xml(fp, data->description);
    576 	SPUTF(fp, "</span></td>" "</tr>");
    577 	if (data->url[0]) {
    578 		SPUTF(fp, "<tr class=\"url\">" "<td></td>" "<td>");
    579 		fprintf(fp, PAGE_ACCESS_LABEL);
    580 		SPUTF(fp, ": <a href=\"");
    581 		put_xml(fp, data->url); /* not percent encoded */
    582 		SPUTF(fp, "\">");
    583 		put_xml(fp, data->url);
    584 		SPUTF(fp, "</a></td></tr>");
    585 	}
    586 	SPUTF(fp, "<tr>" "<td></td>" "<td>" "\n");
    587 
    588 	if (data->readme) {
    589 		SPUTF(fp, "<a href=\"");
    590 		put_absolute_path(fp, "files/");
    591 		fprintf(fp, "%s.html", data->readme);
    592 		SPUTF(fp, "\">README</a> | ");
    593 	}
    594 	
    595 	SPUTF(fp, "<a href=\"");
    596 	put_absolute_path(fp, "files.html");
    597 	SPUTF(fp, "\">Files</a> | ");
    598 
    599 	SPUTF(fp, "<a href=\"");
    600 	put_absolute_path(fp, "log.html");
    601 	SPUTF(fp, "\">Log</a> | ");
    602 
    603 	SPUTF(fp, "<a href=\"");
    604 	put_absolute_path(fp, "refs.html");
    605 	SPUTF(fp, "\">Refs</a>");
    606 
    607 	if (data->submodules) {
    608 		SPUTF(fp, " | <a href=\"");
    609 		put_absolute_path(fp, "files/");
    610 		fprintf(fp, "%s.html", data->submodules);
    611 		SPUTF(fp, "\">Submodules</a>");
    612 	}
    613 
    614 	if (data->license) {
    615 		SPUTF(fp, " | <a href=\"");
    616 		put_absolute_path(fp, "files/");
    617 		fprintf(fp, "%s.html", data->license);
    618 		SPUTF(fp, "\">LICENSE</a>");
    619 	}
    620 
    621 	SPUTF(fp, "</td></tr></table>" "\n");
    622 	SPUTF(fp, "<hr/>" "\n");
    623 
    624 	SPUTF(fp, "<div id=\"content\">" "\n");
    625 }
    626 
    627 void
    628 put_footer(FILE *fp)
    629 {
    630 	SPUTF(fp, "</div>" "\n");
    631 	fprintf(fp, PAGE_FOOTER);
    632 	SPUTF(fp, "</body>" "\n" "</html>" "\n");
    633 }
    634 
    635 size_t
    636 put_file_html(FILE *fp, const git_blob *blob)
    637 {
    638 	size_t n = 0, i, len, prev;
    639 	const char *number_format =
    640 		"<a href=\"#l%zu\" class=\"line\" id=\"l%zu\">%7zu </a>";
    641 	const char *s = git_blob_rawcontent(blob);
    642 
    643 	len = git_blob_rawsize(blob);
    644 	SPUTF(fp, "<pre id=\"blob\">" "\n");
    645 
    646 	if (len > 0) {
    647 		for (i = 0, prev = 0; i < len; i++) {
    648 			if (s[i] != '\n')
    649 				continue;
    650 			n++;
    651 			fprintf(fp, number_format, n, n, n);
    652 			put_xml_line(fp, &s[prev], i - prev + 1);
    653 			SPUTF(fp, "\n");
    654 			prev = i + 1;
    655 		}
    656 		/* trailing data */
    657 		if ((len - prev) > 0) {
    658 			n++;
    659 			fprintf(fp, number_format, n, n, n);
    660 			put_xml_line(fp, &s[prev], len - prev);
    661 		}
    662 	}
    663 
    664 	SPUTF(fp, "</pre>" "\n");
    665 
    666 	return n;
    667 }
    668 
    669 void
    670 print_commit(FILE *fp, struct CommitInfo *ci)
    671 {
    672 	SPUTF(fp, "<b>commit</b> <a href=\"");
    673 	put_absolute_path(fp, "commits/");
    674 	fprintf(fp, "%s.html", ci->oid);
    675 	fprintf(fp, "\">%s</a>" "\n", ci->oid);
    676 
    677 	if (ci->parentoid[0]) {
    678 		SPUTF(fp, "<b>parent</b> <a href=\"");
    679 		put_absolute_path(fp, "commits/");
    680 		fprintf(fp, "%s.html", ci->parentoid);
    681 		fprintf(fp, "\">%s</a>" "\n", ci->parentoid);
    682 	}
    683 
    684 	if (ci->author) {
    685 		SPUTF(fp, "<b>Author:</b> ");
    686 		put_xml(fp, ci->author->name);
    687 		SPUTF(fp, " &lt;<a href=\"mailto:");
    688 		put_xml(fp, ci->author->email); /* not percent encoded */
    689 		SPUTF(fp, "\">");
    690 		put_xml(fp, ci->author->email);
    691 		SPUTF(fp, "</a>&gt;\n<b>Date:</b>   ");
    692 		printtime(fp, &(ci->author->when));
    693 		CPUT(fp, '\n');
    694 	}
    695 	if (ci->msg) {
    696 		CPUT(fp, '\n');
    697 		put_xml(fp, ci->msg);
    698 		CPUT(fp, '\n');
    699 	}
    700 }
    701 
    702 void
    703 put_show_file(FILE *fp, struct CommitInfo *ci)
    704 {
    705 	const git_diff_delta *delta;
    706 	const git_diff_hunk *hunk;
    707 	const git_diff_line *line;
    708 	git_patch *patch;
    709 	size_t nhunks, nhunklines, changed, add, del, total, i, j, k;
    710 	char linestr[80];
    711 	int c;
    712 
    713 	print_commit(fp, ci);
    714 
    715 	if (!ci->deltas)
    716 		return;
    717 
    718 	if (
    719 		ci->file_count > 1000 ||
    720 		ci->ndeltas > 1000 ||
    721 		ci->add_count > 100000 ||
    722 		ci->del_count > 100000
    723 	) {
    724 		fputs("Diff is too large, output suppressed.\n", fp);
    725 		return;
    726 	}
    727 
    728 	/* diff stat */
    729 	fputs("<b>Diffstat:</b>" "\n", fp);
    730 	fputs("<table id=\"diffstat\">", fp);
    731 	for (i = 0; i < ci->ndeltas; i++) {
    732 		delta = git_patch_get_delta(ci->deltas[i]->patch);
    733 
    734 		switch (delta->status) {
    735 		case GIT_DELTA_ADDED: c = 'A'; break;
    736 		case GIT_DELTA_COPIED: c = 'C'; break;
    737 		case GIT_DELTA_DELETED: c = 'D'; break;
    738 		case GIT_DELTA_MODIFIED: c = 'M'; break;
    739 		case GIT_DELTA_RENAMED: c = 'R'; break;
    740 		case GIT_DELTA_TYPECHANGE: c = 'T'; break;
    741 		default: c = ' '; break;
    742 		}
    743 		if (c == ' ')
    744 			fprintf(fp, "<tr><td>%c", c);
    745 		else
    746 			fprintf(fp, "<tr><td class=\"%c\">%c", c, c);
    747 
    748 		fprintf(fp, "</td><td><a href=\"#h%zu\">", i);
    749 		put_xml(fp, delta->old_file.path);
    750 		if (strcmp(delta->old_file.path, delta->new_file.path)) {
    751 			fputs(" -&gt; ", fp);
    752 			put_xml(fp, delta->new_file.path);
    753 		}
    754 
    755 		add = ci->deltas[i]->add_count;
    756 		del = ci->deltas[i]->del_count;
    757 		changed = add + del;
    758 		total = sizeof(linestr) - 2;
    759 		if (changed > total) {
    760 			if (add)
    761 				add = ((float)total / changed * add) + 1;
    762 			if (del)
    763 				del = ((float)total / changed * del) + 1;
    764 		}
    765 		memset(&linestr, '+', add);
    766 		memset(&linestr[add], '-', del);
    767 
    768 		fprintf(
    769 			fp,
    770 			"</a></td>"
    771 			// "<td> | </td>"
    772 			"<td class=\"num\">%zu</td><td><span class=\"i\">",
    773 			ci->deltas[i]->add_count + ci->deltas[i]->del_count
    774 		);
    775 		fwrite(&linestr, 1, add, fp);
    776 		fputs("</span><span class=\"d\">", fp);
    777 		fwrite(&linestr[add], 1, del, fp);
    778 		fputs("</span></td></tr>\n", fp);
    779 	}
    780 	fprintf(
    781 		fp,
    782 		"</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
    783 		ci->file_count, ci->file_count == 1 ? "" : "s",
    784 		ci->add_count, ci->add_count == 1 ? "" : "s",
    785 		ci->del_count, ci->del_count == 1 ? "" : "s"
    786 	);
    787 
    788 	fputs("<hr/>", fp);
    789 
    790 	for (i = 0; i < ci->ndeltas; i++) {
    791 		patch = ci->deltas[i]->patch;
    792 		delta = git_patch_get_delta(patch);
    793 
    794 		SPUTF(fp, "<b>diff --git a/");
    795 		put_xml(fp, delta->old_file.path);
    796 		fprintf(fp, " b/<a id=\"h%zu\" href=\"#h%zu\">", i, i);
    797 		put_xml(fp, delta->new_file.path);
    798 		SPUTF(fp, "</a></b>" "\n");
    799 
    800 		/* check binary data */
    801 		if (delta->flags & GIT_DIFF_FLAG_BINARY) {
    802 			SPUTF(fp, "Binary files differ." "\n");
    803 			continue;
    804 		}
    805 
    806 		nhunks = git_patch_num_hunks(patch);
    807 		for (j = 0; j < nhunks; j++) {
    808 			if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
    809 				break;
    810 
    811 			fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j);
    812 			put_xml_len(fp, hunk->header, hunk->header_len);
    813 			SPUTF(fp, "</a>");
    814 
    815 			for (k = 0; ; k++) {
    816 				if (git_patch_get_line_in_hunk(&line, patch, j, k))
    817 					break;
    818 				if (line->old_lineno == -1)
    819 					fprintf(
    820 						fp,
    821 						"<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+",
    822 						i, j, k, i, j, k
    823 					);
    824 				else if (line->new_lineno == -1)
    825 					fprintf(
    826 						fp,
    827 						"<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-",
    828 						i, j, k, i, j, k
    829 					);
    830 				else
    831 					CPUT(fp, ' ');
    832 				put_xml_line(fp, line->content, line->content_len);
    833 				CPUT(fp, '\n');
    834 				if (line->old_lineno == -1 || line->new_lineno == -1)
    835 					SPUTF(fp, "</a>");
    836 			}
    837 		}
    838 	}
    839 }
    840 
    841 void
    842 put_log_line(FILE *fp, struct CommitInfo *ci)
    843 {
    844 	fputs("<tr><td>", fp);
    845 	if (ci->author)
    846 		printtimeshort(fp, &(ci->author->when));
    847 	fputs("</td><td>", fp);
    848 	if (ci->summary) {
    849 		SPUTF(fp, "<a href=\"");
    850 		put_absolute_path(fp, "commits/");
    851 		fprintf(fp, "%s.html", ci->oid);
    852 		SPUTF(fp, "\">");
    853 		put_xml(fp, ci->summary);
    854 		SPUTF(fp, "</a>");
    855 	}
    856 	SPUTF(fp, "</td><td>");
    857 	if (ci->author) put_xml(fp, ci->author->name);
    858 	SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
    859 	fprintf(fp, "%zu", ci->file_count);
    860 	SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
    861 	fprintf(fp, "+%zu", ci->add_count);
    862 	SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
    863 	fprintf(fp, "-%zu", ci->del_count);
    864 	SPUTF(fp, "</td></tr>" "\n");
    865 }
    866 
    867 void
    868 write_commit_page(char *name, struct CommitInfo *ci)
    869 {
    870 	char fname[PATH_MAX];
    871 	get_page_path(fname, sizeof(fname), opt.out_dir, name);
    872 	FILE *fp = fopen_w(fname);
    873 	// [old] fpfile = efopen(path, "w");
    874 
    875 	put_header(fp, ci->summary);
    876 
    877 	SPUTF(fp, "<pre>");
    878 	put_show_file(fp, ci);
    879 	SPUTF(fp, "</pre>" "\n");
    880 
    881 	put_footer(fp);
    882 	check_file_error(fp, fname, 'w');
    883 	fclose(fp);
    884 }
    885 
    886 int
    887 put_log(FILE *fp, const git_oid *oid)
    888 {
    889 	struct CommitInfo *ci;
    890 	git_revwalk *w = NULL;
    891 	git_oid id;
    892 	char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
    893 	size_t remcommits = 0;
    894 	int r;
    895 	int commits_left = MAX_COMMITS;
    896 
    897 	git_revwalk_new(&w, data->repo);
    898 	git_revwalk_push(w, oid);
    899 
    900 	// TODO avoid processing very large commits
    901 	while (!git_revwalk_next(&id, w)) {
    902 		git_oid_tostr(oidstr, sizeof(oidstr), &id);
    903 		r = snprintf(path, sizeof(path), "commits/%s.html", oidstr);
    904 		if (r < 0 || (size_t)r >= sizeof(path))
    905 			errx(1, "path truncated: 'commits/%s.html'", oidstr);
    906 		r = access(path, F_OK);
    907 
    908 		/* optimization: if there are no log lines to write and
    909 		   the commit file already exists: skip the diffstat */
    910 		if (!commits_left) {
    911 			remcommits++;
    912 			if (!r)
    913 				continue;
    914 		}
    915 
    916 		if (!(ci = commit_info_get_by_oid(&id)))
    917 			break;
    918 		/* diffstat: for stagit HTML required for the log.html line */
    919 		if (commit_info_get_stats(ci) == -1)
    920 			goto err;
    921 
    922 		if (commits_left != 0) {
    923 			put_log_line(fp, ci);
    924 			if (commits_left > 0)
    925 				commits_left--;
    926 		}
    927 
    928 		/* check if file exists if so skip it */
    929 		if (r) write_commit_page(path, ci);
    930 
    931 err:
    932 		commit_info_free(ci);
    933 	}
    934 	git_revwalk_free(w);
    935 
    936 	if (commits_left == 0 && remcommits != 0)
    937 		fprintf(
    938 			fp,
    939 			"<tr><td></td><td colspan=\"5\">"
    940 			"%zu more commits remaining, fetch the repository"
    941 			"</td></tr>" "\n",
    942 			remcommits
    943 		);
    944 
    945 	return 0;
    946 }
    947 
    948 void
    949 put_log_html(FILE *fp)
    950 {
    951 	put_header(fp, "Log");
    952 	fputs(
    953 		"<table id=\"log\"><thead>" "\n"
    954 		"<tr>"
    955 		"<td><b>Date</b></td>"
    956 		"<td><b>Commit message</b></td>"
    957 		"<td><b>Author</b></td>"
    958 		"<td class=\"num\" align=\"right\"><b>Files</b></td>"
    959 		"<td class=\"num\" align=\"right\"><b>+</b></td>"
    960 		"<td class=\"num\" align=\"right\"><b>-</b></td>"
    961 		"</tr>" "\n"
    962 		"</thead><tbody>" "\n",
    963 		fp
    964 	);
    965 
    966 	if (data->head) put_log(fp, data->head);
    967 
    968 	fputs("</tbody></table>", fp);
    969 	put_footer(fp);
    970 }
    971 
    972 void
    973 put_repo_index_html(FILE *fp)
    974 {
    975 	put_header(fp, "Repository Index");
    976 
    977 	fprintf(fp, REPO_INDEX_CONTENTS);
    978 }
    979 
    980 
    981 void
    982 print_commit_atom(FILE *fp, struct CommitInfo *ci, const char *tag)
    983 {
    984 	fputs("<entry>\n", fp);
    985 
    986 	fprintf(fp, "<id>%s</id>\n", ci->oid);
    987 	if (ci->author) {
    988 		SPUTF(fp, "<published>");
    989 		printtimez(fp, &(ci->author->when));
    990 		SPUTF(fp, "</published>\n");
    991 	}
    992 	if (ci->committer) {
    993 		SPUTF(fp, "<updated>");
    994 		printtimez(fp, &(ci->committer->when));
    995 		SPUTF(fp, "</updated>\n");
    996 	}
    997 	if (ci->summary) {
    998 		SPUTF(fp, "<title>");
    999 		if (tag && tag[0]) {
   1000 			SPUTF(fp, "[");
   1001 			put_xml(fp, tag);
   1002 			SPUTF(fp, "] ");
   1003 		}
   1004 		put_xml(fp, ci->summary);
   1005 		SPUTF(fp, "</title>\n");
   1006 	}
   1007 	SPUTF(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"");
   1008 	put_absolute_path(fp, "commits/");
   1009 	fprintf(fp, "%s.html\" />" "\n", ci->oid);
   1010 
   1011 	if (ci->author) {
   1012 		SPUTF(fp, "<author>" "\n" "<name>");
   1013 		put_xml(fp, ci->author->name);
   1014 		SPUTF(fp, "</name>" "\n" "<email>");
   1015 		put_xml(fp, ci->author->email);
   1016 		SPUTF(fp, "</email>" "\n" "</author>" "\n");
   1017 	}
   1018 
   1019 	SPUTF(fp, "<content>");
   1020 	fprintf(fp, "commit %s" "\n", ci->oid);
   1021 	if (ci->parentoid[0])
   1022 		fprintf(fp, "parent %s" "\n", ci->parentoid);
   1023 	if (ci->author) {
   1024 		SPUTF(fp, "Author: ");
   1025 		put_xml(fp, ci->author->name);
   1026 		SPUTF(fp, " &lt;");
   1027 		put_xml(fp, ci->author->email);
   1028 		SPUTF(fp, "&gt;" "\n" "Date:   ");
   1029 		printtime(fp, &(ci->author->when));
   1030 		CPUT(fp, '\n');
   1031 	}
   1032 	if (ci->msg) {
   1033 		CPUT(fp, '\n');
   1034 		put_xml(fp, ci->msg);
   1035 	}
   1036 	SPUTF(fp, "\n" "</content>" "\n" "</entry>" "\n");
   1037 }
   1038 
   1039 int
   1040 write_atom(FILE *fp, int all)
   1041 {
   1042 	struct ReferenceInfo *ris = NULL;
   1043 	size_t refcount = 0;
   1044 	struct CommitInfo *ci;
   1045 	git_revwalk *w = NULL;
   1046 	git_oid id;
   1047 	size_t i, m = 100; /* last 'm' commits */
   1048 
   1049 	SPUTF(
   1050 		fp,
   1051 		"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "\n"
   1052 		"<feed xmlns=\"http://www.w3.org/2005/Atom\">" "\n" "<title>"
   1053 	);
   1054 	put_xml(fp, data->repo_name);
   1055 	SPUTF(fp, ", branch HEAD</title>" "\n" "<subtitle>");
   1056 	put_xml(fp, data->description);
   1057 	SPUTF(fp, "</subtitle>" "\n");
   1058 
   1059 	/* all commits or only tags? */
   1060 	if (all) {
   1061 		git_revwalk_new(&w, data->repo);
   1062 		git_revwalk_push_head(w);
   1063 		for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
   1064 			if (!(ci = commit_info_get_by_oid(&id)))
   1065 				break;
   1066 			print_commit_atom(fp, ci, "");
   1067 			commit_info_free(ci);
   1068 		}
   1069 		git_revwalk_free(w);
   1070 	} else if (get_refs(&ris, &refcount) != -1) {
   1071 		/* references: tags */
   1072 		for (i = 0; i < refcount; i++) {
   1073 			if (git_reference_is_tag(ris[i].ref))
   1074 				print_commit_atom(
   1075 					fp, ris[i].ci,
   1076 					git_reference_shorthand(ris[i].ref)
   1077 				);
   1078 
   1079 			commit_info_free(ris[i].ci);
   1080 			git_reference_free(ris[i].ref);
   1081 		}
   1082 		free(ris);
   1083 	}
   1084 
   1085 	fputs("</feed>\n", fp);
   1086 
   1087 	return 0;
   1088 }
   1089 
   1090 void
   1091 write_binary_file(const char *filename, git_blob *blob) {
   1092 	char dest[PATH_MAX];
   1093 	const char *raw = git_blob_rawcontent(blob);
   1094 	int size = git_blob_rawsize(blob);
   1095 
   1096 	/* set dest */
   1097 	join_path(dest, PATH_MAX, "files", filename);
   1098 
   1099 	/* write the file! */
   1100 	char fname[PATH_MAX];
   1101 	get_page_path(fname, sizeof(fname), opt.out_dir, dest);
   1102 	FILE *fp = fopen_w(fname);
   1103 
   1104 	fwrite(raw, size, 1, fp);
   1105 	check_file_error(fp, fname, 'w');
   1106 	fclose(fp);
   1107 }
   1108 
   1109 void
   1110 put_size(FILE *fp, size_t size, int fixed)
   1111 {
   1112 	int order = 0;
   1113 	int decimal;
   1114 
   1115 	if (size == 0) {
   1116 		fprintf(fp, "  - ");
   1117 		return;
   1118 	}
   1119 
   1120 	while (size > 1000) {
   1121 		order++;
   1122 		decimal = (size / 100) % 10;
   1123 		size /= 1000;
   1124 	}
   1125 
   1126 	if (size < 10 && order > 0) fprintf(fp, "%1zu.%1d", size, decimal);
   1127 	else fprintf(fp, fixed ? "%3zu" : "%zu", size);
   1128 
   1129 	if (order != 0 || fixed) fprintf(fp, "%c", orders[order]);
   1130 }
   1131 
   1132 void
   1133 put_file_title(FILE *fp, const char *name, int is_dir)
   1134 {
   1135 	int i = 0;
   1136 	int len = 0;
   1137 
   1138 	/* project root */
   1139 	if (name[0] == '\0')
   1140 		return;
   1141 
   1142 	if (!DIR_PAGES) {
   1143 		fprintf(fp, "<p>%s</p>", name);
   1144 		return;
   1145 	}
   1146 
   1147 	if (!is_dir) {
   1148 		for (i = 0; name[i] != '\0'; i++)
   1149 			if (name[i] == '/') len = i;	
   1150 
   1151 		if (len != 0) {
   1152 			SPUTF(fp, "<p><a href=\"");
   1153 			put_absolute_path(fp, "files/");
   1154 			for (i = 0; i < len; i++) put_percent_char(fp, name[i]);
   1155 			SPUTF(fp, ".html\">");
   1156 			for (i = 0; i < len; i++) put_percent_char(fp, name[i]);
   1157 			fprintf(fp, "</a>%s</p>", name + len);
   1158 			return;
   1159 		}
   1160 	}
   1161 
   1162 
   1163 	fprintf(fp, "<p>%s</p>", name);
   1164 }
   1165 
   1166 void
   1167 put_binary_blob_stub(FILE *fp, const char *file_url)
   1168 {
   1169 	SPUTF(fp, "<p>Binary file: <a href=\"");
   1170 	put_absolute_path(fp, "files/");
   1171 	fprintf(fp, "%s\">raw</a></p>" "\n", file_url);
   1172 	SPUTF(fp, "<object data=\"");
   1173 	put_absolute_path(fp, "files/");
   1174 	fprintf(fp, "%s\">", file_url);
   1175 	SPUTF(fp, "no preview available.</object></p>" "\n");
   1176 }
   1177 
   1178 struct Weight
   1179 write_blob(
   1180 	git_blob *blob,
   1181 	char *filename,
   1182 	const char *entrypath,
   1183 	const char *name
   1184 ) {
   1185 	char fname[PATH_MAX];
   1186 	FILE *fp;
   1187 	struct Weight ret;
   1188 
   1189 	ret.lines = 0;
   1190 	ret.bytes = 0;
   1191 
   1192 	get_page_path(fname, sizeof(fname), opt.out_dir, filename);
   1193 	fp = fopen_w(fname);
   1194 	
   1195 	put_header(fp, name);
   1196 	put_file_title(fp, entrypath, 0);
   1197 
   1198 	if (git_blob_is_binary(blob)) {
   1199 		put_binary_blob_stub(fp, entrypath); // name);
   1200 		write_binary_file(entrypath, blob);
   1201 		ret.bytes = git_blob_rawsize(blob);
   1202 	} else {
   1203 		ret.lines = put_file_html(fp, blob);
   1204 	}
   1205 
   1206 	put_footer(fp);
   1207 	check_file_error(fp, fname, 'w');
   1208 	fclose(fp);
   1209 
   1210 	return ret;
   1211 }
   1212 
   1213 char
   1214 get_filemode_type(git_filemode_t m)
   1215 {
   1216 	if (S_ISREG(m)) return '-';
   1217 	if (S_ISBLK(m)) return 'b';
   1218 	if (S_ISCHR(m)) return 'c';
   1219 	if (S_ISDIR(m)) return 'd';
   1220 	if (S_ISFIFO(m)) return 'p';
   1221 	if (S_ISLNK(m)) return 'l';
   1222 	if (S_ISSOCK(m)) return 's';
   1223 	return '?';
   1224 }
   1225 
   1226 const char *
   1227 get_filemode(const git_tree_entry *entry)
   1228 {
   1229 	git_filemode_t mode;
   1230 	static char ret[11];
   1231 
   1232 	/* used for backlink: .. */
   1233 	if (entry == NULL) return "d---------";
   1234 
   1235 	mode = git_tree_entry_filemode(entry);
   1236 
   1237 	memset(ret, '-', sizeof(ret) - 1);
   1238 	ret[0] = get_filemode_type(mode);
   1239 	if (mode & S_IRUSR) ret[1] = 'r';
   1240 	if (mode & S_IWUSR) ret[2] = 'w';
   1241 	if (mode & S_IXUSR) ret[3] = 'x';
   1242 	if (mode & S_ISUID) ret[3] = (ret[3] == 'x') ? 's' : 'S';
   1243 	if (mode & S_IRGRP) ret[4] = 'r';
   1244 	if (mode & S_IWGRP) ret[5] = 'w';
   1245 	if (mode & S_IXGRP) ret[6] = 'x';
   1246 	if (mode & S_ISGID) ret[6] = (ret[6] == 'x') ? 's' : 'S';
   1247 	if (mode & S_IROTH) ret[7] = 'r';
   1248 	if (mode & S_IWOTH) ret[8] = 'w';
   1249 	if (mode & S_IXOTH) ret[9] = 'x';
   1250 	if (mode & S_ISVTX) ret[9] = (ret[9] == 'x') ? 't' : 'T';
   1251 	ret[10] = '\0';
   1252 
   1253 	return ret;
   1254 }
   1255 
   1256 void
   1257 put_filetree_file_line(
   1258 	FILE *fp,
   1259 	git_object *obj,
   1260 	const git_tree_entry *entry,
   1261 	char *filename,
   1262 	char *entrypath,
   1263 	const char *name,
   1264 	struct Weight weight
   1265 ) {
   1266 	SPUTF(fp, "<tr><td>");
   1267 	SPUTF(fp, get_filemode(entry));
   1268 	SPUTF(fp, "</td><td><a href=\"");
   1269 	put_absolute_path(fp, "");
   1270 	put_percent_encoded(fp, filename, strlen(filename));
   1271 	SPUTF(fp, "\">");
   1272 	if (DIR_PAGES) put_xml(fp, name);
   1273 	else put_xml(fp, entrypath);
   1274 	SPUTF(fp, "</a></td><td class=\"num\" align=\"right\">");
   1275 	put_size(fp, weight.lines, 1);
   1276 	SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
   1277 	put_size(fp, weight.bytes, 1);
   1278 	SPUTF(fp, "</td></tr>\n");
   1279 }
   1280 
   1281 void
   1282 put_filetree_dir_line(
   1283 	FILE *fp,
   1284 	const git_tree_entry *entry,
   1285 	char *destination,
   1286 	const char *title,
   1287 	size_t lines,
   1288 	size_t bytes
   1289 ) {
   1290 	SPUTF(fp, "<tr><td>");
   1291 	SPUTF(fp, get_filemode(entry));
   1292 	SPUTF(fp, "</td><td><a href=\"");
   1293 	put_absolute_path(fp, "");
   1294 	put_percent_encoded(fp, destination, strlen(destination));
   1295 	SPUTF(fp, "\">");
   1296 	put_xml(fp, title);
   1297 	SPUTF(fp, "</a></td><td class=\"num\" align=\"right\">");
   1298 	put_size(fp, lines, 1);
   1299 	SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
   1300 	put_size(fp, bytes, 1);
   1301 	SPUTF(fp, "</td></tr>\n");
   1302 }
   1303 
   1304 void put_filetree_backlink_line(FILE *fp, const char *path) {
   1305 	char previous_path[PATH_MAX];
   1306 	char previous_filename[PATH_MAX];
   1307 	int i, r, last_slash = 0;
   1308 	
   1309 	/* figure out previous directory */
   1310 	// TODO how to do this more cleanly...
   1311 	for (i = 0; path[i] != '\0'; i++) {
   1312 		previous_path[i] = path[i];
   1313 		if (path[i] == '/') last_slash = i;
   1314 	}
   1315 	previous_path[last_slash] = '\0';
   1316 
   1317 	if (last_slash == 0) {
   1318 		put_filetree_dir_line(fp, NULL, "files.html", "..", 0, 0);
   1319 	} else {
   1320 		r = snprintf(previous_filename, sizeof(previous_filename), "files/%s.html", previous_path);
   1321 		if (r < 0 || (size_t) r >= sizeof(previous_filename))
   1322 			errx(1, "path truncated: 'files/%s.html'", previous_path);
   1323 		put_filetree_dir_line(fp, NULL, previous_filename, "..", 0, 0);
   1324 	}
   1325 }
   1326 
   1327 /* need this here to allow recursion */
   1328 struct Weight write_filetree(
   1329 	const char *path,
   1330 	const char *filename,
   1331 	git_tree *tree
   1332 );
   1333 struct Weight put_file_list(FILE *fp, git_tree *tree, const char *path);
   1334 
   1335 struct Weight
   1336 put_entry_obj(FILE *fp, git_object *obj, const git_tree_entry *entry, char *entrypath, const char *name)
   1337 {
   1338 	struct Weight ret;
   1339 	git_object_t obj_type = git_object_type(obj);
   1340 	char filename[PATH_MAX];
   1341 	int r;
   1342 	
   1343 	/* filename = f("files/%s.html", entrypath) */
   1344 	r = snprintf(filename, sizeof(filename), "files/%s.html", entrypath);
   1345 	if (r < 0 || (size_t) r >= sizeof(filename))
   1346 		errx(1, "path truncated: 'files/%s.html'", entrypath);
   1347 
   1348 	switch (obj_type) {
   1349 	case GIT_OBJ_TREE:
   1350 		if (DIR_PAGES) {
   1351 			/* create dir page */
   1352 			ret = write_filetree(entrypath, filename, (git_tree *) obj);
   1353 			
   1354 			/* add link to that dir page */
   1355 			put_filetree_dir_line(fp, entry, filename, name, ret.lines, ret.bytes);
   1356 		} else {
   1357 			/* list files on this page */
   1358 			ret = put_file_list(fp, (git_tree *) obj, entrypath);
   1359 		}
   1360 		break;
   1361 	case GIT_OBJ_BLOB:
   1362 		ret = write_blob((git_blob *) obj, filename, entrypath, name);
   1363 		put_filetree_file_line(fp, obj, entry, filename, entrypath, name, ret);
   1364 		break;
   1365 	default: break;
   1366 	}
   1367 
   1368 	return ret;
   1369 }
   1370 
   1371 void
   1372 put_submodule_obj(FILE *fp, const git_tree_entry *entry, const char *name)
   1373 {
   1374 	char oid[8];
   1375 
   1376 	SPUTF(fp, "<tr>" "<td>m---------</td>" "<td><a href=\"");
   1377 	put_absolute_path(fp, "files/.gitmodules.html");
   1378 	SPUTF(fp, "\">");
   1379 	put_xml(fp, name);
   1380 	SPUTF(fp, "</a> @ ");
   1381 	git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry));
   1382 	put_xml(fp, oid);
   1383 	SPUTF(fp, "</td>" "<td class=\"num\" align=\"right\"></td>" "\n");
   1384 	SPUTF(fp, "<td class=\"num\" align=\"right\"></td>" "</tr>" "\n");
   1385 }
   1386 
   1387 struct Weight
   1388 put_file_list(FILE *fp, git_tree *tree, const char *path)
   1389 {
   1390 	struct Weight ret;
   1391 	size_t count;
   1392 	size_t i;
   1393 
   1394 	ret.lines = ret.bytes = 0;
   1395 
   1396 	count = git_tree_entrycount(tree);
   1397 	for (i = 0; i < count; i++) {
   1398 		const git_tree_entry *entry = NULL;
   1399 		const char *name;
   1400 		char entrypath[PATH_MAX];
   1401 		git_object *obj = NULL;
   1402 
   1403 		if (
   1404 			!(entry = git_tree_entry_byindex(tree, i)) ||
   1405 			!(name = git_tree_entry_name(entry))
   1406 		)
   1407 			return ret;
   1408 
   1409 		combine_paths(entrypath, path, name);
   1410 
   1411 		if (!git_tree_entry_to_object(&obj, data->repo, entry)) {
   1412 			struct Weight weight =
   1413 				put_entry_obj(fp, obj, entry, entrypath, name);
   1414 			ret.bytes += weight.bytes;
   1415 			ret.lines += weight.lines;
   1416 			git_object_free(obj);
   1417 			continue;
   1418 		}
   1419 
   1420 		if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) {
   1421 			put_submodule_obj(fp, entry, name);
   1422 			continue;
   1423 		}
   1424 	}
   1425 
   1426 	return ret;
   1427 }
   1428 
   1429 struct Weight
   1430 write_filetree(const char *path, const char *filename, git_tree *tree)
   1431 {
   1432 	struct Weight ret;
   1433 	char fname[PATH_MAX] = "";
   1434 	FILE *fp;
   1435 
   1436 	get_page_path(fname, sizeof(fname), opt.out_dir, (char *) filename);
   1437 	fp = fopen_w(fname);
   1438 
   1439 	put_header(fp, "Files");
   1440 	
   1441 	put_file_title(fp, path, 1);
   1442 
   1443 	SPUTF(
   1444 		fp,
   1445 		"<table id=\"files\"><thead>" "\n" "<tr>"
   1446 		"<td><b>Mode</b></td><td><b>Name</b></td>"
   1447 		"<td class=\"num\" align=\"right\"><b>  L </b></td>"
   1448 		"<td class=\"num\" align=\"right\"><b>  B </b></td>"
   1449 		"</tr>" "\n" "</thead><tbody>" "\n"
   1450 	);
   1451 
   1452 	if (tree != NULL) {
   1453 		if (path[0] != '\0')
   1454 			put_filetree_backlink_line(fp, path);
   1455 
   1456 		ret = put_file_list(fp, tree, path);
   1457 	}
   1458 
   1459 	SPUTF(fp, "</tbody></table>");
   1460 
   1461 	// TODO how can this get printed before the contents...
   1462 	/*
   1463 	SPUTF(fp, "<p>Totals: ");
   1464 	put_size(fp, ret.lines, 0);
   1465 	SPUTF(fp, "L ");
   1466 	put_size(fp, ret.bytes, 0);
   1467 	SPUTF(fp, "B</p>");
   1468 	*/
   1469 
   1470 	put_footer(fp);
   1471 	check_file_error(fp, fname, 'w');
   1472 	fclose(fp);
   1473 
   1474 	return ret;
   1475 }
   1476 
   1477 void
   1478 walk_git_tree()
   1479 {
   1480 	git_tree *tree = NULL;
   1481 	git_commit *commit = NULL;
   1482 	int has_files;
   1483 	
   1484 	if (!data->head) return;
   1485 
   1486 	has_files = !git_commit_lookup(&commit, data->repo, data->head) &&
   1487 		!git_commit_tree(&tree, commit);
   1488 
   1489 	write_filetree("", "files.html", has_files ? tree : NULL);
   1490 
   1491 	git_commit_free(commit);
   1492 	git_tree_free(tree);
   1493 }
   1494 
   1495 int
   1496 put_refs(FILE *fp)
   1497 {
   1498 	struct ReferenceInfo *ris = NULL;
   1499 	struct CommitInfo *ci;
   1500 	size_t count, i, j, refcount;
   1501 	const char *titles[] = { "Branches", "Tags" };
   1502 	const char *ids[] = { "branches", "tags" };
   1503 	const char *s;
   1504 
   1505 	if (get_refs(&ris, &refcount) == -1)
   1506 		return -1;
   1507 
   1508 	for (i = 0, j = 0, count = 0; i < refcount; i++) {
   1509 		if (j == 0 && git_reference_is_tag(ris[i].ref)) {
   1510 			if (count)
   1511 				fputs("</tbody></table><br/>\n", fp);
   1512 			count = 0;
   1513 			j = 1;
   1514 		}
   1515 
   1516 		/* print header if it has an entry (first). */
   1517 		if (++count == 1)
   1518 			fprintf(
   1519 				fp,
   1520 				"<h2>%s</h2><table id=\"%s\">"
   1521 				"<thead>\n<tr><td><b>Name</b></td>"
   1522 				"<td><b>Last commit date</b></td>"
   1523 				"<td><b>Author</b></td>\n</tr>\n"
   1524 				"</thead><tbody>\n",
   1525 				titles[j],
   1526 				ids[j]
   1527 			);
   1528 
   1529 		ci = ris[i].ci;
   1530 		s = git_reference_shorthand(ris[i].ref);
   1531 
   1532 		fputs("<tr><td>", fp);
   1533 		put_xml(fp, s);
   1534 		fputs("</td><td>", fp);
   1535 		if (ci->author)
   1536 			printtimeshort(fp, &(ci->author->when));
   1537 		fputs("</td><td>", fp);
   1538 		if (ci->author)
   1539 			put_xml(fp, ci->author->name);
   1540 		fputs("</td></tr>\n", fp);
   1541 	}
   1542 
   1543 	if (count)
   1544 		fputs("</tbody></table><br/>\n", fp);
   1545 
   1546 	for (i = 0; i < refcount; i++) {
   1547 		commit_info_free(ris[i].ci);
   1548 		git_reference_free(ris[i].ref);
   1549 	}
   1550 	free(ris);
   1551 
   1552 	return 0;
   1553 }
   1554 
   1555 void
   1556 write_repo_index_page()
   1557 {
   1558 	FILE *fp;
   1559 	char fname[PATH_MAX];
   1560 
   1561 	get_page_path(fname, sizeof(fname), opt.out_dir, "index.html");
   1562 	fp = fopen_w(fname);
   1563 
   1564 	put_repo_index_html(fp);
   1565 
   1566 	check_file_error(fp, fname, 'w');
   1567 	fclose(fp);
   1568 }
   1569 
   1570 void
   1571 write_log_page()
   1572 {
   1573 	FILE *fp;
   1574 	char fname[PATH_MAX];
   1575 
   1576 	get_page_path(fname, sizeof(fname), opt.out_dir, "log.html");
   1577 	fp = fopen_w(fname);
   1578 
   1579 	put_log_html(fp);
   1580 
   1581 	check_file_error(fp, fname, 'w');
   1582 	fclose(fp);
   1583 }
   1584 
   1585 void
   1586 write_refs_page()
   1587 {
   1588 	FILE *fp;
   1589 	char fname[PATH_MAX];
   1590 
   1591 	get_page_path(fname, sizeof(fname), opt.out_dir, "refs.html");
   1592 	fp = fopen_w(fname);
   1593 
   1594 	put_header(fp, "Refs");
   1595 	put_refs(fp);
   1596 	put_footer(fp);
   1597 
   1598 	check_file_error(fp, fname, 'w');
   1599 	fclose(fp);
   1600 }
   1601 
   1602 void
   1603 write_feed()
   1604 {
   1605 	FILE *fp;
   1606 	char fname[PATH_MAX];
   1607 
   1608 	get_page_path(fname, sizeof(fname), opt.out_dir, "atom.xml");
   1609 	fp = fopen_w(fname);
   1610 
   1611 	write_atom(fp, 1);
   1612 
   1613 	check_file_error(fp, fname, 'w');
   1614 	fclose(fp);
   1615 }
   1616 
   1617 void
   1618 write_releases_feed()
   1619 {
   1620 	FILE *fp;
   1621 	char fname[PATH_MAX];
   1622 
   1623 	get_page_path(fname, sizeof(fname), opt.out_dir, "tags.xml");
   1624 	fp = fopen_w(fname);
   1625 
   1626 	write_atom(fp, 0);
   1627 
   1628 	check_file_error(fp, fname, 'w');
   1629 	fclose(fp);
   1630 }
   1631 
   1632 int
   1633 write_repo_pages(const char* dir)
   1634 {
   1635 	data = alloc_repo_data(dir);
   1636 	if (data == NULL) return 1;
   1637 
   1638 	if (opt.index) {
   1639 		put_index_log(index_fp, data);
   1640 	}
   1641 
   1642 	if (opt.pages) {
   1643 		write_repo_index_page(data);
   1644 		write_log_page(data);
   1645 		walk_git_tree(data);
   1646 		write_refs_page(data);
   1647 		write_feed(data);
   1648 		write_releases_feed(data);
   1649 	}
   1650 
   1651 	free_repo_data(data);
   1652 
   1653 	return 0;
   1654 }
   1655 
   1656 void
   1657 begin_index()
   1658 {
   1659 	printf("Writing index page..." "\n");
   1660 	get_index_page_path(
   1661 		index_fname,
   1662 		sizeof(index_fname),
   1663 		opt.out_dir,
   1664 		INDEX_DEST
   1665 	);
   1666 	index_fp = fopen_w(index_fname);
   1667 	put_index_header(index_fp);
   1668 }
   1669 
   1670 void
   1671 end_index()
   1672 {
   1673 	put_index_footer(index_fp);	
   1674 	check_file_error(index_fp, index_fname, 'w');
   1675 	fclose(index_fp);
   1676 }
   1677 
   1678 void
   1679 init_lib_git()
   1680 {
   1681 	int i;
   1682 
   1683 	/* do not search outside the git repository:
   1684 	   GIT_CONFIG_LEVEL_APP is the highest level currently */
   1685 	git_libgit2_init();
   1686 	for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
   1687 		git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
   1688 
   1689 	/* do not require the git repository to be owned by the current user */
   1690 	git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
   1691 }
   1692 
   1693 int
   1694 main(int argc, const char *argv[])
   1695 {
   1696 	int i, result;
   1697 
   1698 	if (parse_opts(&opt, argc, argv) == OPT_FAIL) {
   1699 		PRINT_USAGE(argv[0]);
   1700 		return 1;
   1701 	}
   1702 
   1703 	if (!opt.pages && !opt.index) {
   1704 		printf("Nothing to do, use -i and/or -p." "\n\n");
   1705 		PRINT_USAGE(argv[0]);
   1706 		return 1;
   1707 	}
   1708 
   1709 	printf("Opening repository..." "\n");
   1710 	init_lib_git();
   1711 
   1712 	/* actually write all the pages! */
   1713 	if (opt.index) begin_index();
   1714 	printf("Writing repositories..." "\n");
   1715 	for (i = 0; i < opt.repo_count; i++) {
   1716 		printf("%d/%d...", i+1, opt.repo_count); fflush(stdout);
   1717 		result = write_repo_pages(opt.repo_dirs[i]);
   1718 		if(result > 0) return 1;
   1719 	}
   1720 	printf("\n");
   1721 	if (opt.index) end_index();
   1722 
   1723 	git_libgit2_shutdown();
   1724 	printf("All done!" "\n");
   1725 
   1726 	return 0;
   1727 }

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