#include <sys/stat.h>
#include <sys/types.h>

#include <err.h>
#include <errno.h>
#include <libgen.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <git2.h>

#ifdef CMARK
#include <cmark.h>
#endif

#include "config.h"
#include "strlcpy.c"

#define T(S) (fputs((S), fp))

struct deltainfo {
	git_patch *patch;

	size_t addcount;
	size_t delcount;
};

struct commitinfo {
	git_commit *commit;

	char oid[GIT_OID_HEXSZ + 1];
	char parentoid[GIT_OID_HEXSZ + 1];

	const git_signature *author;
	const git_signature *committer;
	const char *summary;
	const char *msg;
};

struct commitstats {
	size_t addcount;
	size_t delcount;
	size_t ndeltas;
	struct deltainfo **deltas;
};

static git_repository *repo;

static const char *repodir;

/* reponame is a pointer into repodirabs */
char repodirabs[PATH_MAX + 1];
static char *reponame = "";
static char description[255];
static char url[255];

void
joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
{
	int r;

	r = snprintf(buf, bufsiz, "%s%s%s",
		path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
	if (r < 0 || (size_t)r >= bufsiz)
		errx(1, "path truncated: '%s%s%s'",
			path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
}

int
mkdirp(const char *path)
{
	char tmp[PATH_MAX], *p;

	if (strlcpy(tmp, path, sizeof(tmp)) >= sizeof(tmp))
		errx(1, "path truncated: '%s'", path);
	for (p = tmp + (tmp[0] == '/'); *p; p++) {
		if (*p != '/')
			continue;
		*p = '\0';
		if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
			return -1;
		*p = '/';
	}
	if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
		return -1;
	return 0;
}

FILE *
efopen(const char *name, const char *flags)
{
	FILE *fp;

	if (!(fp = fopen(name, flags)))
		err(1, "fopen: '%s'", name);

	return fp;
}

void
deltainfo_free(struct deltainfo *di)
{
	if (!di)
		return;
	git_patch_free(di->patch);
	memset(di, 0, sizeof(*di));
	free(di);
}

void
commitstats_free(struct commitstats *cs)
{
	size_t i;
	if (!cs)
		return;
	for (i = 0; i < cs->ndeltas; i++)
		deltainfo_free(cs->deltas[i]);
	free(cs->deltas);
	memset(cs, 0, sizeof(*cs));
	free(cs);
}

void
commitinfo_free(struct commitinfo *ci)
{
	if (!ci)
		return;
	git_commit_free(ci->commit);
	memset(ci, 0, sizeof(*ci));
	free(ci);
}

struct commitinfo *
commitinfo_getbyoid(const git_oid *id)
{
	struct commitinfo *ci;

	if (!(ci = calloc(1, sizeof(struct commitinfo))))
		err(1, "calloc");

	if (git_commit_lookup(&(ci->commit), repo, id))
		goto err;

	git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
	git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));

	ci->author = git_commit_author(ci->commit);
	ci->committer = git_commit_committer(ci->commit);
	ci->summary = git_commit_summary(ci->commit);
	ci->msg = git_commit_message(ci->commit);

	return ci;

err:
	commitinfo_free(ci);
	return NULL;
}

int
git_commit_get_diff(git_diff **diff, git_commit *commit)
{
	git_commit *parent;
	git_tree *commit_tree;
	git_tree *parent_tree;
	git_diff_options opts;
	git_diff_find_options fopts;

	if (git_tree_lookup(&commit_tree, repo, git_commit_tree_id(commit)))
		goto err;
	if (git_commit_parent(&parent, commit, 0)) {
		parent = NULL;
	}
	if (parent && git_tree_lookup(&parent_tree, repo, git_commit_tree_id(parent))) {
		parent_tree = NULL;
	}

	if (git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION))
		goto err;
	opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH | GIT_DIFF_INCLUDE_TYPECHANGE;
	if (git_diff_tree_to_tree(diff, repo, parent_tree, commit_tree, &opts))
		goto err;

	git_tree_free(commit_tree);
	git_tree_free(parent_tree);
	git_commit_free(parent);

	if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION))
		goto err;
	fopts.flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
	if (git_diff_find_similar(*diff, &fopts)) {
		git_diff_free(*diff);
		goto err;
	}

	return 0;

