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