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