stagit

static git page generator  https://git.ce9e.org
git clone https://git.ce9e.org/stagit.git

commit
0b28d0832592ca51a81730779b917f63b42a1642
parent
b24cdfc14a898af0cca18bfb1be1fe825ae480aa
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2020-03-26 16:54
major refactoring

Diffstat

M stagit.c 922 ++++++++++++++++++++++++++++---------------------------------

1 files changed, 417 insertions, 505 deletions


diff --git a/stagit.c b/stagit.c

@@ -28,28 +28,22 @@ struct deltainfo {
   28    28 };
   29    29 
   30    30 struct commitinfo {
   31    -1 	const git_oid *id;
   -1    31 	git_commit *commit;
   32    32 
   33    33 	char oid[GIT_OID_HEXSZ + 1];
   34    34 	char parentoid[GIT_OID_HEXSZ + 1];
   35    35 
   36    36 	const git_signature *author;
   37    37 	const git_signature *committer;
   38    -1 	const char          *summary;
   39    -1 	const char          *msg;
   40    -1 
   41    -1 	git_diff   *diff;
   42    -1 	git_commit *commit;
   43    -1 	git_commit *parent;
   44    -1 	git_tree   *commit_tree;
   45    -1 	git_tree   *parent_tree;
   -1    38 	const char *summary;
   -1    39 	const char *msg;
   -1    40 };
   46    41 
   -1    42 struct commitstats {
   47    43 	size_t addcount;
   48    44 	size_t delcount;
   49    -1 	size_t filecount;
   50    -1 
   51    -1 	struct deltainfo **deltas;
   52    45 	size_t ndeltas;
   -1    46 	struct deltainfo **deltas;
   53    47 };
   54    48 
   55    49 static git_repository *repo;
@@ -57,19 +51,13 @@ static git_repository *repo;
   57    51 static const char *relpath = "";
   58    52 static const char *repodir;
   59    53 
   60    -1 static char *name = "";
   61    -1 static char *strippedname = "";
   -1    54 /* reponame is a pointer into repodirabs */
   -1    55 char repodirabs[PATH_MAX + 1];
   -1    56 static char *reponame = "";
   62    57 static char description[255];
   63    -1 static char cloneurl[1024];
   -1    58 static char *cloneurl = "git@git.example.com:";
   64    59 static char *readmefiles[] = { "HEAD:README", "HEAD:README.md", "HEAD:README.rst" };
   65    -1 static char *readme;
   66    -1 static long long nlogcommits = -1; /* < 0 indicates not used */
   67    -1 
   68    -1 /* cache */
   69    -1 static git_oid lastoid;
   70    -1 static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */
   71    -1 static FILE *rcachefp, *wcachefp;
   72    -1 static const char *cachefile;
   -1    60 static int force = 1;
   73    61 
   74    62 void
   75    63 joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
@@ -125,22 +113,24 @@ deltainfo_free(struct deltainfo *di)
  125   113 }
  126   114 
  127   115 void
  128    -1 commitinfo_free(struct commitinfo *ci)
   -1   116 commitstats_free(struct commitstats *cs)
  129   117 {
  130   118 	size_t i;
   -1   119 	if (!cs)
   -1   120 		return;
   -1   121 	for (i = 0; i < cs->ndeltas; i++)
   -1   122 		deltainfo_free(cs->deltas[i]);
   -1   123 	free(cs->deltas);
   -1   124 	memset(cs, 0, sizeof(*cs));
   -1   125 	free(cs);
   -1   126 }
  131   127 
   -1   128 void
   -1   129 commitinfo_free(struct commitinfo *ci)
   -1   130 {
  132   131 	if (!ci)
  133   132 		return;
  134    -1 	if (ci->deltas)
  135    -1 		for (i = 0; i < ci->ndeltas; i++)
  136    -1 			deltainfo_free(ci->deltas[i]);
  137    -1 
  138    -1 	free(ci->deltas);
  139    -1 	git_diff_free(ci->diff);
  140    -1 	git_tree_free(ci->commit_tree);
  141    -1 	git_tree_free(ci->parent_tree);
  142   133 	git_commit_free(ci->commit);
  143    -1 	git_commit_free(ci->parent);
  144   134 	memset(ci, 0, sizeof(*ci));
  145   135 	free(ci);
  146   136 }
@@ -155,7 +145,6 @@ commitinfo_getbyoid(const git_oid *id)
  155   145 
  156   146 	if (git_commit_lookup(&(ci->commit), repo, id))
  157   147 		goto err;
  158    -1 	ci->id = id;
  159   148 
  160   149 	git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
  161   150 	git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
