free libevent event-base memory when gotwebd exits to avoid leak report
[got-portable.git] / gotwebd / pages.tmpl
blobe57cc72aae7755e3e7225bc2152dc0db79b0fe2e
1 {!
2 /*
3  * Copyright (c) 2022 Omar Polo <op@openbsd.org>
4  * Copyright (c) 2016, 2019, 2020-2022 Tracey Emery <tracey@traceyemery.net>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
19 #include "got_compat.h"
21 #include <sys/types.h>
22 #include <sys/queue.h>
23 #include <sys/stat.h>
25 #include <ctype.h>
26 #include <event.h>
27 #include <stdint.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <imsg.h>
33 #include "got_error.h"
34 #include "got_object.h"
35 #include "got_reference.h"
37 #include "gotwebd.h"
38 #include "log.h"
39 #include "tmpl.h"
41 enum gotweb_ref_tm {
42         TM_DIFF,
43         TM_LONG,
46 static int breadcumbs(struct template *);
47 static int datetime(struct template *, time_t, int);
48 static int gotweb_render_blob_line(struct template *, const char *, size_t);
49 static int gotweb_render_tree_item(struct template *, struct got_tree_entry *);
50 static int blame_line(struct template *, const char *, struct blame_line *,
51     int, int);
53 static inline int gotweb_render_more(struct template *, int);
55 static inline int tree_listing(struct template *);
56 static inline int diff_line(struct template *, char *);
57 static inline int tag_item(struct template *, struct repo_tag *);
58 static inline int branch(struct template *, struct got_reflist_entry *);
59 static inline int rss_tag_item(struct template *, struct repo_tag *);
60 static inline int rss_author(struct template *, char *);
62 static inline char *
63 nextsep(char *s, char **t)
65         char *q;
67         while (*s == '/')
68                 s++;
69         *t = s;
70         if (*s == '\0')
71                 return NULL;
73         q = strchr(s, '/');
74         if (q == NULL)
75                 q = strchr(s, '\0');
76         return q;
81 {{ define datetime(struct template *tp, time_t t, int fmt) }}
83         struct tm        tm;
84         char             rfc3339[64];
85         char             datebuf[64];
87         if (gmtime_r(&t, &tm) == NULL)
88                 return -1;
90         if (strftime(rfc3339, sizeof(rfc3339), "%FT%TZ", &tm) == 0)
91                 return -1;
93         if (fmt != TM_DIFF && asctime_r(&tm, datebuf) == NULL)
94                 return -1;
96 <time datetime="{{ rfc3339 }}">
97   {{ if fmt == TM_DIFF }}
98     {{ render gotweb_render_age(tp, t) }}
99   {{ else }}
100     {{ datebuf }} {{ " UTC" }}
101   {{ end }}
102 </time>
103 {{ end }}
105 {{ define breadcumbs(struct template *tp) }}
107         struct request          *c = tp->tp_arg;
108         struct querystring      *qs = c->t->qs;
109         struct gotweb_url        url;
110         const char              *folder = qs->folder;
111         const char              *action = "tree";
112         char                    *t, *s = NULL, *dir = NULL;
113         char                     ch;
115         memset(&url, 0, sizeof(url));
116         url.index_page = -1;
117         url.action = TREE;
118         url.path = qs->path;
119         url.commit = qs->commit;
121         if (qs->action != TREE && qs->action != BLOB) {
122                 action = gotweb_action_name(qs->action);
123                 url.action = qs->action;
124         }
126         if (folder && *folder != '\0') {
127                 while (*folder == '/')
128                         folder++;
129                 dir = strdup(folder);
130                 if (dir == NULL)
131                         return (-1);
132                 s = dir;
133         }
135   {{ " / " }}
136   <a href="{{ render gotweb_render_url(c, &url) }}">{{ action }}</a>
137   {{ " / " }}
138   {{ if dir }}
139     {{ while (s = nextsep(s, &t)) != NULL }}
140       {!
141         ch = *s;
142         *s = '\0';
143         url.folder = dir;
144       !}
146       <a href="{{ render gotweb_render_url(c, &url) }}">
147         {{ t }}
148       </a>
149       {{ " / " }}
151       {! *s = ch; !}
152     {{ end }}
153   {{ end }}
155   {{ if qs->file }}
156     {{ qs->file }}
157   {{ end}}
159   {{ finally }}
160   {! free(dir); !}
161 {{ end }}
163 {{ define gotweb_render_page(struct template *tp,
164     int (*body)(struct template *)) }}
166         struct request          *c = tp->tp_arg;
167         struct server           *srv = c->srv;
168         struct querystring      *qs = c->t->qs;
169         struct gotweb_url        u_path;
170         const char              *prfx = c->document_uri;
171         const char              *css = srv->custom_css;
173         memset(&u_path, 0, sizeof(u_path));
174         u_path.index_page = -1;
175         u_path.action = SUMMARY;
177 <!doctype html>
178 <html>
179   <head>
180     <meta charset="utf-8" />
181     <title>{{ srv->site_name }}</title>
182     <meta name="viewport" content="initial-scale=1.0" />
183     <meta name="msapplication-TileColor" content="#da532c" />
184     <meta name="theme-color" content="#ffffff"/>
185     <link rel="apple-touch-icon" sizes="180x180" href="{{ prfx }}apple-touch-icon.png" />
186     <link rel="icon" type="image/png" sizes="32x32" href="{{ prfx }}favicon-32x32.png" />
187     <link rel="icon" type="image/png" sizes="16x16" href="{{ prfx }}favicon-16x16.png" />
188     <link rel="manifest" href="{{ prfx }}site.webmanifest"/>
189     <link rel="mask-icon" href="{{ prfx }}safari-pinned-tab.svg" />
190     <link rel="stylesheet" type="text/css" href="{{ prfx }}{{ css }}" />
191   </head>
192   <body>
193     <header id="header">
194       <div id="got_link">
195         <a href="{{ srv->logo_url }}" target="_blank">
196           <img src="{{ prfx }}{{ srv->logo }}" />
197         </a>
198       </div>
199     </header>
200     <nav id="site_path">
201       <div id="site_link">
202         <a href="?index_page={{ printf "%d", qs->index_page }}">
203           {{ srv->site_link }}
204         </a>
205         {{ if qs->path }}
206           {! u_path.path = qs->path; !}
207           {{ " / " }}
208           <a href="{{ render gotweb_render_url(tp->tp_arg, &u_path)}}">
209             {{ qs->path }}
210           </a>
211         {{ end }}
212         {{ if qs->action == SUMMARY || qs->action == DIFF ||
213               qs->action == TAG || qs->action == TAGS }}
214           {{ " / " }}{{ gotweb_action_name(qs->action) }}
215         {{ else if qs->action != INDEX}}
216           {{ render breadcumbs(tp) }}
217         {{ end }}
218       </div>
219     </nav>
220     <main class="action-{{ gotweb_action_name(qs->action) }}">
221       {{ render body(tp) }}
222     </main>
223     <footer id="site_owner_wrapper">
224       <p id="site_owner">
225         {{ if srv->show_site_owner }}
226           {{ srv->site_owner }}
227         {{ end }}
228       </p>
229     </footer>
230   </body>
231 </html>
232 {{ end }}
234 {{ define gotweb_render_error(struct template *tp) }}
236         struct request          *c = tp->tp_arg;
237         struct transport        *t = c->t;
239 <div id="err_content">
240   {{ if t->error }}
241     {{ t->error->msg }}
242   {{ else }}
243     See daemon logs for details
244   {{ end }}
245 </div>
246 {{ end }}
248 {{ define gotweb_render_repo_table_hdr(struct template *tp) }}
250         struct request *c = tp->tp_arg;
251         struct server *srv = c->srv;
253 <div id="index_header">
254   <div class="index_project">
255     Project
256   </div>
257   {{ if srv->show_repo_description }}
258     <div class="index_project_description">
259       Description
260     </div>
261   {{ end }}
262   {{ if srv->show_repo_owner }}
263     <div class="index_project_owner">
264       Owner
265     </div>
266   {{ end }}
267   {{ if srv->show_repo_age }}
268     <div class="index_project_age">
269       Last Change
270     </div>
271   {{ end }}
272 </div>
273 {{ end }}
275 {{ define gotweb_render_repo_fragment(struct template *tp, struct repo_dir *repo_dir) }}
277         struct request *c = tp->tp_arg;
278         struct server *srv = c->srv;
279         struct gotweb_url summary = {
280                 .action = SUMMARY,
281                 .index_page = -1,
282                 .path = repo_dir->name,
283         }, briefs = {
284                 .action = BRIEFS,
285                 .index_page = -1,
286                 .path = repo_dir->name,
287         }, commits = {
288                 .action = COMMITS,
289                 .index_page = -1,
290                 .path = repo_dir->name,
291         }, tags = {
292                 .action = TAGS,
293                 .index_page = -1,
294                 .path = repo_dir->name,
295         }, tree = {
296                 .action = TREE,
297                 .index_page = -1,
298                 .path = repo_dir->name,
299         }, rss = {
300                 .action = RSS,
301                 .index_page = -1,
302                 .path = repo_dir->name,
303         };
305 <div class="index_wrapper">
306   <div class="index_project">
307     <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">{{ repo_dir->name }}</a>
308   </div>
309   {{ if srv->show_repo_description }}
310     <div class="index_project_description">
311       {{ repo_dir->description }}
312     </div>
313   {{ end }}
314   {{ if srv->show_repo_owner }}
315     <div class="index_project_owner">
316       {{ repo_dir->owner }}
317     </div>
318   {{ end }}
319   {{ if srv->show_repo_age }}
320     <div class="index_project_age">
321       {{ render datetime(tp, repo_dir->age, TM_DIFF) }}
322     </div>
323   {{ end }}
324   <div class="navs_wrapper">
325     <div class="navs">
326       <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">summary</a>
327       {{ " | " }}
328       <a href="{{ render gotweb_render_url(tp->tp_arg, &briefs) }}">briefs</a>
329       {{ " | " }}
330       <a href="{{ render gotweb_render_url(tp->tp_arg, &commits) }}">commits</a>
331       {{ " | " }}
332       <a href="{{ render gotweb_render_url(tp->tp_arg, &tags) }}">tags</a>
333       {{ " | " }}
334       <a href="{{ render gotweb_render_url(tp->tp_arg, &tree) }}">tree</a>
335       {{ " | " }}
336       <a href="{{ render gotweb_render_url(tp->tp_arg, &rss) }}">rss</a>
337     </div>
338     <hr />
339   </div>
340 </div>
341 {{ end }}
343 {{ define gotweb_render_briefs(struct template *tp) }}
345         struct request          *c = tp->tp_arg;
346         struct transport        *t = c->t;
347         struct querystring      *qs = c->t->qs;
348         struct repo_commit      *rc;
349         struct repo_dir         *repo_dir = t->repo_dir;
350         struct gotweb_url        diff_url, patch_url, tree_url;
351         char                    *tmp, *body;
353         diff_url = (struct gotweb_url){
354                 .action = DIFF,
355                 .index_page = -1,
356                 .path = repo_dir->name,
357                 .headref = qs->headref,
358         };
359         patch_url = (struct gotweb_url){
360                 .action = PATCH,
361                 .index_page = -1,
362                 .path = repo_dir->name,
363                 .headref = qs->headref,
364         };
365         tree_url = (struct gotweb_url){
366                 .action = TREE,
367                 .index_page = -1,
368                 .path = repo_dir->name,
369                 .headref = qs->headref,
370         };
372 <header class='subtitle'>
373   <h2>Commit Briefs</h2>
374 </header>
375 <div id="briefs_content">
376   {{ tailq-foreach rc &t->repo_commits entry }}
377     {!
378         diff_url.commit = rc->commit_id;
379         patch_url.commit = rc->commit_id;
380         tree_url.commit = rc->commit_id;
382         tmp = strchr(rc->committer, '<');
383         if (tmp)
384                 *tmp = '\0';
386         body = strchr(rc->commit_msg, '\n');
387         if (body) {
388                 *body++ = '\0';
389                 while (*body == '\n')
390                       body++;
391         }
392     !}
393     <div class='brief'>
394       <p class='brief_meta'>
395         <span class='briefs_age'>
396           {{ render datetime(tp, rc->committer_time, TM_DIFF) }}
397         </span>
398         {{" "}}
399         <span class='briefs_id'>
400           {{ printf "%.10s", rc->commit_id }}
401         </span>
402         {{" "}}
403         <span class="briefs_author">
404           {{ rc->committer }}
405         </span>
406       </p>
407       {{ if body && *body != '\0' }}
408         <details class="briefs_log">
409           <summary>
410             <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">
411               {{ rc->commit_msg }}
412             </a>
413             {{ if rc->refs_str }}
414               {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span>
415             {{ end }}
416             {{ " " }}
417             <span class="briefs_toggle" aria-hidden="true">
418               {{ " â‹…â‹…â‹… " }}
419             </span>
420           </summary>
421           {{ "\n" }}
422           <p>{{ body }}</p>
423         </details>
424       {{ else }}
425         <p class="briefs_log">
426           <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">
427             {{ rc->commit_msg }}
428           </a>
429           {{ if rc->refs_str }}
430             {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span>
431           {{ end }}
432         </p>
433       {{ end }}
434     </div>
435     <div class="navs_wrapper">
436       <div class="navs">
437         <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">diff</a>
438         {{ " | " }}
439         <a href="{{ render gotweb_render_url(tp->tp_arg, &patch_url) }}">patch</a>
440         {{ " | " }}
441         <a href="{{ render gotweb_render_url(tp->tp_arg, &tree_url) }}">tree</a>
442       </div>
443     </div>
444     <hr />
445   {{ end }}
446   {{ render gotweb_render_more(tp, BRIEFS) }}
447 </div>
448 {{ end }}
450 {{ define gotweb_render_more(struct template *tp, int action) }}
452         struct request          *c = tp->tp_arg;
453         struct transport        *t = c->t;
454         struct querystring      *qs = t->qs;
455         struct gotweb_url        more = {
456                 .action = action,
457                 .index_page = -1,
458                 .path = qs->path,
459                 .commit = t->more_id,
460                 .headref = qs->headref,
461                 .file = qs->file,
462         };
464         if (action == TAGS)
465                 more.commit = t->tags_more_id;
467   {{ if more.commit }}
468     <div id="np_wrapper">
469       <div id="nav_more">
470         <a href="{{ render gotweb_render_url(c, &more) }}">
471           More&nbsp;&darr;
472         </a>
473       </div>
474     </div>
475   {{ end }}
476 {{ end }}
478 {{ define gotweb_render_navs(struct template *tp) }}
480         struct request          *c = tp->tp_arg;
481         struct gotweb_url        prev, next;
482         int                      have_prev, have_next;
484         gotweb_index_navs(c, &prev, &have_prev, &next, &have_next);
486 <div id="np_wrapper">
487   <div id="nav_prev">
488     {{ if have_prev }}
489       <a href="{{ render gotweb_render_url(c, &prev) }}">
490         Previous
491       </a>
492     {{ end }}
493   </div>
494   <div id="nav_next">
495     {{ if have_next }}
496       <a href="{{ render gotweb_render_url(c, &next) }}">
497         Next
498       </a>
499     {{ end }}
500   </div>
501 </div>
502 {{ end }}
504 {{ define gotweb_render_commits(struct template *tp) }}
506         struct request          *c = tp->tp_arg;
507         struct transport        *t = c->t;
508         struct repo_dir         *repo_dir = t->repo_dir;
509         struct repo_commit      *rc;
510         struct gotweb_url        diff, patch, tree;
512         diff = (struct gotweb_url){
513                 .action = DIFF,
514                 .index_page = -1,
515                 .path = repo_dir->name,
516         };
517         patch = (struct gotweb_url){
518                 .action = PATCH,
519                 .index_page = -1,
520                 .path = repo_dir->name,
521         };
522         tree = (struct gotweb_url){
523                 .action = TREE,
524                 .index_page = -1,
525                 .path = repo_dir->name,
526         };
528 <header class="subtitle">
529   <h2>Commits</h2>
530 </header>
531 <div class="commits_content">
532   {{ tailq-foreach rc &t->repo_commits entry }}
533     {!
534         diff.commit = rc->commit_id;
535         patch.commit = rc->commit_id;
536         tree.commit = rc->commit_id;
537     !}
538     <div class="page_header_wrapper">
539       <dl>
540         <dt>Commit:</dt>
541         <dd><code class="commit-id">{{ rc->commit_id }}</code></dd>
542         <dt>From:</dt>
543         <dd>{{ rc->author }}</dd>
544         {{ if strcmp(rc->committer, rc->author) != 0 }}
545           <dt>Via:</dt>
546           <dd>{{ rc->committer }}</dd>
547         {{ end }}
548         <dt>Date:</dt>
549         <dd>
550           {{ render datetime(tp, rc->committer_time, TM_LONG) }}
551         </dd>
552       </dl>
553     </div>
554     <hr />
555     <div class="commit">
556       {{ "\n" }}
557       {{ rc->commit_msg }}
558     </div>
559     <div class="navs_wrapper">
560       <div class="navs">
561         <a href="{{ render gotweb_render_url(c, &diff) }}">diff</a>
562         {{ " | " }}
563         <a href="{{ render gotweb_render_url(c, &patch) }}">patch</a>
564         {{ " | " }}
565         <a href="{{ render gotweb_render_url(c, &tree) }}">tree</a>
566       </div>
567     </div>
568     <hr />
569   {{ end }}
570   {{ render gotweb_render_more(tp, COMMITS) }}
571 </div>
572 {{ end }}
574 {{ define gotweb_render_blob(struct template *tp) }}
576         struct request          *c = tp->tp_arg;
577         struct transport        *t = c->t;
578         struct querystring      *qs = t->qs;
579         struct got_blob_object  *blob = t->blob;
580         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
581         struct gotweb_url        briefs_url, blame_url, raw_url;
583         memset(&briefs_url, 0, sizeof(briefs_url));
584         briefs_url.index_page = -1,
585         briefs_url.action = BRIEFS,
586         briefs_url.path = qs->path,
587         briefs_url.commit = qs->commit,
588         briefs_url.folder = qs->folder,
589         briefs_url.file = qs->file,
591         memcpy(&blame_url, &briefs_url, sizeof(blame_url));
592         blame_url.action = BLAME;
594         memcpy(&raw_url, &briefs_url, sizeof(raw_url));
595         raw_url.action = BLOBRAW;
597 <header class="subtitle">
598   <h2>Blob</h2>
599 </header>
600 <div id="blob_content">
601   <div class="page_header_wrapper">
602     <dl>
603       <dt>Date:</dt>
604       <dd>
605         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
606       </dd>
607       <dt>Message:</dt>
608       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
609       <dt>Actions:</dt>
610       <dd>
611         <a href="{{ render gotweb_render_url(c, &briefs_url) }}">
612           History
613         </a>
614         {{" | "}}
615         <a href="{{ render gotweb_render_url(c, &blame_url) }}">
616           Blame
617         </a>
618         {{" | "}}
619         <a href="{{ render gotweb_render_url(c, &raw_url) }}">
620           Raw File
621         </a>
622       </dd>
623     </dl>
624   </div>
625   <hr />
626   <div id="blob">
627     <pre>
628       {{ render got_output_blob_by_lines(tp, blob, gotweb_render_blob_line) }}
629     </pre>
630   </div>
631 </div>
632 {{ end }}
634 {{ define gotweb_render_blob_line(struct template *tp, const char *line,
635     size_t no) }}
637         char             lineno[16];
638         int              r;
640         r = snprintf(lineno, sizeof(lineno), "%zu", no);
641         if (r < 0 || (size_t)r >= sizeof(lineno))
642                 return -1;
644 <div class="blob_line" id="line{{ lineno }}">
645   <a href="#line{{ lineno }}">{{ lineno }}{{" "}}</a>
646   <span class="blob_code">{{ line }}</span>
647 </div>
648 {{ end }}
650 {{ define tree_listing(struct template *tp) }}
652         const struct got_error  *error;
653         struct request          *c = tp->tp_arg;
654         struct transport        *t = c->t;
655         struct querystring      *qs = c->t->qs;
656         struct gotweb_url        url;
657         char                    *readme = NULL;
658         int                      binary;
659         const uint8_t           *buf;
660         size_t                   len;
662   <table id="tree">
663     {{ render got_output_repo_tree(c, &readme, gotweb_render_tree_item) }}
664   </table>
665   {{ if readme }}
666     {!
667         error = got_open_blob_for_output(&t->blob, &t->fd, &binary, c,
668             qs->folder, readme, qs->commit);
669         if (error) {
670                 free(readme);
671                 return (-1);
672         }
674         memset(&url, 0, sizeof(url));
675         url.index_page = -1;
676         url.action = BLOB;
677         url.path = t->qs->path;
678         url.file = readme;
679         url.folder = t->qs->folder ? t->qs->folder : "";
680         url.commit = t->qs->commit;
681     !}
682     {{ if !binary }}
683       <h2>
684         <a href="{{ render gotweb_render_url(c, &url) }}">
685           {{ readme }}
686         </a>
687       </h2>
688       <pre>
689         {!
690                 for (;;) {
691                         error = got_object_blob_read_block(&len, t->blob);
692                         if (error) {
693                                 free(readme);
694                                 return (-1);
695                         }
696                         if (len == 0)
697                                 break;
698                         buf = got_object_blob_get_read_buf(t->blob);
699                         if (tp_write_htmlescape(tp, buf, len) == -1) {
700                                 free(readme);
701                                 return (-1);
702                         }
703                 }
704         !}
705       </pre>
706     {{ end }}
707   {{ end }}
708 {{ finally }}
709   {! free(readme); !}
710 {{ end }}
712 {{ define gotweb_render_tree(struct template *tp) }}
714         struct request          *c = tp->tp_arg;
715         struct transport        *t = c->t;
716         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
718 <header class='subtitle'>
719   <h2>Tree</h2>
720 </header>
721 <div id="tree_content">
722   <div class="page_header_wrapper">
723     <dl>
724       <dt>Tree:</dt>
725       <dd><code class="commit-id">{{ rc->tree_id }}</code></dd>
726       <dt>Date:</dt>
727       <dd>
728         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
729       </dd>
730       <dt>Message:</dt>
731       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
732     </dl>
733   </div>
734   <hr />
735   {{ render tree_listing(tp) }}
736 </div>
737 {{ end }}
739 {{ define gotweb_render_tree_item(struct template *tp,
740     struct got_tree_entry *te) }}
742         struct request          *c = tp->tp_arg;
743         struct transport        *t = c->t;
744         struct querystring      *qs = t->qs;
745         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
746         const char              *modestr = "";
747         const char              *name;
748         const char              *folder;
749         char                    *dir = NULL;
750         mode_t                   mode;
751         struct gotweb_url        url = {
752                .index_page = -1,
753                .commit = rc->commit_id,
754                .path = qs->path,
755         };
757         name = got_tree_entry_get_name(te);
758         mode = got_tree_entry_get_mode(te);
760         folder = qs->folder ? qs->folder : "";
761         if (S_ISDIR(mode)) {
762                 if (asprintf(&dir, "%s/%s", folder, name) == -1)
763                         return (-1);
765                 url.action = TREE;
766                 url.folder = dir;
767         } else {
768                 url.action = BLOB;
769                 url.folder = folder;
770                 url.file = name;
771         }
773         if (got_object_tree_entry_is_submodule(te))
774                 modestr = "$";
775         else if (S_ISLNK(mode))
776                 modestr = "@";
777         else if (S_ISDIR(mode))
778                 modestr = "/";
779         else if (mode & S_IXUSR)
780                 modestr = "*";
782 <tr class="tree_wrapper">
783   {{ if S_ISDIR(mode) }}
784     <td class="tree_line" colspan=2>
785       <a href="{{ render gotweb_render_url(c, &url) }}">
786         {{ name }}{{ modestr }}
787       </a>
788     </td>
789   {{ else }}
790     <td class="tree_line">
791       <a href="{{ render gotweb_render_url(c, &url) }}">
792         {{ name }}{{ modestr }}
793       </a>
794     </td>
795     <td class="tree_line_blank">
796       {! url.action = COMMITS; !}
797       <a href="{{ render gotweb_render_url(c, &url) }}">
798         commits
799       </a>
800       {{ " | " }}
801       {! url.action = BLAME; !}
802       <a href="{{ render gotweb_render_url(c, &url) }}">
803         blame
804       </a>
805     </td>
806   {{ end }}
807 </tr>
808 {{ finally }}
810         free(dir);
812 {{ end }}
814 {{ define gotweb_render_tags(struct template *tp) }}
816         struct request          *c = tp->tp_arg;
817         struct transport        *t = c->t;
818         struct repo_tag         *rt;
820 <header class='subtitle'>
821   <h2>Tags</h2>
822 </header>
823 <div id="tags_content">
824   {{ if TAILQ_EMPTY(&t->repo_tags) }}
825     <div id="err_content">
826       This repository contains no tags
827     </div>
828   {{ else }}
829     {{ tailq-foreach rt &t->repo_tags entry }}
830       {{ render tag_item(tp, rt) }}
831     {{ end }}
832     {{ render gotweb_render_more(tp, TAGS) }}
833   {{ end }}
834 </div>
835 {{ end }}
837 {{ define tag_item(struct template *tp, struct repo_tag *rt) }}
839         struct request          *c = tp->tp_arg;
840         struct transport        *t = c->t;
841         struct repo_dir         *repo_dir = t->repo_dir;
842         char                    *tag_name = rt->tag_name;
843         char                    *msg = rt->tag_commit;
844         char                    *nl;
845         struct gotweb_url        url = {
846                 .action = TAG,
847                 .index_page = -1,
848                 .path = repo_dir->name,
849                 .commit = rt->commit_id,
850         };
852         if (strncmp(tag_name, "refs/tags/", 10) == 0)
853                 tag_name += 10;
855         if (msg) {
856                 nl = strchr(msg, '\n');
857                 if (nl)
858                         *nl = '\0';
859         }
861 <div class="tag_age">
862   {{ render datetime(tp, rt->tagger_time, TM_DIFF) }}
863 </div>
864 <div class="tag_name">{{ tag_name }}</div>
865 <div class="tag_log">
866   <a href="{{ render gotweb_render_url(c, &url) }}">
867     {{ msg }}
868   </a>
869 </div>
870 <div class="navs_wrapper">
871   <div class="navs">
872     <a href="{{ render gotweb_render_url(c, &url) }}">tag</a>
873     {{ " | " }}
874     {! url.action = BRIEFS; !}
875     <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
876     {{ " | " }}
877     {! url.action = COMMITS; !}
878     <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
879   </div>
880 </div>
881 <hr />
882 {{ end }}
884 {{ define gotweb_render_tag(struct template *tp) }}
886         struct request          *c = tp->tp_arg;
887         struct transport        *t = c->t;
888         struct repo_tag         *rt;
889         const char              *tag_name;
891         rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
892         tag_name = rt->tag_name;
894         if (strncmp(tag_name, "refs/", 5) == 0)
895                 tag_name += 5;
897 <header class="subtitle">
898   <h2>Tag</h2>
899 </header>
900 <div id="tags_content">
901   <div class="page_header_wrapper">
902     <dl>
903       <dt>Commit:</dt>
904       <dd>
905         <code class="commit-id">{{ rt->commit_id }}</code>
906         {{ " " }}
907         <span class="refs_str">({{ tag_name }})</span>
908       </dd>
909       <dt>Tagger:</dt>
910       <dd>{{ rt->tagger }}</dd>
911       <dt>Date:</dt>
912       <dd>
913         {{ render datetime(tp, rt->tagger_time, TM_LONG)}}
914       </dd>
915       <dt>Message:</dt>
916       <dd class="commit-msg">{{ rt->commit_msg }}</dd>
917     </dl>
918     <hr />
919     <pre id="tag_commit">
920       {{ rt->tag_commit }}
921     </pre>
922   </div>
923 </div>
924 {{ end }}
926 {{ define gotweb_render_diff(struct template *tp) }}
928         struct request          *c = tp->tp_arg;
929         struct transport        *t = c->t;
930         struct querystring      *qs = t->qs;
931         FILE                    *fp = t->fp;
932         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
933         char                    *line = NULL;
934         size_t                   linesize = 0;
935         ssize_t                  linelen;
936         struct gotweb_url        patch_url, tree_url = {
937                 .action = TREE,
938                 .index_page = -1,
939                 .path = qs->path,
940                 .commit = rc->commit_id,
941         };
943         memcpy(&patch_url, &tree_url, sizeof(patch_url));
944         patch_url.action = PATCH;
946 <header class="subtitle">
947   <h2>Commit Diff</h2>
948 </header>
949 <div id="diff_content">
950   <div class="page_header_wrapper">
951     <dl>
952       <dt>Commit:</dt>
953       <dd><code class="commit-id">{{ rc->commit_id }}</code></dd>
954       <dt>From:</dt>
955       <dd>{{ rc->author }}</dd>
956       {{ if strcmp(rc->committer, rc->author) != 0 }}
957         <dt>Via:</dt>
958         <dd>{{ rc->committer }}</dd>
959       {{ end }}
960       <dt>Date:</dt>
961       <dd>
962         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
963       </dd>
964       <dt>Message:</dt>
965       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
966       <dt>Actions:</dt>
967       <dd>
968         <a href="{{ render gotweb_render_url(c, &patch_url) }}">
969           Patch
970         </a>
971         {{" | "}}
972         <a href="{{ render gotweb_render_url(c, &tree_url) }}">
973           Tree
974         </a>
975       </dd>
976     </dl>
977   </div>
978   <hr />
979   <pre id="diff">
980     {{ while (linelen = getline(&line, &linesize, fp)) != -1 }}
981       {{ render diff_line(tp, line) }}
982     {{ end }}
983   </pre>
984 </div>
985 {{ finally }}
986 {! free(line); !}
987 {{ end }}
989 {{ define diff_line(struct template *tp, char *line )}}
991         const char              *color = NULL;
992         char                    *nl;
994         if (!strncmp(line, "-", 1))
995                 color = "diff_minus";
996         else if (!strncmp(line, "+", 1))
997                 color = "diff_plus";
998         else if (!strncmp(line, "@@", 2))
999                 color = "diff_chunk_header";
1000         else if (!strncmp(line, "commit +", 8) ||
1001             !strncmp(line, "commit -", 8) ||
1002             !strncmp(line, "blob +", 6) ||
1003             !strncmp(line, "blob -", 6) ||
1004             !strncmp(line, "file +", 6) ||
1005             !strncmp(line, "file -", 6))
1006                 color = "diff_meta";
1007         else if (!strncmp(line, "from:", 5) || !strncmp(line, "via:", 4))
1008                 color = "diff_author";
1009         else if (!strncmp(line, "date:", 5))
1010                 color = "diff_date";
1012         nl = strchr(line, '\n');
1013         if (nl)
1014                 *nl = '\0';
1016 <span class="diff_line {{ color }}">{{ line }}</span>{{"\n"}}
1017 {{ end }}
1019 {{ define gotweb_render_branches(struct template *tp,
1020     struct got_reflist_head *refs) }}
1022         struct got_reflist_entry        *re;
1024 <header class='subtitle'>
1025   <h2>Branches</h2>
1026 </header>
1027 <div id="branches_content">
1028   {{ tailq-foreach re refs entry }}
1029     {{ if !got_ref_is_symbolic(re->ref) }}
1030       {{ render branch(tp, re) }}
1031     {{ end }}
1032   {{ end }}
1033 </div>
1034 {{ end }}
1036 {{ define branch(struct template *tp, struct got_reflist_entry *re) }}
1038         const struct got_error  *err;
1039         struct request          *c = tp->tp_arg;
1040         struct querystring      *qs = c->t->qs;
1041         const char              *refname;
1042         time_t                   age;
1043         struct gotweb_url        url = {
1044                 .action = SUMMARY,
1045                 .index_page = -1,
1046                 .path = qs->path,
1047         };
1049         refname = got_ref_get_name(re->ref);
1051         err = got_get_repo_age(&age, c, refname);
1052         if (err) {
1053                 log_warnx("%s: %s", __func__, err->msg);
1054                 return -1;
1055         }
1057         if (strncmp(refname, "refs/heads/", 11) == 0)
1058                 refname += 11;
1060         url.headref = refname;
1062 <section class="branches_wrapper">
1063   <div class="branches_age">
1064     {{ render datetime(tp, age, TM_DIFF) }}
1065   </div>
1066   <div class="branch">
1067     <a href="{{ render gotweb_render_url(c, &url) }}">{{ refname }}</a>
1068   </div>
1069   <div class="navs_wrapper">
1070     <div class="navs">
1071       <a href="{{ render gotweb_render_url(c, &url) }}">summary</a>
1072       {{" | "}}
1073       {! url.action = BRIEFS; !}
1074       <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
1075       {{" | "}}
1076       {! url.action = COMMITS; !}
1077       <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
1078     </div>
1079   </div>
1080   <hr />
1081 </section>
1082 {{ end }}
1084 {{ define gotweb_render_summary(struct template *tp) }}
1086         struct request          *c = tp->tp_arg;
1087         struct server           *srv = c->srv;
1088         struct transport        *t = c->t;
1089         struct got_reflist_head *refs = &t->refs;
1091 <dl id="summary_wrapper" class="page_header_wrapper">
1092   {{ if srv->show_repo_description }}
1093     <dt>Description:</dt>
1094     <dd>{{ t->repo_dir->description }}</dd>
1095   {{ end }}
1096   {{ if srv->show_repo_owner }}
1097     <dt>Owner:</dt>
1098     <dd>{{ t->repo_dir->owner }}</dd>
1099   {{ end }}
1100   {{ if srv->show_repo_age }}
1101     <dt>Last Change:</dt>
1102     <dd>
1103       {{ render datetime(tp, t->repo_dir->age, TM_DIFF) }}
1104     </dd>
1105   {{ end }}
1106   {{ if srv->show_repo_cloneurl }}
1107     <dt>Clone URL:</dt>
1108     <dd><pre class="clone-url">{{ t->repo_dir->url }}</pre></dd>
1109   {{ end }}
1110 </dl>
1111 <div class="summary-briefs">
1112   {{ render gotweb_render_briefs(tp) }}
1113 </div>
1114 <div class="summary-branches">
1115   {{ render gotweb_render_branches(tp, refs) }}
1116 </div>
1117 <div class="summary-tags">
1118   {{ render gotweb_render_tags(tp) }}
1119 </div>
1120 <div class="summary-tree">
1121   <header class='subtitle'>
1122     <h2>Tree</h2>
1123   </header>
1124   <div id="tree_content">
1125     {{ render tree_listing(tp) }}
1126   </div>
1127 </div>
1128 {{ end }}
1130 {{ define gotweb_render_blame(struct template *tp) }}
1132         const struct got_error  *err;
1133         struct request          *c = tp->tp_arg;
1134         struct transport        *t = c->t;
1135         struct querystring      *qs = t->qs;
1136         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
1137         struct gotweb_url        briefs_url, blob_url, raw_url;
1139         memset(&briefs_url, 0, sizeof(briefs_url));
1140         briefs_url.index_page = -1,
1141         briefs_url.action = BRIEFS,
1142         briefs_url.path = qs->path,
1143         briefs_url.commit = qs->commit,
1144         briefs_url.folder = qs->folder,
1145         briefs_url.file = qs->file,
1147         memcpy(&blob_url, &briefs_url, sizeof(blob_url));
1148         blob_url.action = BLOB;
1150         memcpy(&raw_url, &briefs_url, sizeof(raw_url));
1151         raw_url.action = BLOBRAW;
1153 <header class="subtitle">
1154   <h2>Blame</h2>
1155 </header>
1156 <div id="blame_content">
1157   <div class="page_header_wrapper">
1158     <dl>
1159       <dt>Date:</dt>
1160       <dd>
1161         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
1162       </dd>
1163       <dt>Message:</dt>
1164       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
1165       <dt>Actions:</dt>
1166       <dd>
1167         <a href="{{ render gotweb_render_url(c, &briefs_url) }}">
1168           History
1169         </a>
1170         {{" | "}}
1171         <a href="{{ render gotweb_render_url(c, &blob_url) }}">
1172           Blob
1173         </a>
1174         {{" | "}}
1175         <a href="{{ render gotweb_render_url(c, &raw_url) }}">
1176           Raw File
1177         </a>
1178       </dd>
1179     </dl>
1180   </div>
1181   <hr />
1182   <pre id="blame">
1183     {!
1184         err = got_output_file_blame(c, &blame_line);
1185         if (err && err->code != GOT_ERR_CANCELLED)
1186                 log_warnx("%s: got_output_file_blame: %s", __func__,
1187                     err->msg);
1188         if (err)
1189                 return (-1);
1190     !}
1191   </pre>
1192 </div>
1193 {{ end }}
1195 {{ define blame_line(struct template *tp, const char *line,
1196     struct blame_line *bline, int lprec, int lcur) }}
1198         struct request          *c = tp->tp_arg;
1199         struct transport        *t = c->t;
1200         struct repo_dir         *repo_dir = t->repo_dir;
1201         char                    *committer, *s;
1202         struct gotweb_url        url = {
1203                 .action = DIFF,
1204                 .index_page = -1,
1205                 .path = repo_dir->name,
1206                 .commit = bline->id_str,
1207         };
1209         s = strchr(bline->committer, '<');
1210         committer = s ? s + 1 : bline->committer;
1212         s = strchr(committer, '@');
1213         if (s)
1214                 *s = '\0';
1216 <div class="blame_line">
1217   <span class="blame_number">{{ printf "%*d ", lprec, lcur }}</span>
1218   <span class="blame_hash">
1219     <a href="{{ render gotweb_render_url(c, &url) }}">
1220       {{ printf "%.8s", bline->id_str }}
1221     </a>
1222   </span>
1223   {{" "}}
1224   <span class="blame_date">{{ bline->datebuf }}</span>
1225   {{" "}}
1226   <span class="blame_author">{{ printf "%.9s", committer }}</span>
1227   {{" "}}
1228   <span class="blame_code">{{ line }}</span>
1229 </div>
1230 {{ end }}
1232 {{ define gotweb_render_patch(struct template *tp) }}
1234         struct request          *c = tp->tp_arg;
1235         struct transport        *t = c->t;
1236         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
1237         struct tm                tm;
1238         char                     buf[BUFSIZ], datebuf[64];
1239         size_t                   r;
1241         if (gmtime_r(&rc->committer_time, &tm) == NULL ||
1242             asctime_r(&tm, datebuf) == NULL)
1243                 return (-1);
1245         datebuf[strcspn(datebuf, "\n")] = '\0';
1247 commit {{ rc->commit_id }} {{ "\n" }}
1248 from: {{ rc->author | unsafe }} {{ "\n" }}
1249 {{ if strcmp(rc->committer, rc->author) != 0 }}
1250 via: {{ rc->committer | unsafe }} {{ "\n" }}
1251 {{ end }}
1252 date: {{ datebuf }} {{ " UTC" }} {{ "\n" }}
1253 {{ "\n" }}
1254 {{ rc->commit_msg | unsafe }} {{ "\n" }}
1256         if (template_flush(tp) == -1)
1257                 return (-1);
1258         for (;;) {
1259                 r = fread(buf, 1, sizeof(buf), t->fp);
1260                 if (fcgi_write(c, buf, r) == -1 ||
1261                     r != sizeof(buf))
1262                         break;
1263         }
1265 {{ end }}
1267 {{ define gotweb_render_rss(struct template *tp) }}
1269         struct request          *c = tp->tp_arg;
1270         struct server           *srv = c->srv;
1271         struct transport        *t = c->t;
1272         struct repo_dir         *repo_dir = t->repo_dir;
1273         struct repo_tag         *rt;
1274         struct gotweb_url        summary = {
1275                 .action = SUMMARY,
1276                 .index_page = -1,
1277                 .path = repo_dir->name,
1278         };
1280 <?xml version="1.0" encoding="UTF-8"?>
1281 <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
1282   <channel>
1283     <title>Tags of {{ repo_dir->name }}</title>
1284     <link>
1285       <![CDATA[
1286         {{ render gotweb_render_absolute_url(c, &summary) }}
1287       ]]>
1288     </link>
1289     {{ if srv->show_repo_description }}
1290       <description>{{ repo_dir->description }}</description>
1291     {{ end }}
1292     {{ tailq-foreach rt &t->repo_tags entry }}
1293       {{ render rss_tag_item(tp, rt) }}
1294     {{ end }}
1295   </channel>
1296 </rss>
1297 {{ end }}
1299 {{ define rss_tag_item(struct template *tp, struct repo_tag *rt) }}
1301         struct request          *c = tp->tp_arg;
1302         struct transport        *t = c->t;
1303         struct repo_dir         *repo_dir = t->repo_dir;
1304         struct tm                tm;
1305         char                     rfc822[128];
1306         int                      r;
1307         char                    *tag_name = rt->tag_name;
1308         struct gotweb_url        tag = {
1309                 .action = TAG,
1310                 .index_page = -1,
1311                 .path = repo_dir->name,
1312                 .commit = rt->commit_id,
1313         };
1315         if (strncmp(tag_name, "refs/tags/", 10) == 0)
1316                 tag_name += 10;
1318         if (gmtime_r(&rt->tagger_time, &tm) == NULL)
1319                 return -1;
1320         r = strftime(rfc822, sizeof(rfc822), "%a, %d %b %Y %H:%M:%S GMT", &tm);
1321         if (r == 0)
1322                 return 0;
1324 <item>
1325   <title>{{ repo_dir->name }} {{" "}} {{ tag_name }}</title>
1326   <link>
1327     <![CDATA[
1328       {{ render gotweb_render_absolute_url(c, &tag) }}
1329     ]]>
1330   </link>
1331   <description>
1332     <![CDATA[<pre>{{ rt->tag_commit }}</pre>]]>
1333   </description>
1334   {{ render rss_author(tp, rt->tagger) }}
1335   <guid isPermaLink="false">{{ rt->commit_id }}</guid>
1336   <pubDate>
1337     {{ rfc822 }}
1338   </pubDate>
1339 </item>
1340 {{ end }}
1342 {{ define rss_author(struct template *tp, char *author) }}
1344         char    *t, *mail;
1346         /* what to do if the author name contains a paren? */
1347         if (strchr(author, '(') != NULL || strchr(author, ')') != NULL)
1348                 return 0;
1350         t = strchr(author, '<');
1351         if (t == NULL)
1352                 return 0;
1353         *t = '\0';
1354         mail = t+1;
1356         while (isspace((unsigned char)*--t))
1357                 *t = '\0';
1359         t = strchr(mail, '>');
1360         if (t == NULL)
1361                 return 0;
1362         *t = '\0';
1364 <author>
1365   {{ mail }} {{" "}} ({{ author }})
1366 </author>
1367 {{ end }}