stagit.c 34 KB

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