@@ -169,56 +158,84 @@ commitinfo_getbyoid(const git_oid *id)
  169   158 
  170   159 err:
  171   160 	commitinfo_free(ci);
  172    -1 
  173   161 	return NULL;
  174   162 }
  175   163 
  176   164 int
  177    -1 commitinfo_getstats(struct commitinfo *ci)
   -1   165 git_commit_get_diff(git_diff **diff, git_commit *commit)
  178   166 {
  179    -1 	struct deltainfo *di;
   -1   167 	git_commit *parent;
   -1   168 	git_tree *commit_tree;
   -1   169 	git_tree *parent_tree;
  180   170 	git_diff_options opts;
  181   171 	git_diff_find_options fopts;
  182    -1 	const git_diff_delta *delta;
  183    -1 	const git_diff_hunk *hunk;
  184    -1 	const git_diff_line *line;
  185    -1 	git_patch *patch = NULL;
  186    -1 	size_t ndeltas, nhunks, nhunklines;
  187    -1 	size_t i, j, k;
  188   172 
  189    -1 	if (git_tree_lookup(&(ci->commit_tree), repo, git_commit_tree_id(ci->commit)))
   -1   173 	if (git_tree_lookup(&commit_tree, repo, git_commit_tree_id(commit)))
  190   174 		goto err;
  191    -1 	if (!git_commit_parent(&(ci->parent), ci->commit, 0)) {
  192    -1 		if (git_tree_lookup(&(ci->parent_tree), repo, git_commit_tree_id(ci->parent))) {
  193    -1 			ci->parent = NULL;
  194    -1 			ci->parent_tree = NULL;
   -1   175 	if (!git_commit_parent(&parent, commit, 0)) {
   -1   176 		if (git_tree_lookup(&parent_tree, repo, git_commit_tree_id(parent))) {
   -1   177 			parent = NULL;
   -1   178 			parent_tree = NULL;
  195   179 		}
  196   180 	}
  197   181 
  198    -1 	git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
  199    -1 	opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH |
  200    -1 		      GIT_DIFF_INCLUDE_TYPECHANGE;
  201    -1 	if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts))
   -1   182 	if (git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION))
  202   183 		goto err;
   -1   184 	opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH | GIT_DIFF_INCLUDE_TYPECHANGE;
   -1   185 	if (git_diff_tree_to_tree(diff, repo, parent_tree, commit_tree, &opts))
   -1   186 		goto err;
   -1   187 
   -1   188 	git_tree_free(commit_tree);
   -1   189 	git_tree_free(parent_tree);
   -1   190 	git_commit_free(parent);
  203   191 
  204   192 	if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION))
  205   193 		goto err;
  206   194 	fopts.flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
  207    -1 	if (git_diff_find_similar(ci->diff, &fopts))
   -1   195 	if (git_diff_find_similar(*diff, &fopts)) {
   -1   196 		git_diff_free(*diff);
  208   197 		goto err;
   -1   198 	}
   -1   199 
   -1   200 	return 0;
  209   201 
  210    -1 	ndeltas = git_diff_num_deltas(ci->diff);
  211    -1 	if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *))))
   -1   202 err:
   -1   203 	git_tree_free(commit_tree);
   -1   204 	git_tree_free(parent_tree);
   -1   205 	git_commit_free(parent);
   -1   206 	return -1;
   -1   207 }
   -1   208 
   -1   209 struct commitstats *
   -1   210 commitinfo_getstats(struct commitinfo *ci)
   -1   211 {
   -1   212 	struct commitstats *cs;
   -1   213 	struct deltainfo *di;
   -1   214 	const git_diff_delta *delta;
   -1   215 	const git_diff_hunk *hunk;
   -1   216 	const git_diff_line *line;
   -1   217 	git_diff *diff;
   -1   218 	git_patch *patch = NULL;
   -1   219 	size_t ndeltas, nhunks, nhunklines, i, j, k;
   -1   220 
   -1   221 	if (!(cs = calloc(1, sizeof(struct commitstats))))
   -1   222 		err(1, "calloc");
   -1   223 
   -1   224 	if (git_commit_get_diff(&diff, ci->commit))
   -1   225 		goto err;
   -1   226 
   -1   227 	ndeltas = git_diff_num_deltas(diff);
   -1   228 	if (ndeltas && !(cs->deltas = calloc(ndeltas, sizeof(struct deltainfo *))))
  212   229 		err(1, "calloc");
  213   230 
  214   231 	for (i = 0; i < ndeltas; i++) {
  215    -1 		if (git_patch_from_diff(&patch, ci->diff, i))
  216    -1 			goto err;
   -1   232 		if (git_patch_from_diff(&patch, diff, i))
   -1   233 			break;
  217   234 
  218   235 		if (!(di = calloc(1, sizeof(struct deltainfo))))
  219   236 			err(1, "calloc");
  220   237 		di->patch = patch;
  221    -1 		ci->deltas[i] = di;
   -1   238 		cs->deltas[i] = di;
  222   239 
  223   240 		delta = git_patch_get_delta(patch);
  224   241 
@@ -235,43 +252,22 @@ commitinfo_getstats(struct commitinfo *ci)
  235   252 					break;
  236   253 				if (line->old_lineno == -1) {
  237   254 					di->addcount++;
  238    -1 					ci->addcount++;
   -1   255 					cs->addcount++;
  239   256 				} else if (line->new_lineno == -1) {
  240   257 					di->delcount++;
  241    -1 					ci->delcount++;
   -1   258 					cs->delcount++;
  242   259 				}
  243   260 			}
  244   261 		}
  245   262 	}
  246    -1 	ci->ndeltas = i;
  247    -1 	ci->filecount = i;
   -1   263 	cs->ndeltas = i;
  248   264 
  249    -1 	return 0;
   -1   265 	git_diff_free(diff);
  250   266 
  251   267 err:
  252    -1 	git_diff_free(ci->diff);
  253    -1 	ci->diff = NULL;
  254    -1 	git_tree_free(ci->commit_tree);
  255    -1 	ci->commit_tree = NULL;
  256    -1 	git_tree_free(ci->parent_tree);
  257    -1 	ci->parent_tree = NULL;
  258    -1 	git_commit_free(ci->parent);
  259    -1 	ci->parent = NULL;
  260    -1 
  261    -1 	if (ci->deltas)
  262    -1 		for (i = 0; i < ci->ndeltas; i++)
  263    -1 			deltainfo_free(ci->deltas[i]);
  264    -1 	free(ci->deltas);
  265    -1 	ci->deltas = NULL;
  266    -1 	ci->ndeltas = 0;
  267    -1 	ci->addcount = 0;
  268    -1 	ci->delcount = 0;
  269    -1 	ci->filecount = 0;
  270    -1 
  271    -1 	return -1;
   -1   268 	return cs;
  272   269 }
  273   270 
  274    -1 /* Escape characters below as HTML 2.0 / XML 1.0. */
  275   271 void
  276   272 xmlencode(FILE *fp, const char *s, size_t len)
  277   273 {
@@ -325,21 +321,21 @@ write_header(FILE *fp, const char *title)
  325   321 		"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
  326   322 		"<title>", fp);
  327   323 	xmlencode(fp, title, strlen(title));
  328    -1 	if (title[0] && strippedname[0])
   -1   324 	if (title[0] && reponame[0])
  329   325 		fputs(" - ", fp);
  330    -1 	xmlencode(fp, strippedname, strlen(strippedname));
   -1   326 	xmlencode(fp, reponame, strlen(reponame));
  331   327 	if (description[0])
  332   328 		fputs(" - ", fp);
  333   329 	xmlencode(fp, description, strlen(description));
  334   330 	fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
  335   331 	fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed\" href=\"%satom.xml\" />\n",
  336    -1 		name, relpath);
   -1   332 		reponame, relpath);
  337   333 	fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
  338   334 	fputs("</head>\n<body>\n<table><tr><td>", fp);
  339   335 	fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>",
  340   336 	        relpath, relpath);
  341   337 	fputs("</td><td><h1>", fp);
  342    -1 	xmlencode(fp, strippedname, strlen(strippedname));
   -1   338 	xmlencode(fp, reponame, strlen(reponame));
  343   339 	fputs("</h1><span class=\"desc\">", fp);
  344   340 	xmlencode(fp, description, strlen(description));
  345   341 	fputs("</span></td></tr>", fp);
