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