err:
	git_tree_free(commit_tree);
	git_tree_free(parent_tree);
	git_commit_free(parent);
	return -1;
}

struct commitstats *
commitinfo_getstats(git_diff *diff)
{
	struct commitstats *cs;
	struct deltainfo *di;
	const git_diff_delta *delta;
	const git_diff_hunk *hunk;
	const git_diff_line *line;
	git_patch *patch = NULL;
	size_t ndeltas, nhunks, nhunklines, i, j, k;

	if (!(cs = calloc(1, sizeof(struct commitstats))))
		err(1, "calloc");

	ndeltas = git_diff_num_deltas(diff);
	if (ndeltas && !(cs->deltas = calloc(ndeltas, sizeof(struct deltainfo *))))
		err(1, "calloc");

	for (i = 0; i < ndeltas; i++) {
		if (git_patch_from_diff(&patch, diff, i))
			break;

		if (!(di = calloc(1, sizeof(struct deltainfo))))
			err(1, "calloc");
		di->patch = patch;
		cs->deltas[i] = di;

		delta = git_patch_get_delta(patch);

		/* skip stats for binary data */
		if (delta->flags & GIT_DIFF_FLAG_BINARY)
			continue;

		nhunks = git_patch_num_hunks(patch);
		for (j = 0; j < nhunks; j++) {
			if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
				break;
			for (k = 0; ; k++) {
				if (git_patch_get_line_in_hunk(&line, patch, j, k))
					break;
				if (line->old_lineno == -1) {
					di->addcount++;
					cs->addcount++;
				} else if (line->new_lineno == -1) {
					di->delcount++;
					cs->delcount++;
				}
			}
		}
	}
	cs->ndeltas = i;

	return cs;
}

void
xmlencode(FILE *fp, const char *s, size_t len)
{
	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:   fputc(*s, fp);
		}
	}
}

void
printtimez(FILE *fp, const git_time *intime)
{
	struct tm *intm;
	time_t t;
	char out[32];

	t = (time_t)intime->time;
	if (!(intm = gmtime(&t)))
		return;
	strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm);
	fputs(out, fp);
}

void
printtimeshort(FILE *fp, const git_time *intime)
{
	struct tm *intm;
	time_t t;
	char out[32];

	t = (time_t)intime->time;
	if (!(intm = gmtime(&t)))
		return;
	strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
	fputs(out, fp);
}

void
write_header(FILE *fp, const char *title, const char *relpath)
{
	T("<!DOCTYPE html>\n");
	T("<html>\n");
	T("<head>\n");
	T("<meta charset=\"UTF-8\">\n");
	T("<meta name=\"viewport\" content=\"width=device-width\">\n");
	T("<title>{reponame}");
	if (description[0]) {
		T(": {description}");
	}
	if (title[0]) {
		T(" - {title}");
	}
	T("</title>\n");
	if (favicon[0])
		T("<link rel=\"icon\" type=\"image/png\" href=\"{favicon:%s}\" />\n");
	T("<link rel=\"alternate\" type=\"application/atom+xml\" title=\"{reponame} Feed\" href=\"{relpath:%s}atom.xml\" />\n");
	if (stylesheet[0])
		T("<link rel=\"stylesheet\" type=\"text/css\" href=\"{stylesheet:%s}\" />\n");
	T("</head>\n");
	T("<body>\n");
	T("<header>\n");
	if (logo[0])
		T("<a href=\"{relpath:%s}..\"><img src=\"{logo:%s}\" alt=\"\" width=\"32\" height=\"32\" /></a>");
	T("<div>\n");
	T("<h1>{reponame}</h1>");
	if (description[0] || url[0]) {
		T("<div class=\"desc\">{description}");
		if (description[0] && url[0])
			T("&nbsp; ");
		if (url[0]) {
			T("<a href=\"{url}\">{url}</a>");
		}
		T("</div>");
	}
	if (cloneurl[0]) {
		T("<div class=\"cloneurl\">git clone {cloneurl}{reponame}.git</div>\n");
	}
	T("<nav>\n");
	T("<a href=\"{relpath:%s}.\">Files</a> |\n");
	T("<a href=\"{relpath:%s}log.html\">Log</a>\n");
	T("</nav>\n");
	T("</div>\n");
	T("</header>\n");
	T("<hr/>\n");
	T("<main id=\"content\">\n");
}