@@ -382,8 +378,107 @@ write_readme(FILE *fp, const git_blob *blob)
  382   378 }
  383   379 
  384   380 void
   -1   381 write_commit_statline(FILE *fp, struct deltainfo *di, size_t i)
   -1   382 {
   -1   383 	char c;
   -1   384 	int total;
   -1   385 	size_t j;
   -1   386 	const git_diff_delta *delta;
   -1   387 
   -1   388 	delta = git_patch_get_delta(di->patch);
   -1   389 
   -1   390 	switch (delta->status) {
   -1   391 	case GIT_DELTA_ADDED:      c = 'A'; break;
   -1   392 	case GIT_DELTA_COPIED:     c = 'C'; break;
   -1   393 	case GIT_DELTA_DELETED:    c = 'D'; break;
   -1   394 	case GIT_DELTA_MODIFIED:   c = 'M'; break;
   -1   395 	case GIT_DELTA_RENAMED:    c = 'R'; break;
   -1   396 	case GIT_DELTA_TYPECHANGE: c = 'T'; break;
   -1   397 	default:                   c = ' '; break;
   -1   398 	}
   -1   399 
   -1   400 	fputs("<tr>\n", fp);
   -1   401 	fprintf(fp, "<td class=\"%c\">%c</td>\n", c, c);
   -1   402 	fprintf(fp, "<td><a href=\"#h%zu\">", i);
   -1   403 	xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
   -1   404 	if (strcmp(delta->old_file.path, delta->new_file.path)) {
   -1   405 		fputs(" -&gt; ", fp);
   -1   406 		xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
   -1   407 	}
   -1   408 	fputs("</a></td>\n", fp);
   -1   409 
   -1   410 	total = di->addcount + di->delcount;
   -1   411 	fprintf(fp, "<td class=\"num\">%i</td>\n", total);
   -1   412 	fputs("<td><span class=\"i\">", fp);
   -1   413 	for (j = 0; j < di->addcount && j * total < di->addcount * 60; j++)
   -1   414 		fputc('+', fp);
   -1   415 	fputs("</span><span class=\"d\">", fp);
   -1   416 	for (j = 0; j < di->delcount && j * total < di->delcount * 60; j++)
   -1   417 		fputc('-', fp);
   -1   418 
   -1   419 	fputs("</tr>\n", fp);
   -1   420 }
   -1   421 
   -1   422 void
   -1   423 write_commit_file(FILE *fp, struct deltainfo *di, size_t i)
   -1   424 {
   -1   425 	const git_diff_delta *delta;
   -1   426 	const git_diff_hunk *hunk;
   -1   427 	const git_diff_line *line;
   -1   428 	size_t nhunks, nhunklines, j, k;
   -1   429 
   -1   430 	delta = git_patch_get_delta(di->patch);
   -1   431 	nhunks = git_patch_num_hunks(di->patch);
   -1   432 
   -1   433 	fprintf(fp, "<h2 id=\"h%zu\">\n", i);
   -1   434 	fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"%sfile/", i, relpath);
   -1   435 	xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
   -1   436 	fputs(".html\">", fp);
   -1   437 	xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
   -1   438 	fprintf(fp, "</a> b/<a href=\"%sfile/", relpath);
   -1   439 	xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
   -1   440 	fprintf(fp, ".html\">");
   -1   441 	xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
   -1   442 	fprintf(fp, "</a></b>\n");
   -1   443 
   -1   444 	if (delta->flags & GIT_DIFF_FLAG_BINARY) {
   -1   445 		fputs("Binary files differ.\n", fp);
   -1   446 		return;
   -1   447 	}
   -1   448 
   -1   449 	if (nhunks == 0)
   -1   450 		return;
   -1   451 
   -1   452 	for (j = 0; j < nhunks; j++) {
   -1   453 		if (git_patch_get_hunk(&hunk, &nhunklines, di->patch, j))
   -1   454 			break;
   -1   455 		fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j);
   -1   456 		xmlencode(fp, hunk->header, hunk->header_len - 1);
   -1   457 		fputs("</a>\n", fp);
   -1   458 		for (k = 0; k < nhunklines; k++) {
   -1   459 			if (git_patch_get_line_in_hunk(&line, di->patch, j, k))
   -1   460 				break;
   -1   461 			if (line->old_lineno == -1)
   -1   462 				fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+",
   -1   463 					i, j, k, i, j, k);
   -1   464 			else if (line->new_lineno == -1)
   -1   465 				fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-",
   -1   466 					i, j, k, i, j, k);
   -1   467 			else
   -1   468 				fputc(' ', fp);
   -1   469 			xmlencode(fp, line->content, line->content_len);
   -1   470 			if (line->old_lineno == -1 || line->new_lineno == -1)
   -1   471 				fputs("</a>", fp);
   -1   472 		}
   -1   473 	}
   -1   474 }
   -1   475 
   -1   476 void
  385   477 write_commit(FILE *fp, struct commitinfo *ci)
  386   478 {
   -1   479 	size_t i;
   -1   480 	struct commitstats *cs = commitinfo_getstats(ci);
   -1   481 
  387   482 	fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n",
  388   483 		relpath, ci->oid, ci->oid);
  389   484 
