logo

stagit

STAtic GIT web view generator (in C) git clone https://hacktivis.me/git/stagit.git

stagit.c (34900B)


  1. #include <sys/stat.h>
  2. #include <sys/types.h>
  3. #include <err.h>
  4. #include <errno.h>
  5. #include <libgen.h>
  6. #include <limits.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 <git2.h>
  14. #include "compat.h"
  15. #include "config.h"
  16. #define LEN(s) (sizeof(s)/sizeof(*s))
  17. struct deltainfo {
  18. git_patch *patch;
  19. size_t addcount;
  20. size_t delcount;
  21. };
  22. struct commitinfo {
  23. const git_oid *id;
  24. char oid[GIT_OID_HEXSZ + 1];
  25. char parentoid[GIT_OID_HEXSZ + 1];
  26. const git_signature *author;
  27. const git_signature *committer;
  28. const char *summary;
  29. const char *msg;
  30. git_diff *diff;
  31. git_commit *commit;
  32. git_commit *parent;
  33. git_tree *commit_tree;
  34. git_tree *parent_tree;
  35. size_t addcount;
  36. size_t delcount;
  37. size_t filecount;
  38. struct deltainfo **deltas;
  39. size_t ndeltas;
  40. };
  41. /* reference and associated data for sorting */
  42. struct referenceinfo {
  43. struct git_reference *ref;
  44. struct commitinfo *ci;
  45. };
  46. static git_repository *repo;
  47. static const char *baseurl = ""; /* base URL to make absolute RSS/Atom URI */
  48. static const char *relpath = "";
  49. static const char *repodir;
  50. static char *name = "";
  51. static char *strippedname = "";
  52. static char description[255];
  53. static char cloneurl[1024];
  54. static char *submodules;
  55. static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:COPYING" };
  56. static char *license;
  57. static char *readmefiles[] = { "HEAD:README", "HEAD:README.md" };
  58. static char *readme;
  59. static long long nlogcommits = -1; /* < 0 indicates not used */
  60. /* cache */
  61. static git_oid lastoid;
  62. static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */
  63. static FILE *rcachefp, *wcachefp;
  64. static const char *cachefile;
  65. void
  66. joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
  67. {
  68. int r;
  69. r = snprintf(buf, bufsiz, "%s%s%s",
  70. path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
  71. if (r < 0 || (size_t)r >= bufsiz)
  72. errx(1, "path truncated: '%s%s%s'",
  73. path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
  74. }
  75. void
  76. deltainfo_free(struct deltainfo *di)
  77. {
  78. if (!di)
  79. return;
  80. git_patch_free(di->patch);
  81. memset(di, 0, sizeof(*di));
  82. free(di);
  83. }
  84. int
  85. commitinfo_getstats(struct commitinfo *ci)
  86. {
  87. struct deltainfo *di;
  88. git_diff_options opts;
  89. git_diff_find_options fopts;
  90. const git_diff_delta *delta;
  91. const git_diff_hunk *hunk;
  92. const git_diff_line *line;
  93. git_patch *patch = NULL;
  94. size_t ndeltas, nhunks, nhunklines;
  95. size_t i, j, k;
  96. if (git_tree_lookup(&(ci->commit_tree), repo, git_commit_tree_id(ci->commit)))
  97. goto err;
  98. if (!git_commit_parent(&(ci->parent), ci->commit, 0)) {
  99. if (git_tree_lookup(&(ci->parent_tree), repo, git_commit_tree_id(ci->parent))) {
  100. ci->parent = NULL;
  101. ci->parent_tree = NULL;
  102. }
  103. }
  104. git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
  105. opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH |
  106. GIT_DIFF_IGNORE_SUBMODULES |
  107. GIT_DIFF_INCLUDE_TYPECHANGE;
  108. if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts))
  109. goto err;
  110. if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION))
  111. goto err;
  112. /* find renames and copies, exact matches (no heuristic) for renames. */
  113. fopts.flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES |
  114. GIT_DIFF_FIND_EXACT_MATCH_ONLY;
  115. if (git_diff_find_similar(ci->diff, &fopts))
  116. goto err;
  117. ndeltas = git_diff_num_deltas(ci->diff);
  118. if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *))))
  119. err(1, "calloc");
  120. for (i = 0; i < ndeltas; i++) {
  121. if (git_patch_from_diff(&patch, ci->diff, i))
  122. goto err;
  123. if (!(di = calloc(1, sizeof(struct deltainfo))))
  124. err(1, "calloc");
  125. di->patch = patch;
  126. ci->deltas[i] = di;
  127. delta = git_patch_get_delta(patch);
  128. /* skip stats for binary data */
  129. if (delta->flags & GIT_DIFF_FLAG_BINARY)
  130. continue;
  131. nhunks = git_patch_num_hunks(patch);
  132. for (j = 0; j < nhunks; j++) {
  133. if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
  134. break;
  135. for (k = 0; ; k++) {
  136. if (git_patch_get_line_in_hunk(&line, patch, j, k))
  137. break;
  138. if (line->old_lineno == -1) {
  139. di->addcount++;
  140. ci->addcount++;
  141. } else if (line->new_lineno == -1) {
  142. di->delcount++;
  143. ci->delcount++;
  144. }
  145. }
  146. }
  147. }
  148. ci->ndeltas = i;
  149. ci->filecount = i;
  150. return 0;
  151. err:
  152. git_diff_free(ci->diff);
  153. ci->diff = NULL;
  154. git_tree_free(ci->commit_tree);
  155. ci->commit_tree = NULL;
  156. git_tree_free(ci->parent_tree);
  157. ci->parent_tree = NULL;
  158. git_commit_free(ci->parent);
  159. ci->parent = NULL;
  160. if (ci->deltas)
  161. for (i = 0; i < ci->ndeltas; i++)
  162. deltainfo_free(ci->deltas[i]);
  163. free(ci->deltas);
  164. ci->deltas = NULL;
  165. ci->ndeltas = 0;
  166. ci->addcount = 0;
  167. ci->delcount = 0;
  168. ci->filecount = 0;
  169. return -1;
  170. }
  171. void
  172. commitinfo_free(struct commitinfo *ci)
  173. {
  174. size_t i;
  175. if (!ci)
  176. return;
  177. if (ci->deltas)
  178. for (i = 0; i < ci->ndeltas; i++)
  179. deltainfo_free(ci->deltas[i]);
  180. free(ci->deltas);
  181. git_diff_free(ci->diff);
  182. git_tree_free(ci->commit_tree);
  183. git_tree_free(ci->parent_tree);
  184. git_commit_free(ci->commit);
  185. git_commit_free(ci->parent);
  186. memset(ci, 0, sizeof(*ci));
  187. free(ci);
  188. }
  189. struct commitinfo *
  190. commitinfo_getbyoid(const git_oid *id)
  191. {
  192. struct commitinfo *ci;
  193. if (!(ci = calloc(1, sizeof(struct commitinfo))))
  194. err(1, "calloc");
  195. if (git_commit_lookup(&(ci->commit), repo, id))
  196. goto err;
  197. ci->id = id;
  198. git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
  199. git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
  200. ci->author = git_commit_author(ci->commit);
  201. ci->committer = git_commit_committer(ci->commit);
  202. ci->summary = git_commit_summary(ci->commit);
  203. ci->msg = git_commit_message(ci->commit);
  204. return ci;
  205. err:
  206. commitinfo_free(ci);
  207. return NULL;
  208. }
  209. int
  210. refs_cmp(const void *v1, const void *v2)
  211. {
  212. const struct referenceinfo *r1 = v1, *r2 = v2;
  213. time_t t1, t2;
  214. int r;
  215. if ((r = git_reference_is_tag(r1->ref) - git_reference_is_tag(r2->ref)))
  216. return r;
  217. t1 = r1->ci->author ? r1->ci->author->when.time : 0;
  218. t2 = r2->ci->author ? r2->ci->author->when.time : 0;
  219. if ((r = t1 > t2 ? -1 : (t1 == t2 ? 0 : 1)))
  220. return r;
  221. return strcmp(git_reference_shorthand(r1->ref),
  222. git_reference_shorthand(r2->ref));
  223. }
  224. int
  225. getrefs(struct referenceinfo **pris, size_t *prefcount)
  226. {
  227. struct referenceinfo *ris = NULL;
  228. struct commitinfo *ci = NULL;
  229. git_reference_iterator *it = NULL;
  230. const git_oid *id = NULL;
  231. git_object *obj = NULL;
  232. git_reference *dref = NULL, *r, *ref = NULL;
  233. size_t i, refcount;
  234. *pris = NULL;
  235. *prefcount = 0;
  236. if (git_reference_iterator_new(&it, repo))
  237. return -1;
  238. for (refcount = 0; !git_reference_next(&ref, it); ) {
  239. if (!git_reference_is_branch(ref) && !git_reference_is_tag(ref)) {
  240. git_reference_free(ref);
  241. ref = NULL;
  242. continue;
  243. }
  244. switch (git_reference_type(ref)) {
  245. case GIT_REF_SYMBOLIC:
  246. if (git_reference_resolve(&dref, ref))
  247. goto err;
  248. r = dref;
  249. break;
  250. case GIT_REF_OID:
  251. r = ref;
  252. break;
  253. default:
  254. continue;
  255. }
  256. if (!git_reference_target(r) ||
  257. git_reference_peel(&obj, r, GIT_OBJ_ANY))
  258. goto err;
  259. if (!(id = git_object_id(obj)))
  260. goto err;
  261. if (!(ci = commitinfo_getbyoid(id)))
  262. break;
  263. if (!(ris = reallocarray(ris, refcount + 1, sizeof(*ris))))
  264. err(1, "realloc");
  265. ris[refcount].ci = ci;
  266. ris[refcount].ref = r;
  267. refcount++;
  268. git_object_free(obj);
  269. obj = NULL;
  270. git_reference_free(dref);
  271. dref = NULL;
  272. }
  273. git_reference_iterator_free(it);
  274. /* sort by type, date then shorthand name */
  275. qsort(ris, refcount, sizeof(*ris), refs_cmp);
  276. *pris = ris;
  277. *prefcount = refcount;
  278. return 0;
  279. err:
  280. git_object_free(obj);
  281. git_reference_free(dref);
  282. commitinfo_free(ci);
  283. for (i = 0; i < refcount; i++) {
  284. commitinfo_free(ris[i].ci);
  285. git_reference_free(ris[i].ref);
  286. }
  287. free(ris);
  288. return -1;
  289. }
  290. FILE *
  291. efopen(const char *filename, const char *flags)
  292. {
  293. FILE *fp;
  294. if (!(fp = fopen(filename, flags)))
  295. err(1, "fopen: '%s'", filename);
  296. return fp;
  297. }
  298. /* Escape characters below as HTML 2.0 / XML 1.0. */
  299. void
  300. xmlencode(FILE *fp, const char *s, size_t len)
  301. {
  302. size_t i;
  303. for (i = 0; *s && i < len; s++, i++) {
  304. switch(*s) {
  305. case '<': fputs("&lt;", fp); break;
  306. case '>': fputs("&gt;", fp); break;
  307. case '\'': fputs("&#39;", fp); break;
  308. case '&': fputs("&amp;", fp); break;
  309. case '"': fputs("&quot;", fp); break;
  310. default: putc(*s, fp);
  311. }
  312. }
  313. }
  314. /* Escape characters below as HTML 2.0 / XML 1.0, ignore printing '\r', '\n' */
  315. void
  316. xmlencodeline(FILE *fp, const char *s, size_t len)
  317. {
  318. size_t i;
  319. for (i = 0; *s && i < len; s++, i++) {
  320. switch(*s) {
  321. case '<': fputs("&lt;", fp); break;
  322. case '>': fputs("&gt;", fp); break;
  323. case '\'': fputs("&#39;", fp); break;
  324. case '&': fputs("&amp;", fp); break;
  325. case '"': fputs("&quot;", fp); break;
  326. case '\r': break; /* ignore CR */
  327. case '\n': break; /* ignore LF */
  328. default: putc(*s, fp);
  329. }
  330. }
  331. }
  332. int
  333. mkdirp(const char *path)
  334. {
  335. char tmp[PATH_MAX], *p;
  336. if (strlcpy(tmp, path, sizeof(tmp)) >= sizeof(tmp))
  337. errx(1, "path truncated: '%s'", path);
  338. for (p = tmp + (tmp[0] == '/'); *p; p++) {
  339. if (*p != '/')
  340. continue;
  341. *p = '\0';
  342. if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
  343. return -1;
  344. *p = '/';
  345. }
  346. if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
  347. return -1;
  348. return 0;
  349. }
  350. void
  351. printtimez(FILE *fp, const git_time *intime)
  352. {
  353. struct tm *intm;
  354. time_t t;
  355. char out[32];
  356. t = (time_t)intime->time;
  357. if (!(intm = gmtime(&t)))
  358. return;
  359. strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm);
  360. fputs(out, fp);
  361. }
  362. void
  363. printtime(FILE *fp, const git_time *intime)
  364. {
  365. struct tm *intm;
  366. time_t t;
  367. char out[32];
  368. t = (time_t)intime->time + (intime->offset * 60);
  369. if (!(intm = gmtime(&t)))
  370. return;
  371. strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm);
  372. if (intime->offset < 0)
  373. fprintf(fp, "%s -%02d%02d", out,
  374. -(intime->offset) / 60, -(intime->offset) % 60);
  375. else
  376. fprintf(fp, "%s +%02d%02d", out,
  377. intime->offset / 60, intime->offset % 60);
  378. }
  379. void
  380. printtimeshort(FILE *fp, const git_time *intime)
  381. {
  382. struct tm *intm;
  383. time_t t;
  384. char out[32];
  385. t = (time_t)intime->time;
  386. if (!(intm = gmtime(&t)))
  387. return;
  388. strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
  389. fputs(out, fp);
  390. }
  391. void
  392. writeheader(FILE *fp, const char *title)
  393. {
  394. fputs("<!DOCTYPE html>\n"
  395. "<html>\n<head>\n"
  396. "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
  397. "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
  398. "<title>", fp);
  399. xmlencode(fp, title, strlen(title));
  400. if (title[0] && strippedname[0])
  401. fputs(" - ", fp);
  402. xmlencode(fp, strippedname, strlen(strippedname));
  403. if (description[0])
  404. fputs(" - ", fp);
  405. xmlencode(fp, description, strlen(description));
  406. if(strlen(faviconurl) > 0) {
  407. fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%s\" />\n", faviconurl);
  408. }
  409. fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed (tags)\" href=\"%stags.xml\" />\n",
  410. name, relpath);
  411. fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", assetpath);
  412. fputs("</head>\n<body>\n<header>", fp);
  413. fprintf(fp, "<a href=\"..\"><img src=\"%slogo.png\" alt=\"logo\" width=\"32\" height=\"32\" /></a>",
  414. assetpath);
  415. fputs("<h1>", fp);
  416. xmlencode(fp, strippedname, strlen(strippedname));
  417. fputs("</h1><span class=\"desc\">", fp);
  418. xmlencode(fp, description, strlen(description));
  419. fputs("</span>", fp);
  420. if (cloneurl[0]) {
  421. fputs("<code class=\"url\">git clone <a rel=\"vcs-git\" href=\"", fp);
  422. xmlencode(fp, cloneurl, strlen(cloneurl));
  423. fputs("\">", fp);
  424. xmlencode(fp, cloneurl, strlen(cloneurl));
  425. fputs("</a></code>", fp);
  426. }
  427. fputs("</header><nav>\n", fp);
  428. fprintf(fp, " <a rel=\"version-history\" href=\"%slog.html\">Log</a>", relpath);
  429. fprintf(fp, " (<a rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed\" href=\"%satom.xml\">atom</a>)",
  430. name, relpath);
  431. fprintf(fp, " <a rel=\"index\" href=\"%sfiles.html\">Files</a>", relpath);
  432. fprintf(fp, " <a href=\"%srefs.html\">Refs</a>", relpath);
  433. if (submodules)
  434. fprintf(fp, " <a href=\"%sfile/%s.html\">Submodules</a>",
  435. relpath, submodules);
  436. if (readme)
  437. fprintf(fp, " <a href=\"%sfile/%s.html\">README</a>",
  438. relpath, readme);
  439. if (license)
  440. fprintf(fp, " <a href=\"%sfile/%s.html\">LICENSE</a>",
  441. relpath, license);
  442. fputs("</nav>\n<main>\n", fp);
  443. }
  444. void
  445. writefooter(FILE *fp)
  446. {
  447. fputs("</main>\n", fp);
  448. fputs("\t<footer>Please <a href=\"https://hacktivis.me/about\">contact me</a> by any means for <a href=\"https://git-send-email.io/\">patches</a>, issues(tracker will maybe be added oneday), or whatever else.</footer>\n", fp);
  449. fputs("</body>\n</html>\n", fp);
  450. }
  451. size_t
  452. writeblobhtml(FILE *fp, const git_blob *blob)
  453. {
  454. size_t n = 0, i, len, prev;
  455. const char *s = git_blob_rawcontent(blob);
  456. len = git_blob_rawsize(blob);
  457. fputs("<pre id=\"blob\"><ol>\n", fp);
  458. if (len > 0) {
  459. for (i = 0, prev = 0; i < len; i++) {
  460. if (s[i] != '\n')
  461. continue;
  462. n++;
  463. fprintf(fp, "<li class=\"line\" id=\"l%zu\">", n);
  464. xmlencode(fp, &s[prev], i - prev + 1);
  465. fprintf(fp, "</li>");
  466. prev = i + 1;
  467. }
  468. /* trailing data */
  469. if ((len - prev) > 0) {
  470. n++;
  471. fprintf(fp, "<li class=\"line\" id=\"l%zu\">", n);
  472. xmlencode(fp, &s[prev], len - prev);
  473. fprintf(fp, "</li>");
  474. }
  475. }
  476. fputs("</ol></pre>\n", fp);
  477. return n;
  478. }
  479. void
  480. printcommit(FILE *fp, struct commitinfo *ci)
  481. {
  482. fprintf(fp, "<strong>commit:</strong> <a href=\"%scommit/%s.html\">%s</a>\n",
  483. relpath, ci->oid, ci->oid);
  484. if (ci->parentoid[0])
  485. fprintf(fp, "<strong>parent</strong> <a href=\"%scommit/%s.html\">%s</a>\n",
  486. relpath, ci->parentoid, ci->parentoid);
  487. if (ci->author) {
  488. fputs("<strong>Author: </strong>", fp);
  489. xmlencode(fp, ci->author->name, strlen(ci->author->name));
  490. fputs(" &lt;<a href=\"mailto:", fp);
  491. xmlencode(fp, ci->author->email, strlen(ci->author->email));
  492. fputs("\">", fp);
  493. xmlencode(fp, ci->author->email, strlen(ci->author->email));
  494. fputs("</a>&gt;\n<strong>Date:</strong> ", fp);
  495. printtime(fp, &(ci->author->when));
  496. putc('\n', fp);
  497. }
  498. if (ci->msg) {
  499. putc('\n', fp);
  500. xmlencode(fp, ci->msg, strlen(ci->msg));
  501. putc('\n', fp);
  502. }
  503. }
  504. void
  505. printshowfile(FILE *fp, struct commitinfo *ci)
  506. {
  507. const git_diff_delta *delta;
  508. const git_diff_hunk *hunk;
  509. const git_diff_line *line;
  510. git_patch *patch;
  511. size_t nhunks, nhunklines, changed, add, del, total, i, j, k;
  512. char linestr[80];
  513. int c;
  514. printcommit(fp, ci);
  515. if (!ci->deltas)
  516. return;
  517. if (ci->filecount > 1000 ||
  518. ci->ndeltas > 1000 ||
  519. ci->addcount > 100000 ||
  520. ci->delcount > 100000) {
  521. fputs("Diff is too large, output suppressed.\n", fp);
  522. return;
  523. }
  524. /* diff stat */
  525. fputs("<h2>Diffstat:</h2>\n<table>", fp);
  526. for (i = 0; i < ci->ndeltas; i++) {
  527. delta = git_patch_get_delta(ci->deltas[i]->patch);
  528. switch (delta->status) {
  529. case GIT_DELTA_ADDED: c = 'A'; break;
  530. case GIT_DELTA_COPIED: c = 'C'; break;
  531. case GIT_DELTA_DELETED: c = 'D'; break;
  532. case GIT_DELTA_MODIFIED: c = 'M'; break;
  533. case GIT_DELTA_RENAMED: c = 'R'; break;
  534. case GIT_DELTA_TYPECHANGE: c = 'T'; break;
  535. default: c = ' '; break;
  536. }
  537. if (c == ' ')
  538. fprintf(fp, "<tr><td>%c", c);
  539. else
  540. fprintf(fp, "<tr><td class=\"%c\">%c", c, c);
  541. fprintf(fp, "</td><td><a href=\"#h%zu\">", i);
  542. xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
  543. if (strcmp(delta->old_file.path, delta->new_file.path)) {
  544. fputs(" -&gt; ", fp);
  545. xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
  546. }
  547. add = ci->deltas[i]->addcount;
  548. del = ci->deltas[i]->delcount;
  549. changed = add + del;
  550. total = sizeof(linestr) - 2;
  551. if (changed > total) {
  552. if (add)
  553. add = ((float)total / changed * add) + 1;
  554. if (del)
  555. del = ((float)total / changed * del) + 1;
  556. }
  557. memset(&linestr, '+', add);
  558. memset(&linestr[add], '-', del);
  559. fprintf(fp, "</a></td><td class=\"num\">%zu</td><td><span class=\"i\">",
  560. ci->deltas[i]->addcount + ci->deltas[i]->delcount);
  561. fwrite(&linestr, 1, add, fp);
  562. fputs("</span><span class=\"d\">", fp);
  563. fwrite(&linestr[add], 1, del, fp);
  564. fputs("</span></td></tr>\n", fp);
  565. }
  566. fprintf(fp, "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
  567. ci->filecount, ci->filecount == 1 ? "" : "s",
  568. ci->addcount, ci->addcount == 1 ? "" : "s",
  569. ci->delcount, ci->delcount == 1 ? "" : "s");
  570. fputs("<hr/>", fp);
  571. for (i = 0; i < ci->ndeltas; i++) {
  572. patch = ci->deltas[i]->patch;
  573. delta = git_patch_get_delta(patch);
  574. fprintf(fp, "<strong>diff --git a/<a id=\"h%zu\" href=\"%sfile/", i, relpath);
  575. xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
  576. fputs(".html\">", fp);
  577. xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
  578. fprintf(fp, "</a> b/<a href=\"%sfile/", relpath);
  579. xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
  580. fprintf(fp, ".html\">");
  581. xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
  582. fprintf(fp, "</a></strong>\n");
  583. /* check binary data */
  584. if (delta->flags & GIT_DIFF_FLAG_BINARY) {
  585. fputs("Binary files differ.\n", fp);
  586. continue;
  587. }
  588. nhunks = git_patch_num_hunks(patch);
  589. for (j = 0; j < nhunks; j++) {
  590. if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
  591. break;
  592. fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j);
  593. xmlencode(fp, hunk->header, hunk->header_len);
  594. fputs("</a>", fp);
  595. for (k = 0; ; k++) {
  596. if (git_patch_get_line_in_hunk(&line, patch, j, k))
  597. break;
  598. if (line->old_lineno == -1)
  599. fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+",
  600. i, j, k, i, j, k);
  601. else if (line->new_lineno == -1)
  602. fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-",
  603. i, j, k, i, j, k);
  604. else
  605. putc(' ', fp);
  606. xmlencodeline(fp, line->content, line->content_len);
  607. putc('\n', fp);
  608. if (line->old_lineno == -1 || line->new_lineno == -1)
  609. fputs("</a>", fp);
  610. }
  611. }
  612. }
  613. }
  614. void
  615. writelogline(FILE *fp, struct commitinfo *ci)
  616. {
  617. fputs("<tr><td class=\"date log-author-date\">", fp);
  618. if (ci->author)
  619. printtimeshort(fp, &(ci->author->when));
  620. fputs("</td><td class=\"text log-summary\">", fp);
  621. if (ci->summary) {
  622. fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid);
  623. xmlencode(fp, ci->summary, strlen(ci->summary));
  624. fputs("</a>", fp);
  625. }
  626. fputs("</td><td class=\"text log-author\">", fp);
  627. if (ci->author)
  628. xmlencode(fp, ci->author->name, strlen(ci->author->name));
  629. fputs("</td><td class=\"num log-filecount\">", fp);
  630. fprintf(fp, "%zu", ci->filecount);
  631. fputs("</td><td class=\"num log-addcount\">", fp);
  632. fprintf(fp, "+%zu", ci->addcount);
  633. fputs("</td><td class=\"num log-delcount\">", fp);
  634. fprintf(fp, "-%zu", ci->delcount);
  635. fputs("</td></tr>\n", fp);
  636. }
  637. int
  638. writelog(FILE *fp, const git_oid *oid)
  639. {
  640. struct commitinfo *ci;
  641. git_revwalk *w = NULL;
  642. git_oid id;
  643. char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
  644. FILE *fpfile;
  645. int r;
  646. git_revwalk_new(&w, repo);
  647. git_revwalk_push(w, oid);
  648. while (!git_revwalk_next(&id, w)) {
  649. relpath = "";
  650. if (cachefile && !memcmp(&id, &lastoid, sizeof(id)))
  651. break;
  652. git_oid_tostr(oidstr, sizeof(oidstr), &id);
  653. r = snprintf(path, sizeof(path), "commit/%s.html", oidstr);
  654. if (r < 0 || (size_t)r >= sizeof(path))
  655. errx(1, "path truncated: 'commit/%s.html'", oidstr);
  656. r = access(path, F_OK);
  657. /* optimization: if there are no log lines to write and
  658. the commit file already exists: skip the diffstat */
  659. if (!nlogcommits && !r)
  660. continue;
  661. if (!(ci = commitinfo_getbyoid(&id)))
  662. break;
  663. /* diffstat: for stagit HTML required for the log.html line */
  664. if (commitinfo_getstats(ci) == -1)
  665. goto err;
  666. if (nlogcommits < 0) {
  667. writelogline(fp, ci);
  668. } else if (nlogcommits > 0) {
  669. writelogline(fp, ci);
  670. nlogcommits--;
  671. if (!nlogcommits && ci->parentoid[0])
  672. fputs("<tr><td></td><td colspan=\"5\">"
  673. "More commits remaining [...]</td>"
  674. "</tr>\n", fp);
  675. }
  676. if (cachefile)
  677. writelogline(wcachefp, ci);
  678. /* check if file exists if so skip it */
  679. if (r) {
  680. relpath = "../";
  681. fpfile = efopen(path, "w");
  682. writeheader(fpfile, ci->summary);
  683. fputs("<pre>", fpfile);
  684. printshowfile(fpfile, ci);
  685. fputs("</pre>\n", fpfile);
  686. writefooter(fpfile);
  687. fclose(fpfile);
  688. }
  689. err:
  690. commitinfo_free(ci);
  691. }
  692. git_revwalk_free(w);
  693. relpath = "";
  694. return 0;
  695. }
  696. void
  697. printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag)
  698. {
  699. fputs("<entry>\n", fp);
  700. fprintf(fp, "<id>%s</id>\n", ci->oid);
  701. if (ci->author) {
  702. fputs("<published>", fp);
  703. printtimez(fp, &(ci->author->when));
  704. fputs("</published>\n", fp);
  705. }
  706. if (ci->committer) {
  707. fputs("<updated>", fp);
  708. printtimez(fp, &(ci->committer->when));
  709. fputs("</updated>\n", fp);
  710. }
  711. if (ci->summary) {
  712. fputs("<title type=\"text\">", fp);
  713. if (tag && tag[0]) {
  714. fputs("[", fp);
  715. xmlencode(fp, tag, strlen(tag));
  716. fputs("] ", fp);
  717. }
  718. xmlencode(fp, ci->summary, strlen(ci->summary));
  719. fputs("</title>\n", fp);
  720. }
  721. fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"%scommit/%s.html\" />\n",
  722. baseurl, ci->oid);
  723. if (ci->author) {
  724. fputs("<author>\n<name>", fp);
  725. xmlencode(fp, ci->author->name, strlen(ci->author->name));
  726. fputs("</name>\n<email>", fp);
  727. xmlencode(fp, ci->author->email, strlen(ci->author->email));
  728. fputs("</email>\n</author>\n", fp);
  729. }
  730. fputs("<content type=\"text\">", fp);
  731. fprintf(fp, "commit %s\n", ci->oid);
  732. if (ci->parentoid[0])
  733. fprintf(fp, "parent %s\n", ci->parentoid);
  734. if (ci->author) {
  735. fputs("Author: ", fp);
  736. xmlencode(fp, ci->author->name, strlen(ci->author->name));
  737. fputs(" &lt;", fp);
  738. xmlencode(fp, ci->author->email, strlen(ci->author->email));
  739. fputs("&gt;\nDate: ", fp);
  740. printtime(fp, &(ci->author->when));
  741. putc('\n', fp);
  742. }
  743. if (ci->msg) {
  744. putc('\n', fp);
  745. xmlencode(fp, ci->msg, strlen(ci->msg));
  746. }
  747. fputs("\n</content>\n</entry>\n", fp);
  748. }
  749. int
  750. writeatom(FILE *fp, int all)
  751. {
  752. struct referenceinfo *ris = NULL;
  753. size_t refcount = 0;
  754. struct commitinfo *ci;
  755. git_revwalk *w = NULL;
  756. git_oid id;
  757. size_t i, m = 100; /* last 'm' commits */
  758. fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
  759. "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
  760. xmlencode(fp, strippedname, strlen(strippedname));
  761. fputs(", branch HEAD</title>\n<subtitle>", fp);
  762. xmlencode(fp, description, strlen(description));
  763. fputs("</subtitle>\n", fp);
  764. /* all commits or only tags? */
  765. if (all) {
  766. git_revwalk_new(&w, repo);
  767. git_revwalk_push_head(w);
  768. for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
  769. if (!(ci = commitinfo_getbyoid(&id)))
  770. break;
  771. printcommitatom(fp, ci, "");
  772. commitinfo_free(ci);
  773. }
  774. git_revwalk_free(w);
  775. } else if (getrefs(&ris, &refcount) != -1) {
  776. /* references: tags */
  777. for (i = 0; i < refcount; i++) {
  778. if (git_reference_is_tag(ris[i].ref))
  779. printcommitatom(fp, ris[i].ci,
  780. git_reference_shorthand(ris[i].ref));
  781. commitinfo_free(ris[i].ci);
  782. git_reference_free(ris[i].ref);
  783. }
  784. free(ris);
  785. }
  786. fputs("</feed>\n", fp);
  787. return 0;
  788. }
  789. size_t
  790. writeblob(git_object *obj, const char *fpath, const char *filename, size_t filesize)
  791. {
  792. char tmp[PATH_MAX] = "", *d;
  793. const char *p;
  794. size_t lc = 0;
  795. FILE *fp;
  796. if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
  797. errx(1, "path truncated: '%s'", fpath);
  798. if (!(d = dirname(tmp)))
  799. err(1, "dirname");
  800. if (mkdirp(d))
  801. return -1;
  802. for (p = fpath, tmp[0] = '\0'; *p; p++) {
  803. if (*p == '/' && strlcat(tmp, "../", sizeof(tmp)) >= sizeof(tmp))
  804. errx(1, "path truncated: '../%s'", tmp);
  805. }
  806. relpath = tmp;
  807. fp = efopen(fpath, "w");
  808. writeheader(fp, filename);
  809. fputs("<p> ", fp);
  810. xmlencode(fp, filename, strlen(filename));
  811. fprintf(fp, " (%zuB)", filesize);
  812. fputs("</p><hr/>", fp);
  813. if (git_blob_is_binary((git_blob *)obj)) {
  814. fputs("<p>Binary file.</p>\n", fp);
  815. } else {
  816. lc = writeblobhtml(fp, (git_blob *)obj);
  817. if (ferror(fp))
  818. err(1, "fwrite");
  819. }
  820. writefooter(fp);
  821. fclose(fp);
  822. relpath = "";
  823. return lc;
  824. }
  825. const char *
  826. filemode(git_filemode_t m)
  827. {
  828. static char mode[11];
  829. memset(mode, '-', sizeof(mode) - 1);
  830. mode[10] = '\0';
  831. if (S_ISREG(m))
  832. mode[0] = '-';
  833. else if (S_ISBLK(m))
  834. mode[0] = 'b';
  835. else if (S_ISCHR(m))
  836. mode[0] = 'c';
  837. else if (S_ISDIR(m))
  838. mode[0] = 'd';
  839. else if (S_ISFIFO(m))
  840. mode[0] = 'p';
  841. else if (S_ISLNK(m))
  842. mode[0] = 'l';
  843. else if (S_ISSOCK(m))
  844. mode[0] = 's';
  845. else
  846. mode[0] = '?';
  847. if (m & S_IRUSR) mode[1] = 'r';
  848. if (m & S_IWUSR) mode[2] = 'w';
  849. if (m & S_IXUSR) mode[3] = 'x';
  850. if (m & S_IRGRP) mode[4] = 'r';
  851. if (m & S_IWGRP) mode[5] = 'w';
  852. if (m & S_IXGRP) mode[6] = 'x';
  853. if (m & S_IROTH) mode[7] = 'r';
  854. if (m & S_IWOTH) mode[8] = 'w';
  855. if (m & S_IXOTH) mode[9] = 'x';
  856. if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
  857. if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
  858. if (m & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
  859. return mode;
  860. }
  861. int
  862. writefilestree(FILE *fp, git_tree *tree, const char *path)
  863. {
  864. const git_tree_entry *entry = NULL;
  865. git_object *obj = NULL;
  866. const char *entryname;
  867. char filepath[PATH_MAX], entrypath[PATH_MAX], oid[8];
  868. size_t count, i, lc, filesize;
  869. int r, ret;
  870. count = git_tree_entrycount(tree);
  871. for (i = 0; i < count; i++) {
  872. if (!(entry = git_tree_entry_byindex(tree, i)) ||
  873. !(entryname = git_tree_entry_name(entry)))
  874. return -1;
  875. joinpath(entrypath, sizeof(entrypath), path, entryname);
  876. r = snprintf(filepath, sizeof(filepath), "file/%s.html",
  877. entrypath);
  878. if (r < 0 || (size_t)r >= sizeof(filepath))
  879. errx(1, "path truncated: 'file/%s.html'", entrypath);
  880. if (!git_tree_entry_to_object(&obj, repo, entry)) {
  881. switch (git_object_type(obj)) {
  882. case GIT_OBJ_BLOB:
  883. break;
  884. case GIT_OBJ_TREE:
  885. /* NOTE: recurses */
  886. ret = writefilestree(fp, (git_tree *)obj,
  887. entrypath);
  888. git_object_free(obj);
  889. if (ret)
  890. return ret;
  891. continue;
  892. default:
  893. git_object_free(obj);
  894. continue;
  895. }
  896. filesize = git_blob_rawsize((git_blob *)obj);
  897. lc = writeblob(obj, filepath, entryname, filesize);
  898. fputs("<tr><td>", fp);
  899. fputs(filemode(git_tree_entry_filemode(entry)), fp);
  900. fprintf(fp, "</td><td><a href=\"%s", relpath);
  901. xmlencode(fp, filepath, strlen(filepath));
  902. fputs("\">", fp);
  903. xmlencode(fp, entrypath, strlen(entrypath));
  904. fputs("</a></td><td class=\"num\" align=\"right\">", fp);
  905. if (lc > 0)
  906. fprintf(fp, "%zuL", lc);
  907. else
  908. fprintf(fp, "%zuB", filesize);
  909. fputs("</td></tr>\n", fp);
  910. git_object_free(obj);
  911. } else if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) {
  912. /* commit object in tree is a submodule */
  913. fprintf(fp, "<tr><td>m---------</td><td><a href=\"%sfile/.gitmodules.html\">",
  914. relpath);
  915. xmlencode(fp, entrypath, strlen(entrypath));
  916. fputs("</a> @ ", fp);
  917. git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry));
  918. xmlencode(fp, oid, strlen(oid));
  919. fputs("</td><td class=\"num\" align=\"right\"></td></tr>\n", fp);
  920. }
  921. }
  922. return 0;
  923. }
  924. int
  925. writefiles(FILE *fp, const git_oid *id)
  926. {
  927. git_tree *tree = NULL;
  928. git_commit *commit = NULL;
  929. int ret = -1;
  930. fputs("<table id=\"files\"><thead>\n<tr>"
  931. "<td>Mode</td><td>Name</td>"
  932. "<td class=\"num\" align=\"right\">Size</td>"
  933. "</tr>\n</thead><tbody>\n", fp);
  934. if (!git_commit_lookup(&commit, repo, id) &&
  935. !git_commit_tree(&tree, commit))
  936. ret = writefilestree(fp, tree, "");
  937. fputs("</tbody></table>", fp);
  938. git_commit_free(commit);
  939. git_tree_free(tree);
  940. return ret;
  941. }
  942. int
  943. writerefs(FILE *fp)
  944. {
  945. struct referenceinfo *ris = NULL;
  946. struct commitinfo *ci;
  947. size_t count, i, j, refcount;
  948. const char *titles[] = { "Branches", "Tags" };
  949. const char *ids[] = { "branches", "tags" };
  950. const char *s;
  951. if (getrefs(&ris, &refcount) == -1)
  952. return -1;
  953. for (i = 0, j = 0, count = 0; i < refcount; i++) {
  954. if (j == 0 && git_reference_is_tag(ris[i].ref)) {
  955. if (count)
  956. fputs("</tbody></table><br/>\n", fp);
  957. count = 0;
  958. j = 1;
  959. }
  960. /* print header if it has an entry (first). */
  961. if (++count == 1) {
  962. fprintf(fp, "<h2>%s</h2><table id=\"%s\">"
  963. "<thead>\n<tr><td>Name</td>"
  964. "<td>Last commit date</td>"
  965. "<td>Author</td>\n</tr>\n"
  966. "</thead><tbody>\n",
  967. titles[j], ids[j]);
  968. }
  969. ci = ris[i].ci;
  970. s = git_reference_shorthand(ris[i].ref);
  971. fputs("<tr><td>", fp);
  972. xmlencode(fp, s, strlen(s));
  973. fputs("</td><td>", fp);
  974. if (ci->author)
  975. printtimeshort(fp, &(ci->author->when));
  976. fputs("</td><td>", fp);
  977. if (ci->author)
  978. xmlencode(fp, ci->author->name, strlen(ci->author->name));
  979. fputs("</td></tr>\n", fp);
  980. }
  981. /* table footer */
  982. if (count)
  983. fputs("</tbody></table><br/>\n", fp);
  984. for (i = 0; i < refcount; i++) {
  985. commitinfo_free(ris[i].ci);
  986. git_reference_free(ris[i].ref);
  987. }
  988. free(ris);
  989. return 0;
  990. }
  991. void
  992. usage(char *argv0)
  993. {
  994. fprintf(stderr, "%s [-c cachefile | -l commits] "
  995. "[-u baseurl] repodir\n", argv0);
  996. exit(1);
  997. }
  998. int
  999. main(int argc, char *argv[])
  1000. {
  1001. git_object *obj = NULL;
  1002. const git_oid *head = NULL;
  1003. mode_t mask;
  1004. FILE *fp, *fpread;
  1005. char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p;
  1006. char tmppath[64] = "cache.XXXXXXXXXXXX", buf[BUFSIZ];
  1007. size_t n;
  1008. int i, fd;
  1009. for (i = 1; i < argc; i++) {
  1010. if (argv[i][0] != '-') {
  1011. if (repodir)
  1012. usage(argv[0]);
  1013. repodir = argv[i];
  1014. } else if (argv[i][1] == 'c') {
  1015. if (nlogcommits > 0 || i + 1 >= argc)
  1016. usage(argv[0]);
  1017. cachefile = argv[++i];
  1018. } else if (argv[i][1] == 'l') {
  1019. if (cachefile || i + 1 >= argc)
  1020. usage(argv[0]);
  1021. errno = 0;
  1022. nlogcommits = strtoll(argv[++i], &p, 10);
  1023. if (argv[i][0] == '\0' || *p != '\0' ||
  1024. nlogcommits <= 0 || errno)
  1025. usage(argv[0]);
  1026. } else if (argv[i][1] == 'u') {
  1027. if (i + 1 >= argc)
  1028. usage(argv[0]);
  1029. baseurl = argv[++i];
  1030. }
  1031. }
  1032. if (!repodir)
  1033. usage(argv[0]);
  1034. if (!realpath(repodir, repodirabs))
  1035. err(1, "realpath");
  1036. git_libgit2_init();
  1037. #ifdef __OpenBSD__
  1038. if (unveil(repodir, "r") == -1)
  1039. err(1, "unveil: %s", repodir);
  1040. if (unveil(".", "rwc") == -1)
  1041. err(1, "unveil: .");
  1042. if (cachefile && unveil(cachefile, "rwc") == -1)
  1043. err(1, "unveil: %s", cachefile);
  1044. if (cachefile) {
  1045. if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
  1046. err(1, "pledge");
  1047. } else {
  1048. if (pledge("stdio rpath wpath cpath", NULL) == -1)
  1049. err(1, "pledge");
  1050. }
  1051. #endif
  1052. if (git_repository_open_ext(&repo, repodir,
  1053. GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) {
  1054. fprintf(stderr, "%s: cannot open repository\n", argv[0]);
  1055. return 1;
  1056. }
  1057. /* find HEAD */
  1058. if (!git_revparse_single(&obj, repo, "HEAD"))
  1059. head = git_object_id(obj);
  1060. git_object_free(obj);
  1061. /* use directory name as name */
  1062. if ((name = strrchr(repodirabs, '/')))
  1063. name++;
  1064. else
  1065. name = "";
  1066. /* strip .git suffix */
  1067. if (!(strippedname = strdup(name)))
  1068. err(1, "strdup");
  1069. if ((p = strrchr(strippedname, '.')))
  1070. if (!strcmp(p, ".git"))
  1071. *p = '\0';
  1072. /* read description or .git/description */
  1073. joinpath(path, sizeof(path), repodir, "description");
  1074. if (!(fpread = fopen(path, "r"))) {
  1075. joinpath(path, sizeof(path), repodir, ".git/description");
  1076. fpread = fopen(path, "r");
  1077. }
  1078. if (fpread) {
  1079. if (!fgets(description, sizeof(description), fpread))
  1080. description[0] = '\0';
  1081. fclose(fpread);
  1082. }
  1083. /* read url or .git/url */
  1084. joinpath(path, sizeof(path), repodir, "url");
  1085. if (!(fpread = fopen(path, "r"))) {
  1086. joinpath(path, sizeof(path), repodir, ".git/url");
  1087. fpread = fopen(path, "r");
  1088. }
  1089. if (fpread) {
  1090. if (!fgets(cloneurl, sizeof(cloneurl), fpread))
  1091. cloneurl[0] = '\0';
  1092. cloneurl[strcspn(cloneurl, "\n")] = '\0';
  1093. fclose(fpread);
  1094. }
  1095. /* check LICENSE */
  1096. for (i = 0; i < LEN(licensefiles) && !license; i++) {
  1097. if (!git_revparse_single(&obj, repo, licensefiles[i]) &&
  1098. git_object_type(obj) == GIT_OBJ_BLOB)
  1099. license = licensefiles[i] + strlen("HEAD:");
  1100. git_object_free(obj);
  1101. }
  1102. /* check README */
  1103. for (i = 0; i < LEN(readmefiles) && !readme; i++) {
  1104. if (!git_revparse_single(&obj, repo, readmefiles[i]) &&
  1105. git_object_type(obj) == GIT_OBJ_BLOB)
  1106. readme = readmefiles[i] + strlen("HEAD:");
  1107. git_object_free(obj);
  1108. }
  1109. if (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") &&
  1110. git_object_type(obj) == GIT_OBJ_BLOB)
  1111. submodules = ".gitmodules";
  1112. git_object_free(obj);
  1113. /* log for HEAD */
  1114. fp = efopen("log.html", "w");
  1115. relpath = "";
  1116. mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO);
  1117. writeheader(fp, "Log");
  1118. fputs("<table id=\"log\"><thead>\n<tr><td>Date</td>"
  1119. "<td>Commit message</td>"
  1120. "<td>Author</td><td class=\"num\" align=\"right\">Files</td>"
  1121. "<td class=\"num\" align=\"right\">+</td>"
  1122. "<td class=\"num\" align=\"right\">-</td></tr>\n</thead><tbody>\n", fp);
  1123. if (cachefile && head) {
  1124. /* read from cache file (does not need to exist) */
  1125. if ((rcachefp = fopen(cachefile, "r"))) {
  1126. if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp))
  1127. errx(1, "%s: no object id", cachefile);
  1128. if (git_oid_fromstr(&lastoid, lastoidstr))
  1129. errx(1, "%s: invalid object id", cachefile);
  1130. }
  1131. /* write log to (temporary) cache */
  1132. if ((fd = mkstemp(tmppath)) == -1)
  1133. err(1, "mkstemp");
  1134. if (!(wcachefp = fdopen(fd, "w")))
  1135. err(1, "fdopen: '%s'", tmppath);
  1136. /* write last commit id (HEAD) */
  1137. git_oid_tostr(buf, sizeof(buf), head);
  1138. fprintf(wcachefp, "%s\n", buf);
  1139. writelog(fp, head);
  1140. if (rcachefp) {
  1141. /* append previous log to log.html and the new cache */
  1142. while (!feof(rcachefp)) {
  1143. n = fread(buf, 1, sizeof(buf), rcachefp);
  1144. if (ferror(rcachefp))
  1145. err(1, "fread");
  1146. if (fwrite(buf, 1, n, fp) != n ||
  1147. fwrite(buf, 1, n, wcachefp) != n)
  1148. err(1, "fwrite");
  1149. }
  1150. fclose(rcachefp);
  1151. }
  1152. fclose(wcachefp);
  1153. } else {
  1154. if (head)
  1155. writelog(fp, head);
  1156. }
  1157. fputs("</tbody></table>", fp);
  1158. writefooter(fp);
  1159. fclose(fp);
  1160. /* files for HEAD */
  1161. fp = efopen("files.html", "w");
  1162. writeheader(fp, "Files");
  1163. if (head)
  1164. writefiles(fp, head);
  1165. writefooter(fp);
  1166. fclose(fp);
  1167. /* summary page with branches and tags */
  1168. fp = efopen("refs.html", "w");
  1169. writeheader(fp, "Refs");
  1170. writerefs(fp);
  1171. writefooter(fp);
  1172. fclose(fp);
  1173. /* Atom feed */
  1174. fp = efopen("atom.xml", "w");
  1175. writeatom(fp, 1);
  1176. fclose(fp);
  1177. /* Atom feed for tags / releases */
  1178. fp = efopen("tags.xml", "w");
  1179. writeatom(fp, 0);
  1180. fclose(fp);
  1181. /* rename new cache file on success */
  1182. if (cachefile && head) {
  1183. if (rename(tmppath, cachefile))
  1184. err(1, "rename: '%s' to '%s'", tmppath, cachefile);
  1185. umask((mask = umask(0)));
  1186. if (chmod(cachefile,
  1187. (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask))
  1188. err(1, "chmod: '%s'", cachefile);
  1189. }
  1190. /* cleanup */
  1191. git_repository_free(repo);
  1192. git_libgit2_shutdown();
  1193. return 0;
  1194. }