void
write_footer(FILE *fp)
{
	T("</main>\n</body>\n</html>\n");
}

void
write_readme(FILE *fp, const git_blob *blob)
{
	const void *raw = git_blob_rawcontent(blob);
	git_off_t len = git_blob_rawsize(blob);

#ifdef CMARK
	char *rendered = cmark_markdown_to_html(raw, len, CMARK_OPT_SAFE);
	T("<div id=\"readme\">\n");
	fputs(rendered, fp);
	T("</div>\n");
	free(rendered);
#else
	T("<pre id=\"readme\">\n");
	fwrite(raw, 1, len, fp);
	T("</pre>\n");
#endif
}

void
write_commit_statline(FILE *fp, struct deltainfo *di, size_t i)
{
	char c;
	int total;
	size_t j;
	const git_diff_delta *delta;

	delta = git_patch_get_delta(di->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_TYPECHANGE: c = 'T'; break;
	default:                   c = ' '; break;
	}

	T("<tr>\n");
	T("<td class=\"{c:%c}\">{c:%c}</td>\n");
	T("<td><a href=\"#h{i:%zu}\">");
	T("{delta->old_file.path}");
	if (strcmp(delta->old_file.path, delta->new_file.path)) {
		T(" -&gt; {delta->new_file.path}");
	}
	T("</a></td>\n");

	total = di->addcount + di->delcount;
	T("<td class=\"num\">{total:%i}</td>\n");
	T("<td><span class=\"i\">");
	for (j = 0; j < di->addcount && j * total < di->addcount * 60; j++)
		fputc('+', fp);
	T("</span><span class=\"d\">");
	for (j = 0; j < di->delcount && j * total < di->delcount * 60; j++)
		fputc('-', fp);
	T("</span></td>");

	T("</tr>\n");
}

void
write_commit_file(FILE *fp, struct deltainfo *di, size_t i)
{
	const git_diff_delta *delta;
	const git_diff_hunk *hunk;
	const git_diff_line *line;
	size_t nhunks, nhunklines, j, k;
	const char *tag;

	delta = git_patch_get_delta(di->patch);
	nhunks = git_patch_num_hunks(di->patch);

	T("<h2 id=\"h{i:%zu}\">\n");
	T("diff --git\n");
	T("a/<a href=\"../blob/{delta->old_file.path}\" rel=\"nofollow\">{delta->old_file.path}</a>\n");
	T("b/<a href=\"../blob/{delta->new_file.path}\" rel=\"nofollow\">{delta->new_file.path}</a>\n");
	T("</h2>\n");

	if (delta->flags & GIT_DIFF_FLAG_BINARY) {
		T("Binary files differ.\n");
		return;
	}

	if (nhunks == 0)
		return;

	T("<pre class=\"diff\">\n");
	for (j = 0; j < nhunks; j++) {
		if (git_patch_get_hunk(&hunk, &nhunklines, di->patch, j))
			break;
		T("<a href=\"#h{i:%zu}-{j:%zu}\" id=\"h{i:%zu}-{j:%zu}\" class=\"h\">");
		T("{hunk->header:hunk->header_len - 1}");
		T("</a>\n");
		for (k = 0; k < nhunklines; k++) {
			if (git_patch_get_line_in_hunk(&line, di->patch, j, k))
				break;
			tag = line->old_lineno == -1 ? "ins" : line->new_lineno == -1 ? "del" : "span";
			T("<{tag:%s} class=\"line\" id=\"h{i:%zu}-{j:%zu}-{k:%zu}\">");
			T("<a href=\"#h{i:%zu}-{j:%zu}-{k:%zu}\" tabindex=\"-1\">{line->old_lineno:%5i} {line->new_lineno:%5i}</a> ");
			T("{line->content:line->content_len - 1}");
			T("</{tag:%s}>\n");
		}
	}
	T("</pre>\n");
}