@@ -407,130 +502,51 @@ write_commit(FILE *fp, struct commitinfo *ci)
  407   502 		xmlencode(fp, ci->msg, strlen(ci->msg));
  408   503 		fputc('\n', fp);
  409   504 	}
  410    -1 }
  411    -1 
  412    -1 void
  413    -1 write_commit_file(FILE *fp, struct commitinfo *ci)
  414    -1 {
  415    -1 	const git_diff_delta *delta;
  416    -1 	const git_diff_hunk *hunk;
  417    -1 	const git_diff_line *line;
  418    -1 	git_patch *patch;
  419    -1 	size_t nhunks, nhunklines, changed, add, del, total, i, j, k;
  420    -1 	char linestr[80];
  421    -1 	int c;
  422    -1 
  423    -1 	write_commit(fp, ci);
  424   505 
  425    -1 	if (!ci->deltas)
  426    -1 		return;
   -1   506 	if (!cs->deltas)
   -1   507 		goto err;
  427   508 
  428    -1 	if (ci->filecount > 1000   ||
  429    -1 	    ci->ndeltas   > 1000   ||
  430    -1 	    ci->addcount  > 100000 ||
  431    -1 	    ci->delcount  > 100000) {
   -1   509 	if (
   -1   510 			cs->ndeltas > 1000 ||
   -1   511 			cs->addcount > 100000 ||
   -1   512 			cs->delcount > 100000
   -1   513 		) {
  432   514 		fputs("Diff is too large, output suppressed.\n", fp);
  433    -1 		return;
   -1   515 		goto err;
  434   516 	}
  435   517 
  436    -1 	/* diff stat */
  437    -1 	fputs("<b>Diffstat:</b>\n<table>", fp);
  438    -1 	for (i = 0; i < ci->ndeltas; i++) {
  439    -1 		delta = git_patch_get_delta(ci->deltas[i]->patch);
  440    -1 
  441    -1 		switch (delta->status) {
  442    -1 		case GIT_DELTA_ADDED:      c = 'A'; break;
  443    -1 		case GIT_DELTA_COPIED:     c = 'C'; break;
  444    -1 		case GIT_DELTA_DELETED:    c = 'D'; break;
  445    -1 		case GIT_DELTA_MODIFIED:   c = 'M'; break;
  446    -1 		case GIT_DELTA_RENAMED:    c = 'R'; break;
  447    -1 		case GIT_DELTA_TYPECHANGE: c = 'T'; break;
  448    -1 		default:                   c = ' '; break;
  449    -1 		}
  450    -1 		if (c == ' ')
  451    -1 			fprintf(fp, "<tr><td>%c", c);
  452    -1 		else
  453    -1 			fprintf(fp, "<tr><td class=\"%c\">%c", c, c);
  454    -1 
  455    -1 		fprintf(fp, "</td><td><a href=\"#h%zu\">", i);
  456    -1 		xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
  457    -1 		if (strcmp(delta->old_file.path, delta->new_file.path)) {
  458    -1 			fputs(" -&gt; ", fp);
  459    -1 			xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
  460    -1 		}
   -1   518 	fputs("<h2>Diffstat</h2>\n", fp);
   -1   519 	fputs("<table>\n", fp);
  461   520 
  462    -1 		add = ci->deltas[i]->addcount;
  463    -1 		del = ci->deltas[i]->delcount;
  464    -1 		changed = add + del;
  465    -1 		total = sizeof(linestr) - 2;
  466    -1 		if (changed > total) {
  467    -1 			if (add)
  468    -1 				add = ((float)total / changed * add) + 1;
  469    -1 			if (del)
  470    -1 				del = ((float)total / changed * del) + 1;
  471    -1 		}
  472    -1 		memset(&linestr, '+', add);
  473    -1 		memset(&linestr[add], '-', del);
  474    -1 
  475    -1 		fprintf(fp, "</a></td><td> | </td><td class=\"num\">%zu</td><td><span class=\"i\">",
  476    -1 		        ci->deltas[i]->addcount + ci->deltas[i]->delcount);
  477    -1 		fwrite(&linestr, 1, add, fp);
  478    -1 		fputs("</span><span class=\"d\">", fp);
  479    -1 		fwrite(&linestr[add], 1, del, fp);
  480    -1 		fputs("</span></td></tr>\n", fp);
   -1   521 	for (i = 0; i < cs->ndeltas; i++) {
   -1   522 		write_commit_statline(fp, cs->deltas[i], i);
  481   523 	}
  482   524 	fprintf(fp, "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
  483    -1 		ci->filecount, ci->filecount == 1 ? "" : "s",
  484    -1 	        ci->addcount,  ci->addcount  == 1 ? "" : "s",
  485    -1 	        ci->delcount,  ci->delcount  == 1 ? "" : "s");
   -1   525 		cs->ndeltas,  cs->ndeltas  == 1 ? "" : "s",
   -1   526 		cs->addcount, cs->addcount == 1 ? "" : "s",
   -1   527 		cs->delcount, cs->delcount == 1 ? "" : "s");
  486   528 
   -1   529 	fputs("</table>\n", fp);
   -1   530 	fputs("<p>", fp);
   -1   531 	fputs("</p>\n", fp);
  487   532 	fputs("<hr/>", fp);
  488   533 
  489    -1 	for (i = 0; i < ci->ndeltas; i++) {
  490    -1 		patch = ci->deltas[i]->patch;
  491    -1 		delta = git_patch_get_delta(patch);
  492    -1 		fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"%sfile/", i, relpath);
  493    -1 		xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
  494    -1 		fputs(".html\">", fp);
  495    -1 		xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
  496    -1 		fprintf(fp, "</a> b/<a href=\"%sfile/", relpath);
  497    -1 		xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
  498    -1 		fprintf(fp, ".html\">");
  499    -1 		xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
  500    -1 		fprintf(fp, "</a></b>\n");
  501    -1 
  502    -1 		/* check binary data */
  503    -1 		if (delta->flags & GIT_DIFF_FLAG_BINARY) {
  504    -1 			fputs("Binary files differ.\n", fp);
  505    -1 			continue;
  506    -1 		}
  507    -1 
  508    -1 		nhunks = git_patch_num_hunks(patch);
  509    -1 		for (j = 0; j < nhunks; j++) {
  510    -1 			if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
  511    -1 				break;
   -1   534 	for (i = 0; i < cs->ndeltas; i++) {
   -1   535 		write_commit_file(fp, cs->deltas[i], i);
   -1   536 	}
  512   537 
  513    -1 			fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j);
  514    -1 			xmlencode(fp, hunk->header, hunk->header_len);
  515    -1 			fputs("</a>", fp);
   -1   538 err:
   -1   539 	commitstats_free(cs);
   -1   540 }
  516   541 
  517    -1 			for (k = 0; ; k++) {
  518    -1 				if (git_patch_get_line_in_hunk(&line, patch, j, k))
  519    -1 					break;
  520    -1 				if (line->old_lineno == -1)
  521    -1 					fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+",
  522    -1 						i, j, k, i, j, k);
  523    -1 				else if (line->new_lineno == -1)
  524    -1 					fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-",
  525    -1 						i, j, k, i, j, k);
  526    -1 				else
  527    -1 					fputc(' ', fp);
  528    -1 				xmlencode(fp, line->content, line->content_len);
  529    -1 				if (line->old_lineno == -1 || line->new_lineno == -1)
  530    -1 					fputs("</a>", fp);
  531    -1 			}
  532    -1 		}
  533    -1 	}
   -1   542 void
   -1   543 write_log_header(FILE *fp)
   -1   544 {
   -1   545 	fputs("<table id=\"log\">\n", fp);
   -1   546 	fputs("<thead>\n", fp);
   -1   547 	fputs("<tr><th>Date</th><th class=\"text\">Commit message</th><th>Author</th></tr>\n", fp);
   -1   548 	fputs("</thead>\n", fp);
   -1   549 	fputs("<tbody>\n", fp);
  534   550 }
  535   551 
  536   552 void
