sgw | Static git web (fork of stagit) |
| download: https://git.y1.nz/archives/sgw.tar.gz | |
| README | Files | Log | Refs | LICENSE |
main.c
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 <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <time.h>
12 #include <unistd.h>
13 #include <limits.h>
14
15 #include <git2.h>
16
17 #include "compat.h"
18 #include "opt.h"
19 #include "files.h"
20 #include "repo.h"
21
22 #define CPUT(f, c) putc((c), (f))
23 #define put_xml(fp, s) put_xml_len((fp), (s), strlen(s))
24 #define SPUTF(f, s) fputs((s), (f))
25
26 #include "config.h"
27
28 struct DeltaInfo {
29 git_patch *patch;
30
31 size_t add_count;
32 size_t del_count;
33 };
34
35 struct CommitInfo {
36 const git_oid *id;
37
38 char oid[GIT_OID_HEXSZ + 1];
39 char parentoid[GIT_OID_HEXSZ + 1];
40
41 const git_signature *author;
42 const git_signature *committer;
43 const char *summary;
44 const char *msg;
45
46 git_diff *diff;
47 git_commit *commit;
48 git_commit *parent;
49 git_tree *commit_tree;
50 git_tree *parent_tree;
51
52 size_t add_count;
53 size_t del_count;
54 size_t file_count;
55
56 struct DeltaInfo **deltas;
57 size_t ndeltas;
58 };
59
60 /* reference and associated data for sorting */
61 struct ReferenceInfo {
62 struct git_reference *ref;
63 struct CommitInfo *ci;
64 };
65
66 struct Weight {
67 size_t bytes;
68 size_t lines;
69 };
70
71 /* miscellaneous constants */
72 static const char orders[] = { ' ', 'k', 'M', 'G', 'T', 'P', 'E' };
73 static const char tab[] = "0123456789ABCDEF";
74
75 /* GLOBALS */
76 static Opt opt;
77 static RepoData *data;
78 static FILE *index_fp;
79 static char index_fname[PATH_MAX];
80
81 // TODO reorganize so that this ugliness isn't necessary...
82 int put_index_log(FILE *fp, RepoData *data);
83 void put_index_header(FILE *fp);
84 void put_index_footer(FILE *fp);
85
86 void
87 get_page_path(char *buf, size_t buf_size, const char *dir, char *name)
88 {
89 int index = 0, chars = 0;
90
91 // TODO tweak this so that it correctly places slashes between names
92 chars = snprintf(buf + index, buf_size - index, "%s", dir);
93 if (chars < 0 || (size_t) (index += chars) >= buf_size)
94 goto page_path_err;
95
96 if (chars != 0) buf[index++] = '/';
97
98 chars = snprintf(buf + index, buf_size - index, PAGE_DEST);
99 if (chars < 0 || (size_t) (index += chars) >= buf_size)
100 goto page_path_err;
101
102 if (chars != 0) buf[index++] = '/';
103
104 chars = snprintf(buf + index, buf_size - index, "%s", name);
105 if (chars < 0 || (size_t) (index += chars) >= buf_size)
106 goto page_path_err;
107
108 return;
109
110 page_path_err:
111 errx(1, "page path truncated! '%s' / '%s'" "\n", dir, name);
112 }
113
114 void
115 get_index_page_path(char *buf, size_t buf_size, const char *dir, char *name)
116 {
117 int index = 0, chars = 0;
118
119 // TODO tweak this so that it correctly places slashes between names
120 chars = snprintf(buf + index, buf_size - index, "%s", dir);
121 if (chars < 0 || (size_t) (index += chars) >= buf_size)
122 goto page_path_err;
123
124 const char *format = dir[0] == '\0' ? "%s" : "/%s";
125 chars = snprintf(buf + index, buf_size - index, format, name);
126 if (chars < 0 || (size_t) (index += chars) >= buf_size)
127 goto page_path_err;
128
129 return;
130
131 page_path_err:
132 errx(1, "page path truncated! '%s' / '%s'" "\n", dir, name);
133 }
134
135 void
136 delta_info_free(struct DeltaInfo *di)
137 {
138 if (!di) return;
139
140 git_patch_free(di->patch);
141 memset(di, 0, sizeof(*di));
142 free(di);
143 }
144
145 int
146 commit_info_get_stats(struct CommitInfo *ci)
147 {
148 struct DeltaInfo *di;
149 git_diff_options opts;
150 git_diff_find_options fopts;
151 const git_diff_delta *delta;
152 const git_diff_hunk *hunk;
153 const git_diff_line *line;
154 git_patch *patch = NULL;
155 size_t ndeltas, nhunks, nhunklines;
156 size_t i, j, k;
157
158 if (git_tree_lookup(&(ci->commit_tree), data->repo, git_commit_tree_id(ci->commit)))
159 goto err;
160 if (!git_commit_parent(&(ci->parent), ci->commit, 0)) {
161 if (git_tree_lookup(&(ci->parent_tree), data->repo, git_commit_tree_id(ci->parent))) {
162 ci->parent = NULL;
163 ci->parent_tree = NULL;
164 }
165 }
166
167 git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
168 opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH |
169 GIT_DIFF_IGNORE_SUBMODULES |
170 GIT_DIFF_INCLUDE_TYPECHANGE;
171 if (git_diff_tree_to_tree(&(ci->diff), data->repo, ci->parent_tree, ci->commit_tree, &opts))
172 goto err;
173
174 if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION))
175 goto err;
176 /* find renames and copies, exact matches (no heuristic) for renames. */
177 fopts.flags |= GIT_DIFF_FIND_RENAMES |
178 GIT_DIFF_FIND_COPIES |
179 GIT_DIFF_FIND_EXACT_MATCH_ONLY;
180 if (git_diff_find_similar(ci->diff, &fopts))
181 goto err;
182
183 ndeltas = git_diff_num_deltas(ci->diff);
184 if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct DeltaInfo *))))
185 err(1, "calloc");
186
187 for (i = 0; i < ndeltas; i++) {
188 if (git_patch_from_diff(&patch, ci->diff, i))
189 goto err;
190
191 if (!(di = calloc(1, sizeof(struct DeltaInfo))))
192 err(1, "calloc");
193 di->patch = patch;
194 ci->deltas[i] = di;
195
196 delta = git_patch_get_delta(patch);
197
198 /* skip stats for binary data */
199 if (delta->flags & GIT_DIFF_FLAG_BINARY)
200 continue;
201
202 nhunks = git_patch_num_hunks(patch);
203 for (j = 0; j < nhunks; j++) {
204 if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
205 break;
206 for (k = 0; ; k++) {
207 if (git_patch_get_line_in_hunk(&line, patch, j, k))
208 break;
209 if (line->old_lineno == -1) {
210 di->add_count++;
211 ci->add_count++;
212 } else if (line->new_lineno == -1) {
213 di->del_count++;
214 ci->del_count++;
215 }
216 }
217 }
218 }
219 ci->ndeltas = i;
220 ci->file_count = i;
221
222 return 0;
223
224 err:
225 git_diff_free(ci->diff);
226 ci->diff = NULL;
227 git_tree_free(ci->commit_tree);
228 ci->commit_tree = NULL;
229 git_tree_free(ci->parent_tree);
230 ci->parent_tree = NULL;
231 git_commit_free(ci->parent);
232 ci->parent = NULL;
233
234 if (ci->deltas)
235 for (i = 0; i < ci->ndeltas; i++)
236 delta_info_free(ci->deltas[i]);
237 free(ci->deltas);
238 ci->deltas = NULL;
239 ci->ndeltas = 0;
240 ci->add_count = 0;
241 ci->del_count = 0;
242 ci->file_count = 0;
243
244 return -1;
245 }
246
247 void
248 commit_info_free(struct CommitInfo *ci)
249 {
250 size_t i;
251
252 if (!ci)
253 return;
254 if (ci->deltas)
255 for (i = 0; i < ci->ndeltas; i++)
256 delta_info_free(ci->deltas[i]);
257
258 free(ci->deltas);
259 git_diff_free(ci->diff);
260 git_tree_free(ci->commit_tree);
261 git_tree_free(ci->parent_tree);
262 git_commit_free(ci->commit);
263 git_commit_free(ci->parent);
264 memset(ci, 0, sizeof(*ci));
265 free(ci);
266 }
267
268 struct CommitInfo *
269 commit_info_get_by_oid(const git_oid *id)
270 {
271 struct CommitInfo *ci;
272
273 if (!(ci = calloc(1, sizeof(struct CommitInfo))))
274 err(1, "calloc");
275
276 if (git_commit_lookup(&(ci->commit), data->repo, id))
277 goto err;
278 ci->id = id;
279
280 git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
281 git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
282
283 ci->author = git_commit_author(ci->commit);
284 ci->committer = git_commit_committer(ci->commit);
285 ci->summary = git_commit_summary(ci->commit);
286 ci->msg = git_commit_message(ci->commit);
287
288 return ci;
289
290 err:
291 commit_info_free(ci);
292
293 return NULL;
294 }
295
296 int
297 refs_cmp(const void *v1, const void *v2)
298 {
299 const struct ReferenceInfo *r1 = v1, *r2 = v2;
300 time_t t1, t2;
301 int r;
302
303 if ((r = git_reference_is_tag(r1->ref) - git_reference_is_tag(r2->ref)))
304 return r;
305
306 t1 = r1->ci->author ? r1->ci->author->when.time : 0;
307 t2 = r2->ci->author ? r2->ci->author->when.time : 0;
308 if ((r = t1 > t2 ? -1 : (t1 == t2 ? 0 : 1)))
309 return r;
310
311 return strcmp(
312 git_reference_shorthand(r1->ref),
313 git_reference_shorthand(r2->ref)
314 );
315 }
316
317 int
318 get_refs(struct ReferenceInfo **pris, size_t *prefcount)
319 {
320 struct ReferenceInfo *ris = NULL;
321 struct CommitInfo *ci = NULL;
322 git_reference_iterator *it = NULL;
323 const git_oid *id = NULL;
324 git_object *obj = NULL;
325 git_reference *dref = NULL, *r, *ref = NULL;
326 size_t i, refcount;
327
328 *pris = NULL;
329 *prefcount = 0;
330
331 if (git_reference_iterator_new(&it, data->repo))
332 return -1;
333
334 for (refcount = 0; !git_reference_next(&ref, it); ) {
335 if (!git_reference_is_branch(ref) && !git_reference_is_tag(ref)) {
336 git_reference_free(ref);
337 ref = NULL;
338 continue;
339 }
340
341 switch (git_reference_type(ref)) {
342 case GIT_REF_SYMBOLIC:
343 if (git_reference_resolve(&dref, ref))
344 goto err;
345 r = dref;
346 break;
347 case GIT_REF_OID:
348 r = ref;
349 break;
350 default:
351 continue;
352 }
353 if (
354 !git_reference_target(r) ||
355 git_reference_peel(&obj, r, GIT_OBJ_ANY)
356 )
357 goto err;
358 if (!(id = git_object_id(obj)))
359 goto err;
360 if (!(ci = commit_info_get_by_oid(id)))
361 break;
362
363 if (!(ris = reallocarray(ris, refcount + 1, sizeof(*ris))))
364 err(1, "realloc");
365 ris[refcount].ci = ci;
366 ris[refcount].ref = r;
367 refcount++;
368
369 git_object_free(obj);
370 obj = NULL;
371 git_reference_free(dref);
372 dref = NULL;
373 }
374 git_reference_iterator_free(it);
375
376 /* sort by type, date then shorthand name */
377 qsort(ris, refcount, sizeof(*ris), refs_cmp);
378
379 *pris = ris;
380 *prefcount = refcount;
381
382 return 0;
383
384 err:
385 git_object_free(obj);
386 git_reference_free(dref);
387 commit_info_free(ci);
388 for (i = 0; i < refcount; i++) {
389 commit_info_free(ris[i].ci);
390 git_reference_free(ris[i].ref);
391 }
392 free(ris);
393
394 return -1;
395 }
396
397 /* Percent-encode, see RFC3986 section 2.1. */
398 void
399 put_percent_char(FILE *fp, unsigned char uc)
400 {
401 /* NOTE: do not encode '/' for paths or ",-." */
402 if (
403 uc < ',' ||
404 uc >= 127 ||
405 (uc >= ':' && uc <= '@') ||
406 uc == '[' ||
407 uc == ']'
408 ) {
409 CPUT(fp, '%');
410 CPUT(fp, tab[(uc >> 4) & 0x0f]);
411 CPUT(fp, tab[uc & 0x0f]);
412 return;
413 }
414
415 CPUT(fp, uc);
416 }
417
418 void
419 put_percent_encoded(FILE *fp, const char *s, size_t len)
420 {
421 size_t i;
422
423 for (i = 0; *s && i < len; s++, i++)
424 put_percent_char(fp, *s);
425 }
426
427 /* Escape characters below as HTML 2.0 / XML 1.0. */
428 void
429 put_xml_char(FILE *fp, const char *s)
430 {
431 switch(*s) {
432 case '<': fputs("<", fp); break;
433 case '>': fputs(">", fp); break;
434 case '\'': fputs("'", fp); break;
435 case '&': fputs("&", fp); break;
436 case '"': fputs(""", fp); break;
437 default: putc(*s, fp);
438 }
439 }
440
441 void
442 put_xml_len(FILE *fp, const char *s, size_t len)
443 {
444 size_t i = 0;
445 for (; *s && i < len; s++, i++)
446 put_xml_char(fp, s);
447 }
448
449 /* terminate on, but don't print '\r' or '\n' */
450 void
451 put_xml_line(FILE *fp, const char *s, size_t len)
452 {
453 size_t i = 0;
454 for (; *s && i < len; s++, i++) {
455 if (*s == '\r' || *s == '\n')
456 break;
457 put_xml_char(fp, s);
458 }
459 }
460
461 void
462 printtimez(FILE *fp, const git_time *intime)
463 {
464 struct tm *intm;
465 time_t t;
466 char out[32];
467
468 t = (time_t)intime->time;
469 if (!(intm = gmtime(&t)))
470 return;
471 strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm);
472 fputs(out, fp);
473 }
474
475 void
476 printtime(FILE *fp, const git_time *intime)
477 {
478 struct tm *intm;
479 time_t t;
480 char out[32];
481
482 t = (time_t)intime->time + (intime->offset * 60);
483 if (!(intm = gmtime(&t)))
484 return;
485 strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm);
486 if (intime->offset < 0) {
487 fprintf(
488 fp,
489 "%s -%02d%02d",
490 out,
491 -(intime->offset) / 60,
492 -(intime->offset) % 60
493 );
494 } else {
495 fprintf(
496 fp,
497 "%s +%02d%02d", out,
498 intime->offset / 60,
499 intime->offset % 60
500 );
501 }
502 }
503
504 void
505 printtimeshort(FILE *fp, const git_time *intime)
506 {
507 struct tm *intm;
508 time_t t;
509 char out[32];
510
511 t = (time_t)intime->time;
512 if (!(intm = gmtime(&t)))
513 return;
514 strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
515 fputs(out, fp);
516 }
517
518 void
519 put_absolute_path(FILE *fp, const char * path)
520 {
521 if (opt.base_url[0] != '\0')
522 fprintf(fp, "/%s", opt.base_url);
523 CPUT(fp, '/');
524 fprintf(fp, PAGE_DEST); /* should have no leading/trailing slashes */
525 fprintf(fp, "/%s", path);
526 }
527
528 void
529 put_header(FILE *fp, const char *title)
530 {
531 fputs(
532 "<!DOCTYPE html>\n"
533 "<html>" "\n"
534 "<head>" "\n"
535 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
536 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
537 "<title>",
538 fp
539 );
540 put_xml(fp, title);
541 if (title[0] && data->repo_name[0])
542 fputs(" - ", fp);
543 put_xml(fp, data->repo_name);
544 if (data->description[0])
545 fputs(" - ", fp);
546 put_xml(fp, data->description);
547 SPUTF(fp, "</title>" "\n");
548
549 SPUTF(fp, "<link rel=\"icon\" type=\"image/png\" href=\"");
550 put_absolute_path(fp, "favicon.png");
551 SPUTF(fp, "\" />" "\n");
552
553 SPUTF(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"");
554 put_xml(fp, data->name);
555 SPUTF(fp, " Atom Feed\" href=\"");
556 put_absolute_path(fp, "atom.xml");
557 SPUTF(fp, "\" />" "\n");
558
559 SPUTF(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"");
560 put_xml(fp, data->name);
561 SPUTF(fp, " Atom Feed (tags)\" href=\"");
562 put_absolute_path(fp, "tags.xml");
563 SPUTF(fp, "\" />" "\n");
564
565 SPUTF(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"");
566 fprintf(fp, STYLE_HREF);
567 SPUTF(fp, "\" />" "\n");
568 SPUTF(fp, "</head>" "\n" "<body>" "\n");
569
570 fprintf(fp, PAGE_HEADER);
571
572 SPUTF(fp, "<table>" "<tr>" "<td><h1>");
573 put_xml(fp, data->repo_name);
574 SPUTF(fp, "</h1></td>" "<td><span class=\"desc\">");
575 put_xml(fp, data->description);
576 SPUTF(fp, "</span></td>" "</tr>");
577 if (data->url[0]) {
578 SPUTF(fp, "<tr class=\"url\">" "<td></td>" "<td>");
579 fprintf(fp, PAGE_ACCESS_LABEL);
580 SPUTF(fp, ": <a href=\"");
581 put_xml(fp, data->url); /* not percent encoded */
582 SPUTF(fp, "\">");
583 put_xml(fp, data->url);
584 SPUTF(fp, "</a></td></tr>");
585 }
586 SPUTF(fp, "<tr>" "<td></td>" "<td>" "\n");
587
588 if (data->readme) {
589 SPUTF(fp, "<a href=\"");
590 put_absolute_path(fp, "files/");
591 fprintf(fp, "%s.html", data->readme);
592 SPUTF(fp, "\">README</a> | ");
593 }
594
595 SPUTF(fp, "<a href=\"");
596 put_absolute_path(fp, "files.html");
597 SPUTF(fp, "\">Files</a> | ");
598
599 SPUTF(fp, "<a href=\"");
600 put_absolute_path(fp, "log.html");
601 SPUTF(fp, "\">Log</a> | ");
602
603 SPUTF(fp, "<a href=\"");
604 put_absolute_path(fp, "refs.html");
605 SPUTF(fp, "\">Refs</a>");
606
607 if (data->submodules) {
608 SPUTF(fp, " | <a href=\"");
609 put_absolute_path(fp, "files/");
610 fprintf(fp, "%s.html", data->submodules);
611 SPUTF(fp, "\">Submodules</a>");
612 }
613
614 if (data->license) {
615 SPUTF(fp, " | <a href=\"");
616 put_absolute_path(fp, "files/");
617 fprintf(fp, "%s.html", data->license);
618 SPUTF(fp, "\">LICENSE</a>");
619 }
620
621 SPUTF(fp, "</td></tr></table>" "\n");
622 SPUTF(fp, "<hr/>" "\n");
623
624 SPUTF(fp, "<div id=\"content\">" "\n");
625 }
626
627 void
628 put_footer(FILE *fp)
629 {
630 SPUTF(fp, "</div>" "\n");
631 fprintf(fp, PAGE_FOOTER);
632 SPUTF(fp, "</body>" "\n" "</html>" "\n");
633 }
634
635 size_t
636 put_file_html(FILE *fp, const git_blob *blob)
637 {
638 size_t n = 0, i, len, prev;
639 const char *number_format =
640 "<a href=\"#l%zu\" class=\"line\" id=\"l%zu\">%7zu </a>";
641 const char *s = git_blob_rawcontent(blob);
642
643 len = git_blob_rawsize(blob);
644 SPUTF(fp, "<pre id=\"blob\">" "\n");
645
646 if (len > 0) {
647 for (i = 0, prev = 0; i < len; i++) {
648 if (s[i] != '\n')
649 continue;
650 n++;
651 fprintf(fp, number_format, n, n, n);
652 put_xml_line(fp, &s[prev], i - prev + 1);
653 SPUTF(fp, "\n");
654 prev = i + 1;
655 }
656 /* trailing data */
657 if ((len - prev) > 0) {
658 n++;
659 fprintf(fp, number_format, n, n, n);
660 put_xml_line(fp, &s[prev], len - prev);
661 }
662 }
663
664 SPUTF(fp, "</pre>" "\n");
665
666 return n;
667 }
668
669 void
670 print_commit(FILE *fp, struct CommitInfo *ci)
671 {
672 SPUTF(fp, "<b>commit</b> <a href=\"");
673 put_absolute_path(fp, "commits/");
674 fprintf(fp, "%s.html", ci->oid);
675 fprintf(fp, "\">%s</a>" "\n", ci->oid);
676
677 if (ci->parentoid[0]) {
678 SPUTF(fp, "<b>parent</b> <a href=\"");
679 put_absolute_path(fp, "commits/");
680 fprintf(fp, "%s.html", ci->parentoid);
681 fprintf(fp, "\">%s</a>" "\n", ci->parentoid);
682 }
683
684 if (ci->author) {
685 SPUTF(fp, "<b>Author:</b> ");
686 put_xml(fp, ci->author->name);
687 SPUTF(fp, " <<a href=\"mailto:");
688 put_xml(fp, ci->author->email); /* not percent encoded */
689 SPUTF(fp, "\">");
690 put_xml(fp, ci->author->email);
691 SPUTF(fp, "</a>>\n<b>Date:</b> ");
692 printtime(fp, &(ci->author->when));
693 CPUT(fp, '\n');
694 }
695 if (ci->msg) {
696 CPUT(fp, '\n');
697 put_xml(fp, ci->msg);
698 CPUT(fp, '\n');
699 }
700 }
701
702 void
703 put_show_file(FILE *fp, struct CommitInfo *ci)
704 {
705 const git_diff_delta *delta;
706 const git_diff_hunk *hunk;
707 const git_diff_line *line;
708 git_patch *patch;
709 size_t nhunks, nhunklines, changed, add, del, total, i, j, k;
710 char linestr[80];
711 int c;
712
713 print_commit(fp, ci);
714
715 if (!ci->deltas)
716 return;
717
718 if (
719 ci->file_count > 1000 ||
720 ci->ndeltas > 1000 ||
721 ci->add_count > 100000 ||
722 ci->del_count > 100000
723 ) {
724 fputs("Diff is too large, output suppressed.\n", fp);
725 return;
726 }
727
728 /* diff stat */
729 fputs("<b>Diffstat:</b>" "\n", fp);
730 fputs("<table id=\"diffstat\">", fp);
731 for (i = 0; i < ci->ndeltas; i++) {
732 delta = git_patch_get_delta(ci->deltas[i]->patch);
733
734 switch (delta->status) {
735 case GIT_DELTA_ADDED: c = 'A'; break;
736 case GIT_DELTA_COPIED: c = 'C'; break;
737 case GIT_DELTA_DELETED: c = 'D'; break;
738 case GIT_DELTA_MODIFIED: c = 'M'; break;
739 case GIT_DELTA_RENAMED: c = 'R'; break;
740 case GIT_DELTA_TYPECHANGE: c = 'T'; break;
741 default: c = ' '; break;
742 }
743 if (c == ' ')
744 fprintf(fp, "<tr><td>%c", c);
745 else
746 fprintf(fp, "<tr><td class=\"%c\">%c", c, c);
747
748 fprintf(fp, "</td><td><a href=\"#h%zu\">", i);
749 put_xml(fp, delta->old_file.path);
750 if (strcmp(delta->old_file.path, delta->new_file.path)) {
751 fputs(" -> ", fp);
752 put_xml(fp, delta->new_file.path);
753 }
754
755 add = ci->deltas[i]->add_count;
756 del = ci->deltas[i]->del_count;
757 changed = add + del;
758 total = sizeof(linestr) - 2;
759 if (changed > total) {
760 if (add)
761 add = ((float)total / changed * add) + 1;
762 if (del)
763 del = ((float)total / changed * del) + 1;
764 }
765 memset(&linestr, '+', add);
766 memset(&linestr[add], '-', del);
767
768 fprintf(
769 fp,
770 "</a></td>"
771 // "<td> | </td>"
772 "<td class=\"num\">%zu</td><td><span class=\"i\">",
773 ci->deltas[i]->add_count + ci->deltas[i]->del_count
774 );
775 fwrite(&linestr, 1, add, fp);
776 fputs("</span><span class=\"d\">", fp);
777 fwrite(&linestr[add], 1, del, fp);
778 fputs("</span></td></tr>\n", fp);
779 }
780 fprintf(
781 fp,
782 "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
783 ci->file_count, ci->file_count == 1 ? "" : "s",
784 ci->add_count, ci->add_count == 1 ? "" : "s",
785 ci->del_count, ci->del_count == 1 ? "" : "s"
786 );
787
788 fputs("<hr/>", fp);
789
790 for (i = 0; i < ci->ndeltas; i++) {
791 patch = ci->deltas[i]->patch;
792 delta = git_patch_get_delta(patch);
793
794 SPUTF(fp, "<b>diff --git a/");
795 put_xml(fp, delta->old_file.path);
796 fprintf(fp, " b/<a id=\"h%zu\" href=\"#h%zu\">", i, i);
797 put_xml(fp, delta->new_file.path);
798 SPUTF(fp, "</a></b>" "\n");
799
800 /* check binary data */
801 if (delta->flags & GIT_DIFF_FLAG_BINARY) {
802 SPUTF(fp, "Binary files differ." "\n");
803 continue;
804 }
805
806 nhunks = git_patch_num_hunks(patch);
807 for (j = 0; j < nhunks; j++) {
808 if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
809 break;
810
811 fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j);
812 put_xml_len(fp, hunk->header, hunk->header_len);
813 SPUTF(fp, "</a>");
814
815 for (k = 0; ; k++) {
816 if (git_patch_get_line_in_hunk(&line, patch, j, k))
817 break;
818 if (line->old_lineno == -1)
819 fprintf(
820 fp,
821 "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+",
822 i, j, k, i, j, k
823 );
824 else if (line->new_lineno == -1)
825 fprintf(
826 fp,
827 "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-",
828 i, j, k, i, j, k
829 );
830 else
831 CPUT(fp, ' ');
832 put_xml_line(fp, line->content, line->content_len);
833 CPUT(fp, '\n');
834 if (line->old_lineno == -1 || line->new_lineno == -1)
835 SPUTF(fp, "</a>");
836 }
837 }
838 }
839 }
840
841 void
842 put_log_line(FILE *fp, struct CommitInfo *ci)
843 {
844 fputs("<tr><td>", fp);
845 if (ci->author)
846 printtimeshort(fp, &(ci->author->when));
847 fputs("</td><td>", fp);
848 if (ci->summary) {
849 SPUTF(fp, "<a href=\"");
850 put_absolute_path(fp, "commits/");
851 fprintf(fp, "%s.html", ci->oid);
852 SPUTF(fp, "\">");
853 put_xml(fp, ci->summary);
854 SPUTF(fp, "</a>");
855 }
856 SPUTF(fp, "</td><td>");
857 if (ci->author) put_xml(fp, ci->author->name);
858 SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
859 fprintf(fp, "%zu", ci->file_count);
860 SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
861 fprintf(fp, "+%zu", ci->add_count);
862 SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
863 fprintf(fp, "-%zu", ci->del_count);
864 SPUTF(fp, "</td></tr>" "\n");
865 }
866
867 void
868 write_commit_page(char *name, struct CommitInfo *ci)
869 {
870 char fname[PATH_MAX];
871 get_page_path(fname, sizeof(fname), opt.out_dir, name);
872 FILE *fp = fopen_w(fname);
873 // [old] fpfile = efopen(path, "w");
874
875 put_header(fp, ci->summary);
876
877 SPUTF(fp, "<pre>");
878 put_show_file(fp, ci);
879 SPUTF(fp, "</pre>" "\n");
880
881 put_footer(fp);
882 check_file_error(fp, fname, 'w');
883 fclose(fp);
884 }
885
886 int
887 put_log(FILE *fp, const git_oid *oid)
888 {
889 struct CommitInfo *ci;
890 git_revwalk *w = NULL;
891 git_oid id;
892 char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
893 size_t remcommits = 0;
894 int r;
895 int commits_left = MAX_COMMITS;
896
897 git_revwalk_new(&w, data->repo);
898 git_revwalk_push(w, oid);
899
900 // TODO avoid processing very large commits
901 while (!git_revwalk_next(&id, w)) {
902 git_oid_tostr(oidstr, sizeof(oidstr), &id);
903 r = snprintf(path, sizeof(path), "commits/%s.html", oidstr);
904 if (r < 0 || (size_t)r >= sizeof(path))
905 errx(1, "path truncated: 'commits/%s.html'", oidstr);
906 r = access(path, F_OK);
907
908 /* optimization: if there are no log lines to write and
909 the commit file already exists: skip the diffstat */
910 if (!commits_left) {
911 remcommits++;
912 if (!r)
913 continue;
914 }
915
916 if (!(ci = commit_info_get_by_oid(&id)))
917 break;
918 /* diffstat: for stagit HTML required for the log.html line */
919 if (commit_info_get_stats(ci) == -1)
920 goto err;
921
922 if (commits_left != 0) {
923 put_log_line(fp, ci);
924 if (commits_left > 0)
925 commits_left--;
926 }
927
928 /* check if file exists if so skip it */
929 if (r) write_commit_page(path, ci);
930
931 err:
932 commit_info_free(ci);
933 }
934 git_revwalk_free(w);
935
936 if (commits_left == 0 && remcommits != 0)
937 fprintf(
938 fp,
939 "<tr><td></td><td colspan=\"5\">"
940 "%zu more commits remaining, fetch the repository"
941 "</td></tr>" "\n",
942 remcommits
943 );
944
945 return 0;
946 }
947
948 void
949 put_log_html(FILE *fp)
950 {
951 put_header(fp, "Log");
952 fputs(
953 "<table id=\"log\"><thead>" "\n"
954 "<tr>"
955 "<td><b>Date</b></td>"
956 "<td><b>Commit message</b></td>"
957 "<td><b>Author</b></td>"
958 "<td class=\"num\" align=\"right\"><b>Files</b></td>"
959 "<td class=\"num\" align=\"right\"><b>+</b></td>"
960 "<td class=\"num\" align=\"right\"><b>-</b></td>"
961 "</tr>" "\n"
962 "</thead><tbody>" "\n",
963 fp
964 );
965
966 if (data->head) put_log(fp, data->head);
967
968 fputs("</tbody></table>", fp);
969 put_footer(fp);
970 }
971
972 void
973 put_repo_index_html(FILE *fp)
974 {
975 put_header(fp, "Repository Index");
976
977 fprintf(fp, REPO_INDEX_CONTENTS);
978 }
979
980
981 void
982 print_commit_atom(FILE *fp, struct CommitInfo *ci, const char *tag)
983 {
984 fputs("<entry>\n", fp);
985
986 fprintf(fp, "<id>%s</id>\n", ci->oid);
987 if (ci->author) {
988 SPUTF(fp, "<published>");
989 printtimez(fp, &(ci->author->when));
990 SPUTF(fp, "</published>\n");
991 }
992 if (ci->committer) {
993 SPUTF(fp, "<updated>");
994 printtimez(fp, &(ci->committer->when));
995 SPUTF(fp, "</updated>\n");
996 }
997 if (ci->summary) {
998 SPUTF(fp, "<title>");
999 if (tag && tag[0]) {
1000 SPUTF(fp, "[");
1001 put_xml(fp, tag);
1002 SPUTF(fp, "] ");
1003 }
1004 put_xml(fp, ci->summary);
1005 SPUTF(fp, "</title>\n");
1006 }
1007 SPUTF(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"");
1008 put_absolute_path(fp, "commits/");
1009 fprintf(fp, "%s.html\" />" "\n", ci->oid);
1010
1011 if (ci->author) {
1012 SPUTF(fp, "<author>" "\n" "<name>");
1013 put_xml(fp, ci->author->name);
1014 SPUTF(fp, "</name>" "\n" "<email>");
1015 put_xml(fp, ci->author->email);
1016 SPUTF(fp, "</email>" "\n" "</author>" "\n");
1017 }
1018
1019 SPUTF(fp, "<content>");
1020 fprintf(fp, "commit %s" "\n", ci->oid);
1021 if (ci->parentoid[0])
1022 fprintf(fp, "parent %s" "\n", ci->parentoid);
1023 if (ci->author) {
1024 SPUTF(fp, "Author: ");
1025 put_xml(fp, ci->author->name);
1026 SPUTF(fp, " <");
1027 put_xml(fp, ci->author->email);
1028 SPUTF(fp, ">" "\n" "Date: ");
1029 printtime(fp, &(ci->author->when));
1030 CPUT(fp, '\n');
1031 }
1032 if (ci->msg) {
1033 CPUT(fp, '\n');
1034 put_xml(fp, ci->msg);
1035 }
1036 SPUTF(fp, "\n" "</content>" "\n" "</entry>" "\n");
1037 }
1038
1039 int
1040 write_atom(FILE *fp, int all)
1041 {
1042 struct ReferenceInfo *ris = NULL;
1043 size_t refcount = 0;
1044 struct CommitInfo *ci;
1045 git_revwalk *w = NULL;
1046 git_oid id;
1047 size_t i, m = 100; /* last 'm' commits */
1048
1049 SPUTF(
1050 fp,
1051 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "\n"
1052 "<feed xmlns=\"http://www.w3.org/2005/Atom\">" "\n" "<title>"
1053 );
1054 put_xml(fp, data->repo_name);
1055 SPUTF(fp, ", branch HEAD</title>" "\n" "<subtitle>");
1056 put_xml(fp, data->description);
1057 SPUTF(fp, "</subtitle>" "\n");
1058
1059 /* all commits or only tags? */
1060 if (all) {
1061 git_revwalk_new(&w, data->repo);
1062 git_revwalk_push_head(w);
1063 for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
1064 if (!(ci = commit_info_get_by_oid(&id)))
1065 break;
1066 print_commit_atom(fp, ci, "");
1067 commit_info_free(ci);
1068 }
1069 git_revwalk_free(w);
1070 } else if (get_refs(&ris, &refcount) != -1) {
1071 /* references: tags */
1072 for (i = 0; i < refcount; i++) {
1073 if (git_reference_is_tag(ris[i].ref))
1074 print_commit_atom(
1075 fp, ris[i].ci,
1076 git_reference_shorthand(ris[i].ref)
1077 );
1078
1079 commit_info_free(ris[i].ci);
1080 git_reference_free(ris[i].ref);
1081 }
1082 free(ris);
1083 }
1084
1085 fputs("</feed>\n", fp);
1086
1087 return 0;
1088 }
1089
1090 void
1091 write_binary_file(const char *filename, git_blob *blob) {
1092 char dest[PATH_MAX];
1093 const char *raw = git_blob_rawcontent(blob);
1094 int size = git_blob_rawsize(blob);
1095
1096 /* set dest */
1097 join_path(dest, PATH_MAX, "files", filename);
1098
1099 /* write the file! */
1100 char fname[PATH_MAX];
1101 get_page_path(fname, sizeof(fname), opt.out_dir, dest);
1102 FILE *fp = fopen_w(fname);
1103
1104 fwrite(raw, size, 1, fp);
1105 check_file_error(fp, fname, 'w');
1106 fclose(fp);
1107 }
1108
1109 void
1110 put_size(FILE *fp, size_t size, int fixed)
1111 {
1112 int order = 0;
1113 int decimal;
1114
1115 if (size == 0) {
1116 fprintf(fp, " - ");
1117 return;
1118 }
1119
1120 while (size > 1000) {
1121 order++;
1122 decimal = (size / 100) % 10;
1123 size /= 1000;
1124 }
1125
1126 if (size < 10 && order > 0) fprintf(fp, "%1zu.%1d", size, decimal);
1127 else fprintf(fp, fixed ? "%3zu" : "%zu", size);
1128
1129 if (order != 0 || fixed) fprintf(fp, "%c", orders[order]);
1130 }
1131
1132 void
1133 put_file_title(FILE *fp, const char *name, int is_dir)
1134 {
1135 int i = 0;
1136 int len = 0;
1137
1138 /* project root */
1139 if (name[0] == '\0')
1140 return;
1141
1142 if (!DIR_PAGES) {
1143 fprintf(fp, "<p>%s</p>", name);
1144 return;
1145 }
1146
1147 if (!is_dir) {
1148 for (i = 0; name[i] != '\0'; i++)
1149 if (name[i] == '/') len = i;
1150
1151 if (len != 0) {
1152 SPUTF(fp, "<p><a href=\"");
1153 put_absolute_path(fp, "files/");
1154 for (i = 0; i < len; i++) put_percent_char(fp, name[i]);
1155 SPUTF(fp, ".html\">");
1156 for (i = 0; i < len; i++) put_percent_char(fp, name[i]);
1157 fprintf(fp, "</a>%s</p>", name + len);
1158 return;
1159 }
1160 }
1161
1162
1163 fprintf(fp, "<p>%s</p>", name);
1164 }
1165
1166 void
1167 put_binary_blob_stub(FILE *fp, const char *file_url)
1168 {
1169 SPUTF(fp, "<p>Binary file: <a href=\"");
1170 put_absolute_path(fp, "files/");
1171 fprintf(fp, "%s\">raw</a></p>" "\n", file_url);
1172 SPUTF(fp, "<object data=\"");
1173 put_absolute_path(fp, "files/");
1174 fprintf(fp, "%s\">", file_url);
1175 SPUTF(fp, "no preview available.</object></p>" "\n");
1176 }
1177
1178 struct Weight
1179 write_blob(
1180 git_blob *blob,
1181 char *filename,
1182 const char *entrypath,
1183 const char *name
1184 ) {
1185 char fname[PATH_MAX];
1186 FILE *fp;
1187 struct Weight ret;
1188
1189 ret.lines = 0;
1190 ret.bytes = 0;
1191
1192 get_page_path(fname, sizeof(fname), opt.out_dir, filename);
1193 fp = fopen_w(fname);
1194
1195 put_header(fp, name);
1196 put_file_title(fp, entrypath, 0);
1197
1198 if (git_blob_is_binary(blob)) {
1199 put_binary_blob_stub(fp, entrypath); // name);
1200 write_binary_file(entrypath, blob);
1201 ret.bytes = git_blob_rawsize(blob);
1202 } else {
1203 ret.lines = put_file_html(fp, blob);
1204 }
1205
1206 put_footer(fp);
1207 check_file_error(fp, fname, 'w');
1208 fclose(fp);
1209
1210 return ret;
1211 }
1212
1213 char
1214 get_filemode_type(git_filemode_t m)
1215 {
1216 if (S_ISREG(m)) return '-';
1217 if (S_ISBLK(m)) return 'b';
1218 if (S_ISCHR(m)) return 'c';
1219 if (S_ISDIR(m)) return 'd';
1220 if (S_ISFIFO(m)) return 'p';
1221 if (S_ISLNK(m)) return 'l';
1222 if (S_ISSOCK(m)) return 's';
1223 return '?';
1224 }
1225
1226 const char *
1227 get_filemode(const git_tree_entry *entry)
1228 {
1229 git_filemode_t mode;
1230 static char ret[11];
1231
1232 /* used for backlink: .. */
1233 if (entry == NULL) return "d---------";
1234
1235 mode = git_tree_entry_filemode(entry);
1236
1237 memset(ret, '-', sizeof(ret) - 1);
1238 ret[0] = get_filemode_type(mode);
1239 if (mode & S_IRUSR) ret[1] = 'r';
1240 if (mode & S_IWUSR) ret[2] = 'w';
1241 if (mode & S_IXUSR) ret[3] = 'x';
1242 if (mode & S_ISUID) ret[3] = (ret[3] == 'x') ? 's' : 'S';
1243 if (mode & S_IRGRP) ret[4] = 'r';
1244 if (mode & S_IWGRP) ret[5] = 'w';
1245 if (mode & S_IXGRP) ret[6] = 'x';
1246 if (mode & S_ISGID) ret[6] = (ret[6] == 'x') ? 's' : 'S';
1247 if (mode & S_IROTH) ret[7] = 'r';
1248 if (mode & S_IWOTH) ret[8] = 'w';
1249 if (mode & S_IXOTH) ret[9] = 'x';
1250 if (mode & S_ISVTX) ret[9] = (ret[9] == 'x') ? 't' : 'T';
1251 ret[10] = '\0';
1252
1253 return ret;
1254 }
1255
1256 void
1257 put_filetree_file_line(
1258 FILE *fp,
1259 git_object *obj,
1260 const git_tree_entry *entry,
1261 char *filename,
1262 char *entrypath,
1263 const char *name,
1264 struct Weight weight
1265 ) {
1266 SPUTF(fp, "<tr><td>");
1267 SPUTF(fp, get_filemode(entry));
1268 SPUTF(fp, "</td><td><a href=\"");
1269 put_absolute_path(fp, "");
1270 put_percent_encoded(fp, filename, strlen(filename));
1271 SPUTF(fp, "\">");
1272 if (DIR_PAGES) put_xml(fp, name);
1273 else put_xml(fp, entrypath);
1274 SPUTF(fp, "</a></td><td class=\"num\" align=\"right\">");
1275 put_size(fp, weight.lines, 1);
1276 SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
1277 put_size(fp, weight.bytes, 1);
1278 SPUTF(fp, "</td></tr>\n");
1279 }
1280
1281 void
1282 put_filetree_dir_line(
1283 FILE *fp,
1284 const git_tree_entry *entry,
1285 char *destination,
1286 const char *title,
1287 size_t lines,
1288 size_t bytes
1289 ) {
1290 SPUTF(fp, "<tr><td>");
1291 SPUTF(fp, get_filemode(entry));
1292 SPUTF(fp, "</td><td><a href=\"");
1293 put_absolute_path(fp, "");
1294 put_percent_encoded(fp, destination, strlen(destination));
1295 SPUTF(fp, "\">");
1296 put_xml(fp, title);
1297 SPUTF(fp, "</a></td><td class=\"num\" align=\"right\">");
1298 put_size(fp, lines, 1);
1299 SPUTF(fp, "</td><td class=\"num\" align=\"right\">");
1300 put_size(fp, bytes, 1);
1301 SPUTF(fp, "</td></tr>\n");
1302 }
1303
1304 void put_filetree_backlink_line(FILE *fp, const char *path) {
1305 char previous_path[PATH_MAX];
1306 char previous_filename[PATH_MAX];
1307 int i, r, last_slash = 0;
1308
1309 /* figure out previous directory */
1310 // TODO how to do this more cleanly...
1311 for (i = 0; path[i] != '\0'; i++) {
1312 previous_path[i] = path[i];
1313 if (path[i] == '/') last_slash = i;
1314 }
1315 previous_path[last_slash] = '\0';
1316
1317 if (last_slash == 0) {
1318 put_filetree_dir_line(fp, NULL, "files.html", "..", 0, 0);
1319 } else {
1320 r = snprintf(previous_filename, sizeof(previous_filename), "files/%s.html", previous_path);
1321 if (r < 0 || (size_t) r >= sizeof(previous_filename))
1322 errx(1, "path truncated: 'files/%s.html'", previous_path);
1323 put_filetree_dir_line(fp, NULL, previous_filename, "..", 0, 0);
1324 }
1325 }
1326
1327 /* need this here to allow recursion */
1328 struct Weight write_filetree(
1329 const char *path,
1330 const char *filename,
1331 git_tree *tree
1332 );
1333 struct Weight put_file_list(FILE *fp, git_tree *tree, const char *path);
1334
1335 struct Weight
1336 put_entry_obj(FILE *fp, git_object *obj, const git_tree_entry *entry, char *entrypath, const char *name)
1337 {
1338 struct Weight ret;
1339 git_object_t obj_type = git_object_type(obj);
1340 char filename[PATH_MAX];
1341 int r;
1342
1343 /* filename = f("files/%s.html", entrypath) */
1344 r = snprintf(filename, sizeof(filename), "files/%s.html", entrypath);
1345 if (r < 0 || (size_t) r >= sizeof(filename))
1346 errx(1, "path truncated: 'files/%s.html'", entrypath);
1347
1348 switch (obj_type) {
1349 case GIT_OBJ_TREE:
1350 if (DIR_PAGES) {
1351 /* create dir page */
1352 ret = write_filetree(entrypath, filename, (git_tree *) obj);
1353
1354 /* add link to that dir page */
1355 put_filetree_dir_line(fp, entry, filename, name, ret.lines, ret.bytes);
1356 } else {
1357 /* list files on this page */
1358 ret = put_file_list(fp, (git_tree *) obj, entrypath);
1359 }
1360 break;
1361 case GIT_OBJ_BLOB:
1362 ret = write_blob((git_blob *) obj, filename, entrypath, name);
1363 put_filetree_file_line(fp, obj, entry, filename, entrypath, name, ret);
1364 break;
1365 default: break;
1366 }
1367
1368 return ret;
1369 }
1370
1371 void
1372 put_submodule_obj(FILE *fp, const git_tree_entry *entry, const char *name)
1373 {
1374 char oid[8];
1375
1376 SPUTF(fp, "<tr>" "<td>m---------</td>" "<td><a href=\"");
1377 put_absolute_path(fp, "files/.gitmodules.html");
1378 SPUTF(fp, "\">");
1379 put_xml(fp, name);
1380 SPUTF(fp, "</a> @ ");
1381 git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry));
1382 put_xml(fp, oid);
1383 SPUTF(fp, "</td>" "<td class=\"num\" align=\"right\"></td>" "\n");
1384 SPUTF(fp, "<td class=\"num\" align=\"right\"></td>" "</tr>" "\n");
1385 }
1386
1387 struct Weight
1388 put_file_list(FILE *fp, git_tree *tree, const char *path)
1389 {
1390 struct Weight ret;
1391 size_t count;
1392 size_t i;
1393
1394 ret.lines = ret.bytes = 0;
1395
1396 count = git_tree_entrycount(tree);
1397 for (i = 0; i < count; i++) {
1398 const git_tree_entry *entry = NULL;
1399 const char *name;
1400 char entrypath[PATH_MAX];
1401 git_object *obj = NULL;
1402
1403 if (
1404 !(entry = git_tree_entry_byindex(tree, i)) ||
1405 !(name = git_tree_entry_name(entry))
1406 )
1407 return ret;
1408
1409 combine_paths(entrypath, path, name);
1410
1411 if (!git_tree_entry_to_object(&obj, data->repo, entry)) {
1412 struct Weight weight =
1413 put_entry_obj(fp, obj, entry, entrypath, name);
1414 ret.bytes += weight.bytes;
1415 ret.lines += weight.lines;
1416 git_object_free(obj);
1417 continue;
1418 }
1419
1420 if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) {
1421 put_submodule_obj(fp, entry, name);
1422 continue;
1423 }
1424 }
1425
1426 return ret;
1427 }
1428
1429 struct Weight
1430 write_filetree(const char *path, const char *filename, git_tree *tree)
1431 {
1432 struct Weight ret;
1433 char fname[PATH_MAX] = "";
1434 FILE *fp;
1435
1436 get_page_path(fname, sizeof(fname), opt.out_dir, (char *) filename);
1437 fp = fopen_w(fname);
1438
1439 put_header(fp, "Files");
1440
1441 put_file_title(fp, path, 1);
1442
1443 SPUTF(
1444 fp,
1445 "<table id=\"files\"><thead>" "\n" "<tr>"
1446 "<td><b>Mode</b></td><td><b>Name</b></td>"
1447 "<td class=\"num\" align=\"right\"><b> L </b></td>"
1448 "<td class=\"num\" align=\"right\"><b> B </b></td>"
1449 "</tr>" "\n" "</thead><tbody>" "\n"
1450 );
1451
1452 if (tree != NULL) {
1453 if (path[0] != '\0')
1454 put_filetree_backlink_line(fp, path);
1455
1456 ret = put_file_list(fp, tree, path);
1457 }
1458
1459 SPUTF(fp, "</tbody></table>");
1460
1461 // TODO how can this get printed before the contents...
1462 /*
1463 SPUTF(fp, "<p>Totals: ");
1464 put_size(fp, ret.lines, 0);
1465 SPUTF(fp, "L ");
1466 put_size(fp, ret.bytes, 0);
1467 SPUTF(fp, "B</p>");
1468 */
1469
1470 put_footer(fp);
1471 check_file_error(fp, fname, 'w');
1472 fclose(fp);
1473
1474 return ret;
1475 }
1476
1477 void
1478 walk_git_tree()
1479 {
1480 git_tree *tree = NULL;
1481 git_commit *commit = NULL;
1482 int has_files;
1483
1484 if (!data->head) return;
1485
1486 has_files = !git_commit_lookup(&commit, data->repo, data->head) &&
1487 !git_commit_tree(&tree, commit);
1488
1489 write_filetree("", "files.html", has_files ? tree : NULL);
1490
1491 git_commit_free(commit);
1492 git_tree_free(tree);
1493 }
1494
1495 int
1496 put_refs(FILE *fp)
1497 {
1498 struct ReferenceInfo *ris = NULL;
1499 struct CommitInfo *ci;
1500 size_t count, i, j, refcount;
1501 const char *titles[] = { "Branches", "Tags" };
1502 const char *ids[] = { "branches", "tags" };
1503 const char *s;
1504
1505 if (get_refs(&ris, &refcount) == -1)
1506 return -1;
1507
1508 for (i = 0, j = 0, count = 0; i < refcount; i++) {
1509 if (j == 0 && git_reference_is_tag(ris[i].ref)) {
1510 if (count)
1511 fputs("</tbody></table><br/>\n", fp);
1512 count = 0;
1513 j = 1;
1514 }
1515
1516 /* print header if it has an entry (first). */
1517 if (++count == 1)
1518 fprintf(
1519 fp,
1520 "<h2>%s</h2><table id=\"%s\">"
1521 "<thead>\n<tr><td><b>Name</b></td>"
1522 "<td><b>Last commit date</b></td>"
1523 "<td><b>Author</b></td>\n</tr>\n"
1524 "</thead><tbody>\n",
1525 titles[j],
1526 ids[j]
1527 );
1528
1529 ci = ris[i].ci;
1530 s = git_reference_shorthand(ris[i].ref);
1531
1532 fputs("<tr><td>", fp);
1533 put_xml(fp, s);
1534 fputs("</td><td>", fp);
1535 if (ci->author)
1536 printtimeshort(fp, &(ci->author->when));
1537 fputs("</td><td>", fp);
1538 if (ci->author)
1539 put_xml(fp, ci->author->name);
1540 fputs("</td></tr>\n", fp);
1541 }
1542
1543 if (count)
1544 fputs("</tbody></table><br/>\n", fp);
1545
1546 for (i = 0; i < refcount; i++) {
1547 commit_info_free(ris[i].ci);
1548 git_reference_free(ris[i].ref);
1549 }
1550 free(ris);
1551
1552 return 0;
1553 }
1554
1555 void
1556 write_repo_index_page()
1557 {
1558 FILE *fp;
1559 char fname[PATH_MAX];
1560
1561 get_page_path(fname, sizeof(fname), opt.out_dir, "index.html");
1562 fp = fopen_w(fname);
1563
1564 put_repo_index_html(fp);
1565
1566 check_file_error(fp, fname, 'w');
1567 fclose(fp);
1568 }
1569
1570 void
1571 write_log_page()
1572 {
1573 FILE *fp;
1574 char fname[PATH_MAX];
1575
1576 get_page_path(fname, sizeof(fname), opt.out_dir, "log.html");
1577 fp = fopen_w(fname);
1578
1579 put_log_html(fp);
1580
1581 check_file_error(fp, fname, 'w');
1582 fclose(fp);
1583 }
1584
1585 void
1586 write_refs_page()
1587 {
1588 FILE *fp;
1589 char fname[PATH_MAX];
1590
1591 get_page_path(fname, sizeof(fname), opt.out_dir, "refs.html");
1592 fp = fopen_w(fname);
1593
1594 put_header(fp, "Refs");
1595 put_refs(fp);
1596 put_footer(fp);
1597
1598 check_file_error(fp, fname, 'w');
1599 fclose(fp);
1600 }
1601
1602 void
1603 write_feed()
1604 {
1605 FILE *fp;
1606 char fname[PATH_MAX];
1607
1608 get_page_path(fname, sizeof(fname), opt.out_dir, "atom.xml");
1609 fp = fopen_w(fname);
1610
1611 write_atom(fp, 1);
1612
1613 check_file_error(fp, fname, 'w');
1614 fclose(fp);
1615 }
1616
1617 void
1618 write_releases_feed()
1619 {
1620 FILE *fp;
1621 char fname[PATH_MAX];
1622
1623 get_page_path(fname, sizeof(fname), opt.out_dir, "tags.xml");
1624 fp = fopen_w(fname);
1625
1626 write_atom(fp, 0);
1627
1628 check_file_error(fp, fname, 'w');
1629 fclose(fp);
1630 }
1631
1632 int
1633 write_repo_pages(const char* dir)
1634 {
1635 data = alloc_repo_data(dir);
1636 if (data == NULL) return 1;
1637
1638 if (opt.index) {
1639 put_index_log(index_fp, data);
1640 }
1641
1642 if (opt.pages) {
1643 write_repo_index_page(data);
1644 write_log_page(data);
1645 walk_git_tree(data);
1646 write_refs_page(data);
1647 write_feed(data);
1648 write_releases_feed(data);
1649 }
1650
1651 free_repo_data(data);
1652
1653 return 0;
1654 }
1655
1656 void
1657 begin_index()
1658 {
1659 printf("Writing index page..." "\n");
1660 get_index_page_path(
1661 index_fname,
1662 sizeof(index_fname),
1663 opt.out_dir,
1664 INDEX_DEST
1665 );
1666 index_fp = fopen_w(index_fname);
1667 put_index_header(index_fp);
1668 }
1669
1670 void
1671 end_index()
1672 {
1673 put_index_footer(index_fp);
1674 check_file_error(index_fp, index_fname, 'w');
1675 fclose(index_fp);
1676 }
1677
1678 void
1679 init_lib_git()
1680 {
1681 int i;
1682
1683 /* do not search outside the git repository:
1684 GIT_CONFIG_LEVEL_APP is the highest level currently */
1685 git_libgit2_init();
1686 for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
1687 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
1688
1689 /* do not require the git repository to be owned by the current user */
1690 git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
1691 }
1692
1693 int
1694 main(int argc, const char *argv[])
1695 {
1696 int i, result;
1697
1698 if (parse_opts(&opt, argc, argv) == OPT_FAIL) {
1699 PRINT_USAGE(argv[0]);
1700 return 1;
1701 }
1702
1703 if (!opt.pages && !opt.index) {
1704 printf("Nothing to do, use -i and/or -p." "\n\n");
1705 PRINT_USAGE(argv[0]);
1706 return 1;
1707 }
1708
1709 printf("Opening repository..." "\n");
1710 init_lib_git();
1711
1712 /* actually write all the pages! */
1713 if (opt.index) begin_index();
1714 printf("Writing repositories..." "\n");
1715 for (i = 0; i < opt.repo_count; i++) {
1716 printf("%d/%d...", i+1, opt.repo_count); fflush(stdout);
1717 result = write_repo_pages(opt.repo_dirs[i]);
1718 if(result > 0) return 1;
1719 }
1720 printf("\n");
1721 if (opt.index) end_index();
1722
1723 git_libgit2_shutdown();
1724 printf("All done!" "\n");
1725
1726 return 0;
1727 }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.