void
write_commit(FILE *fp, struct commitinfo *ci, git_diff *diff)
{
	size_t i;
	struct commitstats *cs = commitinfo_getstats(diff);

	T("<dl>\n");
	T("<dt>commit</dt><dd><a href=\"\">{ci->oid:%s}</a></dd>\n");
	if (ci->parentoid[0])
		T("<dt>parent</dt><dd><a href=\"{ci->parentoid:%s}.html\">{ci->parentoid:%s}</a></dd>\n");
	if (ci->author) {
		T("<dt>Author</dt><dd>{ci->author->name} ");
		T("&lt;<a href=\"mailto:{ci->author->email}\">{ci->author->email}</a>&gt;");
		T("</dd>\n<dt>Date</dt><dd>");
		printtimeshort(fp, &(ci->author->when));
		T("</dd>\n");
	}
	T("</dl>\n");
	if (ci->msg) {
		T("<pre>{ci->msg}</pre>\n");
	}

	if (!cs->deltas)
		goto err;

	if (
			cs->ndeltas > 1000 ||
			cs->addcount > 100000 ||
			cs->delcount > 100000
		) {
		T("Diff is too large, output suppressed.\n");
		goto err;
	}

	T("<h2>Diffstat</h2>\n");
	T("<table>\n");

	for (i = 0; i < cs->ndeltas; i++) {
		write_commit_statline(fp, cs->deltas[i], i);
	}

	T("</table>\n");
	T("<p>{cs->ndeltas:%zu} files changed, {cs->addcount:%zu} insertions, {cs->delcount:%zu} deletions</p>\n");
	T("<hr/>\n");

	for (i = 0; i < cs->ndeltas; i++) {
		write_commit_file(fp, cs->deltas[i], i);
	}

err:
	commitstats_free(cs);
}

void
write_diff(FILE *fp, git_diff *diff)
{
	git_buf buf = GIT_BUF_INIT;
	git_diff_to_buf(&buf, diff, GIT_DIFF_FORMAT_PATCH);
	fprintf(fp, "%s", buf.ptr);
}

void
write_log_header(FILE *fp)
{
	T("<table id=\"log\">\n");
	T("<thead>\n");
	T("<tr><th>Date</th><th class=\"text\">Commit message</th><th>Author</th></tr>\n");
	T("</thead>\n");
	T("<tbody>\n");
}

void
write_log_line(FILE *fp, struct commitinfo *ci)
{
	T("<tr><td>");
	if (ci->author) {
		T("<a href=\"commit/{ci->oid:%s}.html\">");
		printtimeshort(fp, &(ci->author->when));
		T("</a>");
	}
	T("</td><td class=\"text\">");
	if (ci->summary)
		T("{ci->summary}");
	T("</td><td>");
	if (ci->author)
		T("{ci->author->name}");
	T("</td></tr>\n");
}

void
write_log_footer(FILE *fp)
{
	T("</tbody>\n</table>\n");
}