@@ -548,85 +564,26 @@ write_log_line(FILE *fp, struct commitinfo *ci)
  548   564 	fputs("</td><td>", fp);
  549   565 	if (ci->author)
  550   566 		xmlencode(fp, ci->author->name, strlen(ci->author->name));
  551    -1 	fputs("</td><td class=\"num\" align=\"right\">", fp);
  552    -1 	fprintf(fp, "%zu", ci->filecount);
  553    -1 	fputs("</td><td class=\"num\" align=\"right\">", fp);
  554    -1 	fprintf(fp, "+%zu", ci->addcount);
  555    -1 	fputs("</td><td class=\"num\" align=\"right\">", fp);
  556    -1 	fprintf(fp, "-%zu", ci->delcount);
  557   567 	fputs("</td></tr>\n", fp);
  558   568 }
  559   569 
  560    -1 int
  561    -1 write_log(FILE *fp, const git_oid *oid)
   -1   570 void
   -1   571 write_log_footer(FILE *fp)
  562   572 {
  563    -1 	struct commitinfo *ci;
  564    -1 	git_revwalk *w = NULL;
  565    -1 	git_oid id;
  566    -1 	char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
  567    -1 	FILE *fpfile;
  568    -1 	int r;
  569    -1 
  570    -1 	git_revwalk_new(&w, repo);
  571    -1 	git_revwalk_push(w, oid);
  572    -1 	git_revwalk_simplify_first_parent(w);
  573    -1 
  574    -1 	while (!git_revwalk_next(&id, w)) {
  575    -1 		relpath = "";
  576    -1 
  577    -1 		if (cachefile && !memcmp(&id, &lastoid, sizeof(id)))
  578    -1 			break;
  579    -1 
  580    -1 		git_oid_tostr(oidstr, sizeof(oidstr), &id);
  581    -1 		r = snprintf(path, sizeof(path), "commit/%s.html", oidstr);
  582    -1 		if (r < 0 || (size_t)r >= sizeof(path))
  583    -1 			errx(1, "path truncated: 'commit/%s.html'", oidstr);
  584    -1 		r = access(path, F_OK);
  585    -1 
  586    -1 		/* optimization: if there are no log lines to write and
  587    -1 		   the commit file already exists: skip the diffstat */
  588    -1 		if (!nlogcommits && !r)
  589    -1 			continue;
  590    -1 
  591    -1 		if (!(ci = commitinfo_getbyoid(&id)))
  592    -1 			break;
  593    -1 		/* diffstat: for stagit HTML required for the log.html line */
  594    -1 		if (commitinfo_getstats(ci) == -1)
  595    -1 			goto err;
  596    -1 
  597    -1 		if (nlogcommits < 0) {
  598    -1 			write_log_line(fp, ci);
  599    -1 		} else if (nlogcommits > 0) {
  600    -1 			write_log_line(fp, ci);
  601    -1 			nlogcommits--;
  602    -1 			if (!nlogcommits && ci->parentoid[0])
  603    -1 				fputs("<tr><td></td><td colspan=\"5\">"
  604    -1 				      "More commits remaining [...]</td>"
  605    -1 				      "</tr>\n", fp);
  606    -1 		}
  607    -1 
  608    -1 		if (cachefile)
  609    -1 			write_log_line(wcachefp, ci);
  610    -1 
  611    -1 		/* check if file exists if so skip it */
  612    -1 		if (r) {
  613    -1 			relpath = "../";
  614    -1 			fpfile = efopen(path, "w");
  615    -1 			write_header(fpfile, ci->summary);
  616    -1 			fputs("<pre>", fpfile);
  617    -1 			write_commit_file(fpfile, ci);
  618    -1 			fputs("</pre>\n", fpfile);
  619    -1 			write_footer(fpfile);
  620    -1 			fclose(fpfile);
  621    -1 		}
  622    -1 err:
  623    -1 		commitinfo_free(ci);
  624    -1 	}
  625    -1 	git_revwalk_free(w);
  626    -1 
  627    -1 	relpath = "";
   -1   573 	fputs("</tbody>\n", fp);
   -1   574 	fputs("</table>\n", fp);
   -1   575 }
  628   576 
  629    -1 	return 0;
   -1   577 void
   -1   578 write_atom_header(FILE *fp)
   -1   579 {
   -1   580 	fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", fp);
   -1   581 	fputs("<feed xmlns=\"http://www.w3.org/2005/Atom\">\n", fp);
   -1   582 	fputs("<title>", fp);
   -1   583 	xmlencode(fp, reponame, strlen(reponame));
   -1   584 	fputs(", branch HEAD</title>\n<subtitle>", fp);
   -1   585 	xmlencode(fp, description, strlen(description));
   -1   586 	fputs("</subtitle>\n", fp);
  630   587 }
  631   588 
  632   589 void
