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