void
write_atom_header(FILE *fp)
{
	T("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
	T("<feed xmlns=\"http://www.w3.org/2005/Atom\">\n");
	T("<title>{reponame}, branch HEAD</title>\n");
	T("<subtitle>{description}</subtitle>\n");
}

void
write_atom_entry(FILE *fp, struct commitinfo *ci)
{
	T("<entry>\n");

	T("<id>{ci->oid:%s}</id>\n");
	if (ci->author) {
		T("<published>");
		printtimez(fp, &(ci->author->when));
		T("</published>\n");
	}
	if (ci->committer) {
		T("<updated>");
		printtimez(fp, &(ci->committer->when));
		T("</updated>\n");
	}
	if (ci->summary) {
		T("<title type=\"text\">{ci->summary}</title>\n");
	}
	T("<link rel=\"alternate\" type=\"text/html\" href=\"commit/{ci->oid:%s}.html\" />\n");

	if (ci->author) {
		T("<author>\n");
		T("<name>{ci->author->name}</name>\n");
		T("<email>{ci->author->email}</email>\n");
		T("</author>\n");
	}

	if (ci->msg) {
		T("<content type=\"text\">{ci->msg}</content>\n");
	}
	T("</entry>\n");
}

void
write_atom_footer(FILE *fp)
{
	T("</feed>\n");
}

void
copy_blob(git_object *obj, const char *fpath)
{
	char tmp[PATH_MAX] = "";
	char *d;
	git_off_t len;
	const void *raw;
	FILE *fp;

	if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
		errx(1, "path truncated: '%s'", fpath);

	if (!(d = dirname(tmp)))
		err(1, "dirname");
	if (mkdirp(d))
		return;

	fp = efopen(fpath, "w");
	len = git_blob_rawsize((git_blob *)obj);
	raw = git_blob_rawcontent((git_blob *)obj);
	fwrite(raw, 1, len, fp);
	fclose(fp);
}

void
write_files_header(FILE *fp)
{
	T("<table id=\"files\">\n");
	T("<thead>\n");
	T("<tr><th>Name</th><th class=\"num\">Size</th></tr>\n");
	T("</thead>\n");
	T("<tbody>\n");
}

void
write_files_line(FILE *fp, char *entrypath, char *filepath, uintmax_t size)
{
	T("<tr><td><a href=\"{filepath}\">{entrypath}</a></td><td class=\"num\">{size:%ju}B</td></tr>\n");
}

void
write_files_footer(FILE *fp)
{
	T("</tbody>\n</table>\n");
}

void
process_readme(FILE *fp)
{
	int i;
	git_object *obj = NULL;

	for (i = 0; i < sizeof(readmefiles) / sizeof(*readmefiles); i++) {
		if (
			!git_revparse_single(&obj, repo, readmefiles[i]) &&
			git_object_type(obj) == GIT_OBJ_BLOB
		) {
			write_readme(fp, (git_blob *)obj);
			break;
		}
		git_object_free(obj);
	}
}

int
_process_files(FILE *fp, git_tree *tree, const char *path)
{
	const git_tree_entry *entry = NULL;
	git_object *obj = NULL;
	git_off_t filesize;
	const char *entryname;
	char filepath[PATH_MAX], entrypath[PATH_MAX];
	size_t count, i;
	int ret;

	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))
		)
			return -1;
		joinpath(entrypath, sizeof(entrypath), path, entryname);
		joinpath(filepath, sizeof(filepath), "blob", entrypath);

		if (!git_tree_entry_to_object(&obj, repo, entry)) {
			switch (git_object_type(obj)) {
			case GIT_OBJ_BLOB:
				break;
			case GIT_OBJ_TREE:
				/* NOTE: recurses */
				ret = _process_files(fp, (git_tree *)obj, entrypath);
				git_object_free(obj);
				if (ret)
					return ret;
				continue;
			default:
				git_object_free(obj);
				continue;
			}

			copy_blob(obj, filepath);

			filesize = git_blob_rawsize((git_blob *)obj);
			write_files_line(fp, entrypath, filepath, filesize);

			git_object_free(obj);
		}
	}

	return 0;
}

int
process_files(FILE *fp)
{
	git_tree *tree = NULL;
	git_commit *commit = NULL;
	git_object *obj = NULL;
	const git_oid *head = NULL;
	int ret = -1;

	/* find HEAD */
	if (!git_revparse_single(&obj, repo, "HEAD"))
		head = git_object_id(obj);
	git_object_free(obj);
	if (!head) {
		fprintf(stderr, "no HEAD found\n");
		return 1;
	}

	if (!git_commit_lookup(&commit, repo, head) && !git_commit_tree(&tree, commit))
		ret = _process_files(fp, tree, "");

	git_commit_free(commit);
	git_tree_free(tree);

	return ret;
}

