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