@@ -650,8 +607,7 @@ write_atom_entry(FILE *fp, struct commitinfo *ci)
  650   607 		xmlencode(fp, ci->summary, strlen(ci->summary));
  651   608 		fputs("</title>\n", fp);
  652   609 	}
  653    -1 	fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"commit/%s.html\" />\n",
  654    -1 	        ci->oid);
   -1   610 	fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"commit/%s.html\" />\n", ci->oid);
  655   611 
  656   612 	if (ci->author) {
  657   613 		fputs("<author>\n<name>", fp);
@@ -669,65 +625,84 @@ write_atom_entry(FILE *fp, struct commitinfo *ci)
  669   625 	fputs("</entry>\n", fp);
  670   626 }
  671   627 
  672    -1 int
  673    -1 write_atom(FILE *fp)
   -1   628 void
   -1   629 write_atom_footer(FILE *fp)
  674   630 {
  675    -1 	struct commitinfo *ci;
  676    -1 	git_revwalk *w = NULL;
  677    -1 	git_oid id;
  678    -1 	size_t i, m = 100; /* last 'm' commits */
  679    -1 
  680    -1 	fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
  681    -1 	      "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
  682    -1 	xmlencode(fp, strippedname, strlen(strippedname));
  683    -1 	fputs(", branch HEAD</title>\n<subtitle>", fp);
  684    -1 	xmlencode(fp, description, strlen(description));
  685    -1 	fputs("</subtitle>\n", fp);
  686    -1 
  687    -1 	git_revwalk_new(&w, repo);
  688    -1 	git_revwalk_push_head(w);
  689    -1 	git_revwalk_simplify_first_parent(w);
  690    -1 
  691    -1 	for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
  692    -1 		if (!(ci = commitinfo_getbyoid(&id)))
  693    -1 			break;
  694    -1 		write_atom_entry(fp, ci);
  695    -1 		commitinfo_free(ci);
  696    -1 	}
  697    -1 	git_revwalk_free(w);
  698    -1 
  699   631 	fputs("</feed>\n", fp);
  700    -1 
  701    -1 	return 0;
  702   632 }
  703   633 
  704    -1 int
  705    -1 copy_blob(git_object *obj, const char *fpath, const char *filename, git_off_t filesize)
   -1   634 void
   -1   635 copy_blob(git_object *obj, const char *fpath)
  706   636 {
  707    -1 	char tmp[PATH_MAX] = "", *d;
  708    -1 	int lc = 0;
   -1   637 	char tmp[PATH_MAX] = "";
   -1   638 	char *d;
  709   639 	git_off_t len;
  710   640 	const void *raw;
  711   641 	FILE *fp;
  712   642 
  713   643 	if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
  714   644 		errx(1, "path truncated: '%s'", fpath);
   -1   645 
  715   646 	if (!(d = dirname(tmp)))
  716   647 		err(1, "dirname");
  717   648 	if (mkdirp(d))
  718    -1 		return -1;
   -1   649 		return;
  719   650 
  720   651 	fp = efopen(fpath, "w");
  721   652 	len = git_blob_rawsize((git_blob *)obj);
  722   653 	raw = git_blob_rawcontent((git_blob *)obj);
  723   654 	fwrite(raw, 1, len, fp);
  724   655 	fclose(fp);
   -1   656 }
   -1   657 
   -1   658 void
   -1   659 write_files_header(FILE *fp)
   -1   660 {
   -1   661 	fputs("<table id=\"files\"><thead>\n<tr>"
   -1   662 		"<td><b>Name</b></td>"
   -1   663 		"<td class=\"num\" align=\"right\"><b>Size</b></td>"
   -1   664 		"</tr>\n</thead><tbody>\n", fp);
   -1   665 }
   -1   666 
   -1   667 void
   -1   668 write_files_line(FILE *fp, char *entrypath, char *filepath, uintmax_t size)
   -1   669 {
   -1   670 	fprintf(fp, "<tr><td><a href=\"%s", relpath);
   -1   671 	xmlencode(fp, filepath, strlen(filepath));
   -1   672 	fputs("\">", fp);
   -1   673 	xmlencode(fp, entrypath, strlen(entrypath));
   -1   674 	fputs("</a></td><td class=\"num\" align=\"right\">", fp);
   -1   675 	fprintf(fp, "%juB", (uintmax_t)size);
   -1   676 	fputs("</td></tr>\n", fp);
   -1   677 }
   -1   678 
   -1   679 void
   -1   680 write_files_footer(FILE *fp)
   -1   681 {
   -1   682 	fputs("</tbody>\n", fp);
   -1   683 	fputs("</table>\n", fp);
   -1   684 }
   -1   685 
   -1   686 void
   -1   687 process_readme(FILE *fp)
   -1   688 {
   -1   689 	int i;
   -1   690 	git_object *obj = NULL;
  725   691 
  726    -1 	return lc;
   -1   692 	for (i = 0; i < sizeof(readmefiles) / sizeof(*readmefiles); i++) {
   -1   693 		if (
   -1   694 			!git_revparse_single(&obj, repo, readmefiles[i]) &&
   -1   695 			git_object_type(obj) == GIT_OBJ_BLOB
   -1   696 		) {
   -1   697 			write_readme(fp, (git_blob *)obj);
   -1   698 			break;
   -1   699 		}
   -1   700 		git_object_free(obj);
   -1   701 	}
  727   702 }
  728   703 
  729   704 int
  730    -1 writefilestree(FILE *fp, git_tree *tree, const char *path)
   -1   705 _process_files(FILE *fp, git_tree *tree, const char *path)
  731   706 {
  732   707 	const git_tree_entry *entry = NULL;
  733   708 	git_object *obj = NULL;
@@ -735,19 +710,17 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
  735   710 	const char *entryname;
  736   711 	char filepath[PATH_MAX], entrypath[PATH_MAX];
  737   712 	size_t count, i;
  738    -1 	int lc, r, ret;
   -1   713 	int ret;
  739   714 
  740   715 	count = git_tree_entrycount(tree);
  741   716 	for (i = 0; i < count; i++) {
  742    -1 		if (!(entry = git_tree_entry_byindex(tree, i)) ||
  743    -1 		    !(entryname = git_tree_entry_name(entry)))
   -1   717 		if (
   -1   718 			!(entry = git_tree_entry_byindex(tree, i)) ||
   -1   719 			!(entryname = git_tree_entry_name(entry))
   -1   720 		)
  744   721 			return -1;
  745   722 		joinpath(entrypath, sizeof(entrypath), path, entryname);
  746    -1 
  747    -1 		r = snprintf(filepath, sizeof(filepath), "file/%s",
  748    -1 		         entrypath);
  749    -1 		if (r < 0 || (size_t)r >= sizeof(filepath))
  750    -1 			errx(1, "path truncated: 'file/%s.html'", entrypath);
   -1   723 		joinpath(filepath, sizeof(filepath), "blob", entrypath);
  751   724 
  752   725 		if (!git_tree_entry_to_object(&obj, repo, entry)) {
  753   726 			switch (git_object_type(obj)) {
@@ -755,8 +728,7 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
  755   728 				break;
  756   729 			case GIT_OBJ_TREE:
  757   730 				/* NOTE: recurses */
  758    -1 				ret = writefilestree(fp, (git_tree *)obj,
  759    -1 				                     entrypath);
   -1   731 				ret = _process_files(fp, (git_tree *)obj, entrypath);
  760   732 				git_object_free(obj);
  761   733 				if (ret)
  762   734 					return ret;
@@ -766,19 +738,11 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
  766   738 				continue;
  767   739 			}
  768   740 
   -1   741 			copy_blob(obj, filepath);
   -1   742 
  769   743 			filesize = git_blob_rawsize((git_blob *)obj);
  770    -1 			lc = copy_blob(obj, filepath, entryname, filesize);
  771    -1 
  772    -1 			fprintf(fp, "<tr><td><a href=\"%s", relpath);
  773    -1 			xmlencode(fp, filepath, strlen(filepath));
  774    -1 			fputs("\">", fp);
  775    -1 			xmlencode(fp, entrypath, strlen(entrypath));
  776    -1 			fputs("</a></td><td class=\"num\" align=\"right\">", fp);
  777    -1 			if (lc > 0)
  778    -1 				fprintf(fp, "%dL", lc);
  779    -1 			else
  780    -1 				fprintf(fp, "%juB", (uintmax_t)filesize);
  781    -1 			fputs("</td></tr>\n", fp);
   -1   744 			write_files_line(fp, entrypath, filepath, filesize);
   -1   745 
  782   746 			git_object_free(obj);
  783   747 		}
  784   748 	}
@@ -787,22 +751,25 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
  787   751 }
  788   752 
  789   753 int
  790    -1 write_files(FILE *fp, const git_oid *id)
   -1   754 process_files(FILE *fp)
  791   755 {
  792   756 	git_tree *tree = NULL;
  793   757 	git_commit *commit = NULL;
   -1   758 	git_object *obj = NULL;
   -1   759 	const git_oid *head = NULL;
  794   760 	int ret = -1;
  795   761 
  796    -1 	fputs("<table id=\"files\"><thead>\n<tr>"
  797    -1 	      "<td><b>Name</b></td>"
  798    -1 	      "<td class=\"num\" align=\"right\"><b>Size</b></td>"
  799    -1 	      "</tr>\n</thead><tbody>\n", fp);
  800    -1 
  801    -1 	if (!git_commit_lookup(&commit, repo, id) &&
  802    -1 	    !git_commit_tree(&tree, commit))
  803    -1 		ret = writefilestree(fp, tree, "");
   -1   762 	/* find HEAD */
   -1   763 	if (!git_revparse_single(&obj, repo, "HEAD"))
   -1   764 		head = git_object_id(obj);
   -1   765 	git_object_free(obj);
   -1   766 	if (!head) {
   -1   767 		fprintf(stderr, "no HEAD found\n");
   -1   768 		return 1;
   -1   769 	}
  804   770 
  805    -1 	fputs("</tbody></table>", fp);
   -1   771 	if (!git_commit_lookup(&commit, repo, head) && !git_commit_tree(&tree, commit))
   -1   772 		ret = _process_files(fp, tree, "");
  806   773 
  807   774 	git_commit_free(commit);
  808   775 	git_tree_free(tree);
@@ -811,49 +778,97 @@ write_files(FILE *fp, const git_oid *id)
  811   778 }
  812   779 
  813   780 void
   -1   781 process_commits(FILE *fp_log, FILE *fp_atom, size_t m)
   -1   782 {
   -1   783 	struct commitinfo *ci;
   -1   784 	git_revwalk *w = NULL;
   -1   785 	git_oid id;
   -1   786 	FILE *fp_commit;
   -1   787 	char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
   -1   788 
   -1   789 	git_revwalk_new(&w, repo);
   -1   790 	git_revwalk_push_head(w);
   -1   791 
   -1   792 	while (!git_revwalk_next(&id, w)) {
   -1   793 		if (m == 0) {
   -1   794 			fputs("<tr><td colspan=\"3\">More commits remaining&hellip;</td></tr>\n", fp_log);
   -1   795 			break;
   -1   796 		}
   -1   797 		if (!(ci = commitinfo_getbyoid(&id)))
   -1   798 			break;
   -1   799 
   -1   800 		write_log_line(fp_log, ci);
   -1   801 		write_atom_entry(fp_atom, ci);
   -1   802 
   -1   803 		git_oid_tostr(oidstr, sizeof(oidstr), &id);
   -1   804 		snprintf(path, sizeof(path), "commit/%s.html", oidstr);
   -1   805 
   -1   806 		if (force || access(path, F_OK)) {
   -1   807 			relpath = "../";
   -1   808 			mkdirp("commit");
   -1   809 			fp_commit = efopen(path, "w");
   -1   810 			write_header(fp_commit, ci->summary);
   -1   811 			write_commit(fp_commit, ci);
   -1   812 			write_footer(fp_commit);
   -1   813 			fclose(fp_commit);
   -1   814 			relpath = "";
   -1   815 		}
   -1   816 
   -1   817 		m -= 1;
   -1   818 		commitinfo_free(ci);
   -1   819 	}
   -1   820 	git_revwalk_free(w);
   -1   821 }
   -1   822 
   -1   823 void
  814   824 usage(char *argv0)
  815   825 {
  816    -1 	fprintf(stderr, "%s [-c cachefile | -l commits] repodir\n", argv0);
   -1   826 	fprintf(stderr, "%s repodir\n", argv0);
  817   827 	exit(1);
  818   828 }
  819   829 
  820    -1 int
  821    -1 main(int argc, char *argv[])
   -1   830 void
   -1   831 get_repo_name(const char *path)
  822   832 {
  823    -1 	git_object *obj = NULL;
  824    -1 	const git_oid *head = NULL;
  825    -1 	mode_t mask;
  826    -1 	FILE *fp, *fpread;
  827    -1 	char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p;
  828    -1 	char tmppath[64] = "cache.XXXXXXXXXXXX", buf[BUFSIZ];
  829    -1 	size_t n;
  830    -1 	int i, fd;
  831    -1 
  832    -1 	for (i = 1; i < argc; i++) {
  833    -1 		if (argv[i][0] != '-') {
  834    -1 			if (repodir)
  835    -1 				usage(argv[0]);
  836    -1 			repodir = argv[i];
  837    -1 		} else if (argv[i][1] == 'c') {
  838    -1 			if (nlogcommits > 0 || i + 1 >= argc)
  839    -1 				usage(argv[0]);
  840    -1 			cachefile = argv[++i];
  841    -1 		} else if (argv[i][1] == 'l') {
  842    -1 			if (cachefile || i + 1 >= argc)
  843    -1 				usage(argv[0]);
  844    -1 			errno = 0;
  845    -1 			nlogcommits = strtoll(argv[++i], &p, 10);
  846    -1 			if (argv[i][0] == '\0' || *p != '\0' ||
  847    -1 			    nlogcommits <= 0 || errno)
  848    -1 				usage(argv[0]);
  849    -1 		}
  850    -1 	}
  851    -1 	if (!repodir)
  852    -1 		usage(argv[0]);
   -1   833 	char *p;
  853   834 
  854   835 	if (!realpath(repodir, repodirabs))
  855   836 		err(1, "realpath");
  856   837 
   -1   838 	if (!(reponame = strrchr(repodirabs, '/'))) {
   -1   839 		fprintf(stderr, "could not use directory name\n");
   -1   840 		return;
   -1   841 	}
   -1   842 	reponame++;
   -1   843 	if ((p = strrchr(reponame, '.')))
   -1   844 		if (!strcmp(p, ".git"))
   -1   845 			*p = '\0';
   -1   846 }
   -1   847 
   -1   848 void
   -1   849 get_metadata(void)
   -1   850 {
   -1   851 	char path[PATH_MAX];
   -1   852 	FILE *fpread;
   -1   853 
   -1   854 	joinpath(path, sizeof(path), repodir, "description");
   -1   855 	if ((fpread = fopen(path, "r"))) {
   -1   856 		if (!fgets(description, sizeof(description), fpread))
   -1   857 			description[0] = '\0';
   -1   858 		fclose(fpread);
   -1   859 	}
   -1   860 }
   -1   861 
   -1   862 int
   -1   863 main(int argc, char *argv[])
   -1   864 {
   -1   865 	static FILE *fp_index, *fp_log, *fp_atom;
   -1   866 
   -1   867 	if (argc == 2)
   -1   868 		repodir = argv[1];
   -1   869 	else
   -1   870 		usage(argv[0]);
   -1   871 
  857   872 	git_libgit2_init();
  858   873 
  859   874 #ifdef __OpenBSD__
@@ -861,152 +876,49 @@ main(int argc, char *argv[])
  861   876 		err(1, "unveil: %s", repodir);
  862   877 	if (unveil(".", "rwc") == -1)
  863   878 		err(1, "unveil: .");
  864    -1 	if (cachefile && unveil(cachefile, "rwc") == -1)
  865    -1 		err(1, "unveil: %s", cachefile);
  866    -1 
  867    -1 	if (cachefile) {
  868    -1 		if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
  869    -1 			err(1, "pledge");
  870    -1 	} else {
  871    -1 		if (pledge("stdio rpath wpath cpath", NULL) == -1)
  872    -1 			err(1, "pledge");
  873    -1 	}
   -1   879 
   -1   880 	if (pledge("stdio rpath wpath cpath", NULL) == -1)
   -1   881 		err(1, "pledge");
  874   882 #endif
  875   883 
  876    -1 	if (git_repository_open_ext(&repo, repodir,
  877    -1 		GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) {
   -1   884 	if (git_repository_open_ext(
   -1   885 		&repo, repodir, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL
   -1   886 	) < 0) {
  878   887 		fprintf(stderr, "%s: cannot open repository\n", argv[0]);
  879   888 		return 1;
  880   889 	}
  881   890 
  882    -1 	/* find HEAD */
  883    -1 	if (!git_revparse_single(&obj, repo, "HEAD"))
  884    -1 		head = git_object_id(obj);
  885    -1 	git_object_free(obj);
   -1   891 	get_repo_name(repodir);
   -1   892 	get_metadata();
  886   893 
  887    -1 	/* use directory name as name */
  888    -1 	if ((name = strrchr(repodirabs, '/')))
  889    -1 		name++;
  890    -1 	else
  891    -1 		name = "";
   -1   894 	fp_index = efopen("index.html", "w");
   -1   895 	fp_log = efopen("log.html", "w");
   -1   896 	fp_atom = efopen("atom.xml", "w");
  892   897 
  893    -1 	/* strip .git suffix */
  894    -1 	if (!(strippedname = strdup(name)))
  895    -1 		err(1, "strdup");
  896    -1 	if ((p = strrchr(strippedname, '.')))
  897    -1 		if (!strcmp(p, ".git"))
  898    -1 			*p = '\0';
  899    -1 
  900    -1 	/* read description or .git/description */
  901    -1 	joinpath(path, sizeof(path), repodir, "description");
  902    -1 	if (!(fpread = fopen(path, "r"))) {
  903    -1 		joinpath(path, sizeof(path), repodir, ".git/description");
  904    -1 		fpread = fopen(path, "r");
  905    -1 	}
  906    -1 	if (fpread) {
  907    -1 		if (!fgets(description, sizeof(description), fpread))
  908    -1 			description[0] = '\0';
  909    -1 		fclose(fpread);
  910    -1 	}
   -1   898 	write_header(fp_index, "Files");
   -1   899 	write_files_header(fp_index);
  911   900 
  912    -1 	/* read url or .git/url */
  913    -1 	joinpath(path, sizeof(path), repodir, "url");
  914    -1 	if (!(fpread = fopen(path, "r"))) {
  915    -1 		joinpath(path, sizeof(path), repodir, ".git/url");
  916    -1 		fpread = fopen(path, "r");
  917    -1 	}
  918    -1 	if (fpread) {
  919    -1 		if (!fgets(cloneurl, sizeof(cloneurl), fpread))
  920    -1 			cloneurl[0] = '\0';
  921    -1 		cloneurl[strcspn(cloneurl, "\n")] = '\0';
  922    -1 		fclose(fpread);
  923    -1 	}
   -1   901 	write_header(fp_log, "Log");
   -1   902 	write_log_header(fp_log);
  924   903 
  925    -1 	/* log for HEAD */
  926    -1 	fp = efopen("log.html", "w");
  927    -1 	relpath = "";
  928    -1 	mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO);
  929    -1 	write_header(fp, "Log");
  930    -1 	fputs("<table id=\"log\"><thead>\n<tr><td><b>Date</b></td>"
  931    -1 	      "<td><b>Commit message</b></td>"
  932    -1 	      "<td><b>Author</b></td><td class=\"num\" align=\"right\"><b>Files</b></td>"
  933    -1 	      "<td class=\"num\" align=\"right\"><b>+</b></td>"
  934    -1 	      "<td class=\"num\" align=\"right\"><b>-</b></td></tr>\n</thead><tbody>\n", fp);
  935    -1 
  936    -1 	if (cachefile && head) {
  937    -1 		/* read from cache file (does not need to exist) */
  938    -1 		if ((rcachefp = fopen(cachefile, "r"))) {
  939    -1 			if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp))
  940    -1 				errx(1, "%s: no object id", cachefile);
  941    -1 			if (git_oid_fromstr(&lastoid, lastoidstr))
  942    -1 				errx(1, "%s: invalid object id", cachefile);
  943    -1 		}
   -1   904 	write_atom_header(fp_atom);
  944   905 
  945    -1 		/* write log to (temporary) cache */
  946    -1 		if ((fd = mkstemp(tmppath)) == -1)
  947    -1 			err(1, "mkstemp");
  948    -1 		if (!(wcachefp = fdopen(fd, "w")))
  949    -1 			err(1, "fdopen: '%s'", tmppath);
  950    -1 		/* write last commit id (HEAD) */
  951    -1 		git_oid_tostr(buf, sizeof(buf), head);
  952    -1 		fprintf(wcachefp, "%s\n", buf);
  953    -1 
  954    -1 		write_log(fp, head);
  955    -1 
  956    -1 		if (rcachefp) {
  957    -1 			/* append previous log to log.html and the new cache */
  958    -1 			while (!feof(rcachefp)) {
  959    -1 				n = fread(buf, 1, sizeof(buf), rcachefp);
  960    -1 				if (ferror(rcachefp))
  961    -1 					err(1, "fread");
  962    -1 				if (fwrite(buf, 1, n, fp) != n ||
  963    -1 				    fwrite(buf, 1, n, wcachefp) != n)
  964    -1 					err(1, "fwrite");
  965    -1 			}
  966    -1 			fclose(rcachefp);
  967    -1 		}
  968    -1 		fclose(wcachefp);
  969    -1 	} else {
  970    -1 		if (head)
  971    -1 			write_log(fp, head);
  972    -1 	}
   -1   906 	process_files(fp_index);
   -1   907 	write_files_footer(fp_index);
   -1   908 	process_readme(fp_index);
   -1   909 	process_commits(fp_log, fp_atom, 100);
  973   910 
  974    -1 	fputs("</tbody></table>", fp);
  975    -1 	write_footer(fp);
  976    -1 	fclose(fp);
   -1   911 	write_atom_footer(fp_atom);
  977   912 
  978    -1 	/* files for HEAD */
  979    -1 	fp = efopen("files.html", "w");
  980    -1 	write_header(fp, "Files");
  981    -1 	if (head)
  982    -1 		write_files(fp, head);
  983    -1 	for (i = 0; i < sizeof(readmefiles) / sizeof(*readmefiles) && !readme; i++) {
  984    -1 		if (!git_revparse_single(&obj, repo, readmefiles[i]) &&
  985    -1 		    git_object_type(obj) == GIT_OBJ_BLOB) {
  986    -1 			readme = readmefiles[i];
  987    -1 			write_readme(fp, (git_blob *)obj);
  988    -1 		}
  989    -1 		git_object_free(obj);
  990    -1 	}
  991    -1 	write_footer(fp);
  992    -1 	fclose(fp);
   -1   913 	write_log_footer(fp_log);
   -1   914 	write_footer(fp_log);
  993   915 
  994    -1 	/* Atom feed */
  995    -1 	fp = efopen("atom.xml", "w");
  996    -1 	write_atom(fp);
  997    -1 	fclose(fp);
   -1   916 	write_footer(fp_index);
  998   917 
  999    -1 	/* rename new cache file on success */
 1000    -1 	if (cachefile && head) {
 1001    -1 		if (rename(tmppath, cachefile))
 1002    -1 			err(1, "rename: '%s' to '%s'", tmppath, cachefile);
 1003    -1 		umask((mask = umask(0)));
 1004    -1 		if (chmod(cachefile,
 1005    -1 		    (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask))
 1006    -1 			err(1, "chmod: '%s'", cachefile);
 1007    -1 	}
   -1   918 	fclose(fp_index);
   -1   919 	fclose(fp_log);
   -1   920 	fclose(fp_atom);
 1008   921 
 1009    -1 	/* cleanup */
 1010   922 	git_repository_free(repo);
 1011   923 	git_libgit2_shutdown();
 1012   924