void
process_commits(FILE *fp_log, FILE *fp_atom, size_t m)
{
	struct commitinfo *ci;
	git_revwalk *w = NULL;
	git_oid id;
	git_diff *diff;
	FILE *fp_commit;
	char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];

	git_revwalk_new(&w, repo);
	git_revwalk_push_head(w);

	while (!git_revwalk_next(&id, w)) {
		if (m == 0) {
			fputs("<tr><td colspan=\"3\">More commits remaining&hellip;</td></tr>\n", fp_log);
			break;
		}
		if (!(ci = commitinfo_getbyoid(&id)))
			break;

		if (git_commit_get_diff(&diff, ci->commit))
			break;

		write_log_line(fp_log, ci);
		write_atom_entry(fp_atom, ci);

		git_oid_tostr(oidstr, sizeof(oidstr), &id);
		snprintf(path, sizeof(path), "commit/%s.html", oidstr);

		if (force_commits || access(path, F_OK)) {
			mkdirp("commit");
			fp_commit = efopen(path, "w");
			write_header(fp_commit, ci->summary, "../");
			write_commit(fp_commit, ci, diff);
			write_footer(fp_commit);
			fclose(fp_commit);
		}

		snprintf(path, sizeof(path), "commit/%s.diff", oidstr);
		if (force_commits || access(path, F_OK)) {
			fp_commit = efopen(path, "w");
			write_diff(fp_commit, diff);
			fclose(fp_commit);
		}

		m -= 1;
		git_diff_free(diff);
		commitinfo_free(ci);
	}
	git_revwalk_free(w);
}

void
usage(char *argv0)
{
	fprintf(stderr, "%s repodir\n", argv0);
	exit(1);
}

void
get_repo_name(const char *path)
{
	char *p;

	if (!realpath(repodir, repodirabs))
		err(1, "realpath");

	if (!(reponame = strrchr(repodirabs, '/'))) {
		fprintf(stderr, "could not use directory name\n");
		return;
	}
	reponame++;
	if ((p = strrchr(reponame, '.')))
		if (!strcmp(p, ".git"))
			*p = '\0';
}

void
get_metadata(void)
{
	char path[PATH_MAX];
	FILE *fpread;

	joinpath(path, sizeof(path), repodir, "description");
	if ((fpread = fopen(path, "r"))) {
		if (!fgets(description, sizeof(description), fpread))
			description[0] = '\0';
		fclose(fpread);
	}

	joinpath(path, sizeof(path), repodir, "url");
	if ((fpread = fopen(path, "r"))) {
		if (!fgets(url, sizeof(url), fpread))
			url[0] = '\0';
		fclose(fpread);
	}
}

int
main(int argc, char *argv[])
{
	static FILE *fp_index, *fp_log, *fp_atom;

	if (argc == 2)
		repodir = argv[1];
	else
		usage(argv[0]);

	git_libgit2_init();

#ifdef __OpenBSD__
	if (unveil(repodir, "r") == -1)
		err(1, "unveil: %s", repodir);
	if (unveil(".", "rwc") == -1)
		err(1, "unveil: .");

	if (pledge("stdio rpath wpath cpath", NULL) == -1)
		err(1, "pledge");
#endif

	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;
	}

	get_repo_name(repodir);
	get_metadata();

	fp_index = efopen("index.html", "w");
	fp_log = efopen("log.html", "w");
	fp_atom = efopen("atom.xml", "w");

	write_header(fp_index, "", "");
	write_files_header(fp_index);

	write_header(fp_log, "Log", "");
	write_log_header(fp_log);

	write_atom_header(fp_atom);

	process_files(fp_index);
	write_files_footer(fp_index);
	process_readme(fp_index);
	process_commits(fp_log, fp_atom, 100);

	write_atom_footer(fp_atom);

	write_log_footer(fp_log);
	write_footer(fp_log);

	write_footer(fp_index);

	fclose(fp_index);
	fclose(fp_log);
	fclose(fp_atom);

	git_repository_free(repo);
	git_libgit2_shutdown();

	return 0;
}
