gotwebd: fix (again) the styling for the next/prev buttons
[got-portable.git] / gotwebd / pages.tmpl
bloba55f8da771b11abd364a24ae8e9f6fc22975d62c
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 "tmpl.h"
40 enum gotweb_ref_tm {
41         TM_DIFF,
42         TM_LONG,
45 static int breadcumbs(struct template *);
46 static int datetime(struct template *, time_t, int);
47 static int gotweb_render_blob_line(struct template *, const char *, size_t);
48 static int gotweb_render_tree_item(struct template *, struct got_tree_entry *);
49 static int blame_line(struct template *, const char *, struct blame_line *,
50     int, int);
52 static inline int gotweb_render_more(struct template *, int);
54 static inline int tree_listing(struct template *);
55 static inline int diff_line(struct template *, char *);
56 static inline int tag_item(struct template *, struct repo_tag *);
57 static inline int branch(struct template *, struct got_reflist_entry *);
58 static inline int rss_tag_item(struct template *, struct repo_tag *);
59 static inline int rss_author(struct template *, char *);
61 static inline char *
62 nextsep(char *s, char **t)
64         char *q;
66         while (*s == '/')
67                 s++;
68         *t = s;
69         if (*s == '\0')
70                 return NULL;
72         q = strchr(s, '/');
73         if (q == NULL)
74                 q = strchr(s, '\0');
75         return q;
80 {{ define datetime(struct template *tp, time_t t, int fmt) }}
82         struct tm        tm;
83         char             rfc3339[64];
84         char             datebuf[64];
86         if (gmtime_r(&t, &tm) == NULL)
87                 return -1;
89         if (strftime(rfc3339, sizeof(rfc3339), "%FT%TZ", &tm) == 0)
90                 return -1;
92         if (fmt != TM_DIFF && asctime_r(&tm, datebuf) == NULL)
93                 return -1;
95 <time datetime="{{ rfc3339 }}">
96   {{ if fmt == TM_DIFF }}
97     {{ render gotweb_render_age(tp, t) }}
98   {{ else }}
99     {{ datebuf }} {{ " UTC" }}
100   {{ end }}
101 </time>
102 {{ end }}
104 {{ define breadcumbs(struct template *tp) }}
106         struct request          *c = tp->tp_arg;
107         struct querystring      *qs = c->t->qs;
108         struct gotweb_url        url;
109         const char              *folder = qs->folder;
110         const char              *action = "tree";
111         char                    *t, *s = NULL, *dir = NULL;
112         char                     ch;
114         memset(&url, 0, sizeof(url));
115         url.index_page = -1;
116         url.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.page = -1;
176         u_path.action = SUMMARY;
178 <!doctype html>
179 <html>
180   <head>
181     <meta charset="utf-8" />
182     <title>{{ srv->site_name }}</title>
183     <meta name="viewport" content="initial-scale=1.0" />
184     <meta name="msapplication-TileColor" content="#da532c" />
185     <meta name="theme-color" content="#ffffff"/>
186     <link rel="apple-touch-icon" sizes="180x180" href="{{ prfx }}apple-touch-icon.png" />
187     <link rel="icon" type="image/png" sizes="32x32" href="{{ prfx }}favicon-32x32.png" />
188     <link rel="icon" type="image/png" sizes="16x16" href="{{ prfx }}favicon-16x16.png" />
189     <link rel="manifest" href="{{ prfx }}site.webmanifest"/>
190     <link rel="mask-icon" href="{{ prfx }}safari-pinned-tab.svg" />
191     <link rel="stylesheet" type="text/css" href="{{ prfx }}{{ css }}" />
192   </head>
193   <body>
194     <header id="header">
195       <div id="got_link">
196         <a href="{{ srv->logo_url }}" target="_blank">
197           <img src="{{ prfx }}{{ srv->logo }}" />
198         </a>
199       </div>
200     </header>
201     <nav id="site_path">
202       <div id="site_link">
203         <a href="?index_page={{ printf "%d", qs->index_page }}">
204           {{ srv->site_link }}
205         </a>
206         {{ if qs->path }}
207           {! u_path.path = qs->path; !}
208           {{ " / " }}
209           <a href="{{ render gotweb_render_url(tp->tp_arg, &u_path)}}">
210             {{ qs->path }}
211           </a>
212         {{ end }}
213         {{ if qs->action == SUMMARY || qs->action == DIFF ||
214               qs->action == TAG || qs->action == TAGS }}
215           {{ " / " }}{{ gotweb_action_name(qs->action) }}
216         {{ else if qs->action != INDEX}}
217           {{ render breadcumbs(tp) }}
218         {{ end }}
219       </div>
220     </nav>
221     <main>
222       {{ render body(tp) }}
223     </main>
224     <footer id="site_owner_wrapper">
225       <p id="site_owner">
226         {{ if srv->show_site_owner }}
227           {{ srv->site_owner }}
228         {{ end }}
229       </p>
230     </footer>
231   </body>
232 </html>
233 {{ end }}
235 {{ define gotweb_render_error(struct template *tp) }}
237         struct request          *c = tp->tp_arg;
238         struct transport        *t = c->t;
240 <div id="err_content">
241   {{ if t->error }}
242     {{ t->error->msg }}
243   {{ else }}
244     See daemon logs for details
245   {{ end }}
246 </div>
247 {{ end }}
249 {{ define gotweb_render_repo_table_hdr(struct template *tp) }}
251         struct request *c = tp->tp_arg;
252         struct server *srv = c->srv;
254 <div id="index_header">
255   <div class="index_project">
256     Project
257   </div>
258   {{ if srv->show_repo_description }}
259     <div class="index_project_description">
260       Description
261     </div>
262   {{ end }}
263   {{ if srv->show_repo_owner }}
264     <div class="index_project_owner">
265       Owner
266     </div>
267   {{ end }}
268   {{ if srv->show_repo_age }}
269     <div class="index_project_age">
270       Last Change
271     </div>
272   {{ end }}
273 </div>
274 {{ end }}
276 {{ define gotweb_render_repo_fragment(struct template *tp, struct repo_dir *repo_dir) }}
278         struct request *c = tp->tp_arg;
279         struct server *srv = c->srv;
280         struct gotweb_url summary = {
281                 .action = SUMMARY,
282                 .index_page = -1,
283                 .page = -1,
284                 .path = repo_dir->name,
285         }, briefs = {
286                 .action = BRIEFS,
287                 .index_page = -1,
288                 .page = -1,
289                 .path = repo_dir->name,
290         }, commits = {
291                 .action = COMMITS,
292                 .index_page = -1,
293                 .page = -1,
294                 .path = repo_dir->name,
295         }, tags = {
296                 .action = TAGS,
297                 .index_page = -1,
298                 .page = -1,
299                 .path = repo_dir->name,
300         }, tree = {
301                 .action = TREE,
302                 .index_page = -1,
303                 .page = -1,
304                 .path = repo_dir->name,
305         }, rss = {
306                 .action = RSS,
307                 .index_page = -1,
308                 .page = -1,
309                 .path = repo_dir->name,
310         };
312 <div class="index_wrapper">
313   <div class="index_project">
314     <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">{{ repo_dir->name }}</a>
315   </div>
316   {{ if srv->show_repo_description }}
317     <div class="index_project_description">
318       {{ repo_dir->description }}
319     </div>
320   {{ end }}
321   {{ if srv->show_repo_owner }}
322     <div class="index_project_owner">
323       {{ repo_dir->owner }}
324     </div>
325   {{ end }}
326   {{ if srv->show_repo_age }}
327     <div class="index_project_age">
328       {{ render datetime(tp, repo_dir->age, TM_DIFF) }}
329     </div>
330   {{ end }}
331   <div class="navs_wrapper">
332     <div class="navs">
333       <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">summary</a>
334       {{ " | " }}
335       <a href="{{ render gotweb_render_url(tp->tp_arg, &briefs) }}">briefs</a>
336       {{ " | " }}
337       <a href="{{ render gotweb_render_url(tp->tp_arg, &commits) }}">commits</a>
338       {{ " | " }}
339       <a href="{{ render gotweb_render_url(tp->tp_arg, &tags) }}">tags</a>
340       {{ " | " }}
341       <a href="{{ render gotweb_render_url(tp->tp_arg, &tree) }}">tree</a>
342       {{ " | " }}
343       <a href="{{ render gotweb_render_url(tp->tp_arg, &rss) }}">rss</a>
344     </div>
345     <hr />
346   </div>
347 </div>
348 {{ end }}
350 {{ define gotweb_render_briefs(struct template *tp) }}
352         struct request          *c = tp->tp_arg;
353         struct transport        *t = c->t;
354         struct querystring      *qs = c->t->qs;
355         struct repo_commit      *rc;
356         struct repo_dir         *repo_dir = t->repo_dir;
357         struct gotweb_url        diff_url, patch_url, tree_url;
358         char                    *tmp, *body;
360         diff_url = (struct gotweb_url){
361                 .action = DIFF,
362                 .index_page = -1,
363                 .page = -1,
364                 .path = repo_dir->name,
365                 .headref = qs->headref,
366         };
367         patch_url = (struct gotweb_url){
368                 .action = PATCH,
369                 .index_page = -1,
370                 .page = -1,
371                 .path = repo_dir->name,
372                 .headref = qs->headref,
373         };
374         tree_url = (struct gotweb_url){
375                 .action = TREE,
376                 .index_page = -1,
377                 .page = -1,
378                 .path = repo_dir->name,
379                 .headref = qs->headref,
380         };
382 <header class='subtitle'>
383   <h2>Commit Briefs</h2>
384 </header>
385 <div id="briefs_content">
386   {{ tailq-foreach rc &t->repo_commits entry }}
387     {!
388         diff_url.commit = rc->commit_id;
389         patch_url.commit = rc->commit_id;
390         tree_url.commit = rc->commit_id;
392         tmp = strchr(rc->committer, '<');
393         if (tmp)
394                 *tmp = '\0';
396         body = strchr(rc->commit_msg, '\n');
397         if (body) {
398                 *body++ = '\0';
399                 while (*body == '\n')
400                       body++;
401         }
402     !}
403     <div class='brief'>
404       <p class='brief_meta'>
405         <span class='briefs_age'>
406           {{ render datetime(tp, rc->committer_time, TM_DIFF) }}
407         </span>
408         {{" "}}
409         <span class="briefs_author">
410           {{ rc->committer }}
411         </span>
412       </p>
413       {{ if body && *body != '\0' }}
414         <details class="briefs_log">
415           <summary>
416             <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">
417               {{ rc->commit_msg }}
418             </a>
419             {{ if rc->refs_str }}
420               {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span>
421             {{ end }}
422             {{ " " }}
423             <span class="briefs_toggle" aria-hidden="true">
424               {{ " â‹…â‹…â‹… " }}
425             </span>
426           </summary>
427           {{ "\n" }}
428           <p>{{ body }}</p>
429         </details>
430       {{ else }}
431         <p class="briefs_log">
432           <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">
433             {{ rc->commit_msg }}
434           </a>
435           {{ if rc->refs_str }}
436             {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span>
437           {{ end }}
438         </p>
439       {{ end }}
440     </div>
441     <div class="navs_wrapper">
442       <div class="navs">
443         <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">diff</a>
444         {{ " | " }}
445         <a href="{{ render gotweb_render_url(tp->tp_arg, &patch_url) }}">patch</a>
446         {{ " | " }}
447         <a href="{{ render gotweb_render_url(tp->tp_arg, &tree_url) }}">tree</a>
448       </div>
449     </div>
450     <hr />
451   {{ end }}
452   {{ render gotweb_render_more(tp, BRIEFS) }}
453 </div>
454 {{ end }}
456 {{ define gotweb_render_more(struct template *tp, int action) }}
458         struct request          *c = tp->tp_arg;
459         struct transport        *t = c->t;
460         struct querystring      *qs = t->qs;
461         struct gotweb_url        more = {
462                 .action = action,
463                 .index_page = -1,
464                 .path = qs->path,
465                 .commit = t->more_id,
466                 .headref = qs->headref,
467                 .file = qs->file,
468         };
470         if (action == TAGS)
471                 more.commit = t->tags_more_id;
473   {{ if more.commit }}
474     <div id="np_wrapper">
475       <div id="nav_more">
476         <a href="{{ render gotweb_render_url(c, &more) }}">
477           More&nbsp;&darr;
478         </a>
479       </div>
480     </div>
481   {{ end }}
482 {{ end }}
484 {{ define gotweb_render_navs(struct template *tp) }}
486         struct request          *c = tp->tp_arg;
487         struct gotweb_url        prev, next;
488         int                      have_prev, have_next;
490         gotweb_index_navs(c, &prev, &have_prev, &next, &have_next);
492 <div id="np_wrapper">
493   <div id="nav_prev">
494     {{ if have_prev }}
495       <a href="{{ render gotweb_render_url(c, &prev) }}">
496         Previous
497       </a>
498     {{ end }}
499   </div>
500   <div id="nav_next">
501     {{ if have_next }}
502       <a href="{{ render gotweb_render_url(c, &next) }}">
503         Next
504       </a>
505     {{ end }}
506   </div>
507 </div>
508 {{ end }}
510 {{ define gotweb_render_commits(struct template *tp) }}
512         struct request          *c = tp->tp_arg;
513         struct transport        *t = c->t;
514         struct repo_dir         *repo_dir = t->repo_dir;
515         struct repo_commit      *rc;
516         struct gotweb_url        diff, patch, tree;
518         diff = (struct gotweb_url){
519                 .action = DIFF,
520                 .index_page = -1,
521                 .page = -1,
522                 .path = repo_dir->name,
523         };
524         patch = (struct gotweb_url){
525                 .action = PATCH,
526                 .index_page = -1,
527                 .page = -1,
528                 .path = repo_dir->name,
529         };
530         tree = (struct gotweb_url){
531                 .action = TREE,
532                 .index_page = -1,
533                 .page = -1,
534                 .path = repo_dir->name,
535         };
537 <header class="subtitle">
538   <h2>Commits</h2>
539 </header>
540 <div class="commits_content">
541   {{ tailq-foreach rc &t->repo_commits entry }}
542     {!
543         diff.commit = rc->commit_id;
544         patch.commit = rc->commit_id;
545         tree.commit = rc->commit_id;
546     !}
547     <div class="page_header_wrapper">
548       <dl>
549         <dt>Commit:</dt>
550         <dd><code class="commit-id">{{ rc->commit_id }}</code></dd>
551         <dt>From:</dt>
552         <dd>{{ rc->author }}</dd>
553         {{ if strcmp(rc->committer, rc->author) != 0 }}
554           <dt>Via:</dt>
555           <dd>{{ rc->committer }}</dd>
556         {{ end }}
557         <dt>Date:</dt>
558         <dd>
559           {{ render datetime(tp, rc->committer_time, TM_LONG) }}
560         </dd>
561       </dl>
562     </div>
563     <hr />
564     <div class="commit">
565       {{ "\n" }}
566       {{ rc->commit_msg }}
567     </div>
568     <div class="navs_wrapper">
569       <div class="navs">
570         <a href="{{ render gotweb_render_url(c, &diff) }}">diff</a>
571         {{ " | " }}
572         <a href="{{ render gotweb_render_url(c, &patch) }}">patch</a>
573         {{ " | " }}
574         <a href="{{ render gotweb_render_url(c, &tree) }}">tree</a>
575       </div>
576     </div>
577     <hr />
578   {{ end }}
579   {{ render gotweb_render_more(tp, COMMITS) }}
580 </div>
581 {{ end }}
583 {{ define gotweb_render_blob(struct template *tp) }}
585         struct request          *c = tp->tp_arg;
586         struct transport        *t = c->t;
587         struct querystring      *qs = t->qs;
588         struct got_blob_object  *blob = t->blob;
589         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
590         struct gotweb_url        briefs_url, blame_url, raw_url;
592         memset(&briefs_url, 0, sizeof(briefs_url));
593         briefs_url.index_page = -1,
594         briefs_url.page = -1,
595         briefs_url.action = BRIEFS,
596         briefs_url.path = qs->path,
597         briefs_url.commit = qs->commit,
598         briefs_url.folder = qs->folder,
599         briefs_url.file = qs->file,
601         memcpy(&blame_url, &briefs_url, sizeof(blame_url));
602         blame_url.action = BLAME;
604         memcpy(&raw_url, &briefs_url, sizeof(raw_url));
605         raw_url.action = BLOBRAW;
607 <header class="subtitle">
608   <h2>Blob</h2>
609 </header>
610 <div id="blob_content">
611   <div class="page_header_wrapper">
612     <dl>
613       <dt>Date:</dt>
614       <dd>
615         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
616       </dd>
617       <dt>Message:</dt>
618       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
619       <dt>Actions:</dt>
620       <dd>
621         <a href="{{ render gotweb_render_url(c, &briefs_url) }}">
622           History
623         </a>
624         {{" | "}}
625         <a href="{{ render gotweb_render_url(c, &blame_url) }}">
626           Blame
627         </a>
628         {{" | "}}
629         <a href="{{ render gotweb_render_url(c, &raw_url) }}">
630           Raw File
631         </a>
632       </dd>
633     </dl>
634   </div>
635   <hr />
636   <div id="blob">
637     <pre>
638       {{ render got_output_blob_by_lines(tp, blob, gotweb_render_blob_line) }}
639     </pre>
640   </div>
641 </div>
642 {{ end }}
644 {{ define gotweb_render_blob_line(struct template *tp, const char *line,
645     size_t no) }}
647         char             lineno[16];
648         int              r;
650         r = snprintf(lineno, sizeof(lineno), "%zu", no);
651         if (r < 0 || (size_t)r >= sizeof(lineno))
652                 return -1;
654 <div class="blob_line" id="line{{ lineno }}">
655   <a href="#line{{ lineno }}">{{ lineno }}</a>
656   {{" "}}
657   <span class="blob_code">{{ line }}</span>
658 </div>
659 {{ end }}
661 {{ define tree_listing(struct template *tp) }}
663         const struct got_error  *error;
664         struct request          *c = tp->tp_arg;
665         struct transport        *t = c->t;
666         struct querystring      *qs = c->t->qs;
667         struct gotweb_url        url;
668         char                    *readme = NULL;
669         int                      binary;
670         const uint8_t           *buf;
671         size_t                   len;
673   <table id="tree">
674     {{ render got_output_repo_tree(c, &readme, gotweb_render_tree_item) }}
675   </table>
676   {{ if readme }}
677     {!
678         error = got_open_blob_for_output(&t->blob, &t->fd, &binary, c,
679             qs->folder, readme, qs->commit);
680         if (error) {
681                 free(readme);
682                 return (-1);
683         }
685         memset(&url, 0, sizeof(url));
686         url.index_page = -1;
687         url.page = -1;
688         url.action = BLOB;
689         url.path = t->qs->path;
690         url.file = readme;
691         url.folder = t->qs->folder;
692         url.commit = t->qs->commit;
693     !}
694     {{ if !binary }}
695       <h2>
696         <a href="{{ render gotweb_render_url(c, &url) }}">
697           {{ readme }}
698         </a>
699       </h2>
700       <pre>
701         {!
702                 for (;;) {
703                         error = got_object_blob_read_block(&len, t->blob);
704                         if (error) {
705                                 free(readme);
706                                 return (-1);
707                         }
708                         if (len == 0)
709                                 break;
710                         buf = got_object_blob_get_read_buf(t->blob);
711                         if (tp_write_htmlescape(tp, buf, len) == -1) {
712                                 free(readme);
713                                 return (-1);
714                         }
715                 }
716         !}
717       </pre>
718     {{ end }}
719   {{ end }}
720 {{ finally }}
721   {! free(readme); !}
722 {{ end }}
724 {{ define gotweb_render_tree(struct template *tp) }}
726         struct request          *c = tp->tp_arg;
727         struct transport        *t = c->t;
728         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
730 <header class='subtitle'>
731   <h2>Tree</h2>
732 </header>
733 <div id="tree_content">
734   <div class="page_header_wrapper">
735     <dl>
736       <dt>Tree:</dt>
737       <dd><code class="commit-id">{{ rc->tree_id }}</code></dd>
738       <dt>Date:</dt>
739       <dd>
740         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
741       </dd>
742       <dt>Message:</dt>
743       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
744     </dl>
745   </div>
746   <hr />
747   {{ render tree_listing(tp) }}
748 </div>
749 {{ end }}
751 {{ define gotweb_render_tree_item(struct template *tp,
752     struct got_tree_entry *te) }}
754         struct request          *c = tp->tp_arg;
755         struct transport        *t = c->t;
756         struct querystring      *qs = t->qs;
757         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
758         const char              *modestr = "";
759         const char              *name;
760         const char              *folder;
761         char                    *dir = NULL;
762         mode_t                   mode;
763         struct gotweb_url        url = {
764                .index_page = -1,
765                .page = -1,
766                .commit = rc->commit_id,
767                .path = qs->path,
768         };
770         name = got_tree_entry_get_name(te);
771         mode = got_tree_entry_get_mode(te);
773         folder = qs->folder ? qs->folder : "";
774         if (S_ISDIR(mode)) {
775                 if (asprintf(&dir, "%s/%s", folder, name) == -1)
776                         return (-1);
778                 url.action = TREE;
779                 url.folder = dir;
780         } else {
781                 url.action = BLOB;
782                 url.folder = folder;
783                 url.file = name;
784         }
786         if (got_object_tree_entry_is_submodule(te))
787                 modestr = "$";
788         else if (S_ISLNK(mode))
789                 modestr = "@";
790         else if (S_ISDIR(mode))
791                 modestr = "/";
792         else if (mode & S_IXUSR)
793                 modestr = "*";
795 <tr class="tree_wrapper">
796   {{ if S_ISDIR(mode) }}
797     <td class="tree_line" colspan=2>
798       <a href="{{ render gotweb_render_url(c, &url) }}">
799         {{ name }}{{ modestr }}
800       </a>
801     </td>
802   {{ else }}
803     <td class="tree_line">
804       <a href="{{ render gotweb_render_url(c, &url) }}">
805         {{ name }}{{ modestr }}
806       </a>
807     </td>
808     <td class="tree_line_blank">
809       {! url.action = COMMITS; !}
810       <a href="{{ render gotweb_render_url(c, &url) }}">
811         commits
812       </a>
813       {{ " | " }}
814       {! url.action = BLAME; !}
815       <a href="{{ render gotweb_render_url(c, &url) }}">
816         blame
817       </a>
818     </td>
819   {{ end }}
820 </tr>
821 {{ finally }}
823         free(dir);
825 {{ end }}
827 {{ define gotweb_render_tags(struct template *tp) }}
829         struct request          *c = tp->tp_arg;
830         struct transport        *t = c->t;
831         struct querystring      *qs = t->qs;
832         struct repo_tag         *rt;
833         int                      commit_found;
835         commit_found = qs->commit == NULL;
837 <header class='subtitle'>
838   <h2>Tags</h2>
839 </header>
840 <div id="tags_content">
841   {{ if t->tag_count == 0 }}
842     <div id="err_content">
843       This repository contains no tags
844     </div>
845   {{ else }}
846     {{ tailq-foreach rt &t->repo_tags entry }}
847       {{ if commit_found || !strcmp(qs->commit, rt->commit_id) }}
848         {! commit_found = 1; !}
849         {{ render tag_item(tp, rt) }}
850       {{ end }}
851     {{ end }}
852     {{ render gotweb_render_more(tp, TAGS) }}
853   {{ end }}
854 </div>
855 {{ end }}
857 {{ define tag_item(struct template *tp, struct repo_tag *rt) }}
859         struct request          *c = tp->tp_arg;
860         struct transport        *t = c->t;
861         struct repo_dir         *repo_dir = t->repo_dir;
862         char                    *tag_name = rt->tag_name;
863         char                    *msg = rt->tag_commit;
864         char                    *nl;
865         struct gotweb_url        url = {
866                 .action = TAG,
867                 .index_page = -1,
868                 .page = -1,
869                 .path = repo_dir->name,
870                 .commit = rt->commit_id,
871         };
873         if (strncmp(tag_name, "refs/tags/", 10) == 0)
874                 tag_name += 10;
876         if (msg) {
877                 nl = strchr(msg, '\n');
878                 if (nl)
879                         *nl = '\0';
880         }
882 <div class="tag_age">
883   {{ render datetime(tp, rt->tagger_time, TM_DIFF) }}
884 </div>
885 <div class="tag_name">{{ tag_name }}</div>
886 <div class="tag_log">
887   <a href="{{ render gotweb_render_url(c, &url) }}">
888     {{ msg }}
889   </a>
890 </div>
891 <div class="navs_wrapper">
892   <div class="navs">
893     <a href="{{ render gotweb_render_url(c, &url) }}">tag</a>
894     {{ " | " }}
895     {! url.action = BRIEFS; !}
896     <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
897     {{ " | " }}
898     {! url.action = COMMITS; !}
899     <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
900   </div>
901 </div>
902 <hr />
903 {{ end }}
905 {{ define gotweb_render_tag(struct template *tp) }}
907         struct request          *c = tp->tp_arg;
908         struct transport        *t = c->t;
909         struct repo_tag         *rt;
910         const char              *tag_name;
912         rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
913         tag_name = rt->tag_name;
915         if (strncmp(tag_name, "refs/", 5) == 0)
916                 tag_name += 5;
918 <header class="subtitle">
919   <h2>Tag</h2>
920 </header>
921 <div id="tags_content">
922   <div class="page_header_wrapper">
923     <dl>
924       <dt>Commit:</dt>
925       <dd>
926         <code class="commit-id">{{ rt->commit_id }}</code>
927         {{ " " }}
928         <span class="refs_str">({{ tag_name }})</span>
929       </dd>
930       <dt>Tagger:</dt>
931       <dd>{{ rt->tagger }}</dd>
932       <dt>Date:</dt>
933       <dd>
934         {{ render datetime(tp, rt->tagger_time, TM_LONG)}}
935       </dd>
936       <dt>Message:</dt>
937       <dd class="commit-msg">{{ rt->commit_msg }}</dd>
938     </dl>
939     <hr />
940     <pre id="tag_commit">
941       {{ rt->tag_commit }}
942     </pre>
943   </div>
944 </div>
945 {{ end }}
947 {{ define gotweb_render_diff(struct template *tp) }}
949         struct request          *c = tp->tp_arg;
950         struct transport        *t = c->t;
951         struct querystring      *qs = t->qs;
952         FILE                    *fp = t->fp;
953         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
954         char                    *line = NULL;
955         size_t                   linesize = 0;
956         ssize_t                  linelen;
957         struct gotweb_url        patch_url, tree_url = {
958                 .action = TREE,
959                 .index_page = -1,
960                 .page = -1,
961                 .path = qs->path,
962                 .commit = rc->commit_id,
963         };
965         memcpy(&patch_url, &tree_url, sizeof(patch_url));
966         patch_url.action = PATCH;
968 <header class="subtitle">
969   <h2>Commit Diff</h2>
970 </header>
971 <div id="diff_content">
972   <div class="page_header_wrapper">
973     <dl>
974       <dt>Commit:</dt>
975       <dd><code class="commit-id">{{ rc->commit_id }}</code></dd>
976       <dt>From:</dt>
977       <dd>{{ rc->author }}</dd>
978       {{ if strcmp(rc->committer, rc->author) != 0 }}
979         <dt>Via:</dt>
980         <dd>{{ rc->committer }}</dd>
981       {{ end }}
982       <dt>Date:</dt>
983       <dd>
984         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
985       </dd>
986       <dt>Message:</dt>
987       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
988       <dt>Actions:</dt>
989       <dd>
990         <a href="{{ render gotweb_render_url(c, &patch_url) }}">
991           Patch
992         </a>
993         {{" | "}}
994         <a href="{{ render gotweb_render_url(c, &tree_url) }}">
995           Tree
996         </a>
997       </dd>
998     </dl>
999   </div>
1000   <hr />
1001   <pre id="diff">
1002     {{ while (linelen = getline(&line, &linesize, fp)) != -1 }}
1003       {{ render diff_line(tp, line) }}
1004     {{ end }}
1005   </pre>
1006 </div>
1007 {{ finally }}
1008 {! free(line); !}
1009 {{ end }}
1011 {{ define diff_line(struct template *tp, char *line )}}
1013         const char              *color = NULL;
1014         char                    *nl;
1016         if (!strncmp(line, "-", 1))
1017                 color = "diff_minus";
1018         else if (!strncmp(line, "+", 1))
1019                 color = "diff_plus";
1020         else if (!strncmp(line, "@@", 2))
1021                 color = "diff_chunk_header";
1022         else if (!strncmp(line, "commit +", 8) ||
1023             !strncmp(line, "commit -", 8) ||
1024             !strncmp(line, "blob +", 6) ||
1025             !strncmp(line, "blob -", 6) ||
1026             !strncmp(line, "file +", 6) ||
1027             !strncmp(line, "file -", 6))
1028                 color = "diff_meta";
1029         else if (!strncmp(line, "from:", 5) || !strncmp(line, "via:", 4))
1030                 color = "diff_author";
1031         else if (!strncmp(line, "date:", 5))
1032                 color = "diff_date";
1034         nl = strchr(line, '\n');
1035         if (nl)
1036                 *nl = '\0';
1038 <span class="diff_line {{ color }}">{{ line }}</span>{{"\n"}}
1039 {{ end }}
1041 {{ define gotweb_render_branches(struct template *tp,
1042     struct got_reflist_head *refs) }}
1044         struct got_reflist_entry        *re;
1046 <header class='subtitle'>
1047   <h2>Branches</h2>
1048 </header>
1049 <div id="branches_content">
1050   {{ tailq-foreach re refs entry }}
1051     {{ if !got_ref_is_symbolic(re->ref) }}
1052       {{ render branch(tp, re) }}
1053     {{ end }}
1054   {{ end }}
1055 </div>
1056 {{ end }}
1058 {{ define branch(struct template *tp, struct got_reflist_entry *re) }}
1060         const struct got_error  *err;
1061         struct request          *c = tp->tp_arg;
1062         struct querystring      *qs = c->t->qs;
1063         const char              *refname;
1064         time_t                   age;
1065         struct gotweb_url        url = {
1066                 .action = SUMMARY,
1067                 .index_page = -1,
1068                 .page = -1,
1069                 .path = qs->path,
1070         };
1072         refname = got_ref_get_name(re->ref);
1074         err = got_get_repo_age(&age, c, refname);
1075         if (err) {
1076                 log_warnx("%s: %s", __func__, err->msg);
1077                 return -1;
1078         }
1080         if (strncmp(refname, "refs/heads/", 11) == 0)
1081                 refname += 11;
1083         url.headref = refname;
1085 <section class="branches_wrapper">
1086   <div class="branches_age">
1087     {{ render datetime(tp, age, TM_DIFF) }}
1088   </div>
1089   <div class="branch">
1090     <a href="{{ render gotweb_render_url(c, &url) }}">{{ refname }}</a>
1091   </div>
1092   <div class="navs_wrapper">
1093     <div class="navs">
1094       <a href="{{ render gotweb_render_url(c, &url) }}">summary</a>
1095       {{" | "}}
1096       {! url.action = BRIEFS; !}
1097       <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
1098       {{" | "}}
1099       {! url.action = COMMITS; !}
1100       <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
1101     </div>
1102   </div>
1103   <hr />
1104 </section>
1105 {{ end }}
1107 {{ define gotweb_render_summary(struct template *tp) }}
1109         struct request          *c = tp->tp_arg;
1110         struct server           *srv = c->srv;
1111         struct transport        *t = c->t;
1112         struct got_reflist_head *refs = &t->refs;
1114 <dl id="summary_wrapper" class="page_header_wrapper">
1115   {{ if srv->show_repo_description }}
1116     <dt>Description:</dt>
1117     <dd>{{ t->repo_dir->description }}</dd>
1118   {{ end }}
1119   {{ if srv->show_repo_owner }}
1120     <dt>Owner:</dt>
1121     <dd>{{ t->repo_dir->owner }}</dd>
1122   {{ end }}
1123   {{ if srv->show_repo_age }}
1124     <dt>Last Change:</dt>
1125     <dd>
1126       {{ render datetime(tp, t->repo_dir->age, TM_DIFF) }}
1127     </dd>
1128   {{ end }}
1129   {{ if srv->show_repo_cloneurl }}
1130     <dt>Clone URL:</dt>
1131     <dd><pre class="clone-url">{{ t->repo_dir->url }}</pre></dd>
1132   {{ end }}
1133 </dl>
1134 {{ render gotweb_render_briefs(tp) }}
1135 {{ render gotweb_render_branches(tp, refs) }}
1136 {{ render gotweb_render_tags(tp) }}
1137 <header class='subtitle'>
1138   <h2>Tree</h2>
1139 </header>
1140 <div id="tree_content">
1141   {{ render tree_listing(tp) }}
1142 </div>
1143 {{ end }}
1145 {{ define gotweb_render_blame(struct template *tp) }}
1147         const struct got_error  *err;
1148         struct request          *c = tp->tp_arg;
1149         struct transport        *t = c->t;
1150         struct querystring      *qs = t->qs;
1151         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
1152         struct gotweb_url        briefs_url, blob_url, raw_url;
1154         memset(&briefs_url, 0, sizeof(briefs_url));
1155         briefs_url.index_page = -1,
1156         briefs_url.page = -1,
1157         briefs_url.action = BRIEFS,
1158         briefs_url.path = qs->path,
1159         briefs_url.commit = qs->commit,
1160         briefs_url.folder = qs->folder,
1161         briefs_url.file = qs->file,
1163         memcpy(&blob_url, &briefs_url, sizeof(blob_url));
1164         blob_url.action = BLOB;
1166         memcpy(&raw_url, &briefs_url, sizeof(raw_url));
1167         raw_url.action = BLOBRAW;
1169 <header class="subtitle">
1170   <h2>Blame</h2>
1171 </header>
1172 <div id="blame_content">
1173   <div class="page_header_wrapper">
1174     <dl>
1175       <dt>Date:</dt>
1176       <dd>
1177         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
1178       </dd>
1179       <dt>Message:</dt>
1180       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
1181       <dt>Actions:</dt>
1182       <dd>
1183         <a href="{{ render gotweb_render_url(c, &briefs_url) }}">
1184           History
1185         </a>
1186         {{" | "}}
1187         <a href="{{ render gotweb_render_url(c, &blob_url) }}">
1188           Blob
1189         </a>
1190         {{" | "}}
1191         <a href="{{ render gotweb_render_url(c, &raw_url) }}">
1192           Raw File
1193         </a>
1194       </dd>
1195     </dl>
1196   </div>
1197   <hr />
1198   <pre id="blame">
1199     {!
1200         err = got_output_file_blame(c, &blame_line);
1201         if (err && err->code != GOT_ERR_CANCELLED)
1202                 log_warnx("%s: got_output_file_blame: %s", __func__,
1203                     err->msg);
1204         if (err)
1205                 return (-1);
1206     !}
1207   </pre>
1208 </div>
1209 {{ end }}
1211 {{ define blame_line(struct template *tp, const char *line,
1212     struct blame_line *bline, int lprec, int lcur) }}
1214         struct request          *c = tp->tp_arg;
1215         struct transport        *t = c->t;
1216         struct repo_dir         *repo_dir = t->repo_dir;
1217         char                    *committer, *s;
1218         struct gotweb_url        url = {
1219                 .action = DIFF,
1220                 .index_page = -1,
1221                 .page = -1,
1222                 .path = repo_dir->name,
1223                 .commit = bline->id_str,
1224         };
1226         s = strchr(bline->committer, '<');
1227         committer = s ? s + 1 : bline->committer;
1229         s = strchr(committer, '@');
1230         if (s)
1231                 *s = '\0';
1233 <div class="blame_line">
1234   <span class="blame_number">{{ printf "%*d ", lprec, lcur }}</span>
1235   <span class="blame_hash">
1236     <a href="{{ render gotweb_render_url(c, &url) }}">
1237       {{ printf "%.8s", bline->id_str }}
1238     </a>
1239   </span>
1240   {{" "}}
1241   <span class="blame_date">{{ bline->datebuf }}</span>
1242   {{" "}}
1243   <span class="blame_author">{{ printf "%.9s", committer }}</span>
1244   {{" "}}
1245   <span class="blame_code">{{ line }}</span>
1246 </div>
1247 {{ end }}
1249 {{ define gotweb_render_patch(struct template *tp) }}
1251         struct request          *c = tp->tp_arg;
1252         struct transport        *t = c->t;
1253         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
1254         struct tm                tm;
1255         char                     buf[BUFSIZ], datebuf[64];
1256         size_t                   r;
1258         if (gmtime_r(&rc->committer_time, &tm) == NULL ||
1259             strftime(datebuf, sizeof(datebuf), "%a %b %d %T %Y UTC", &tm) == 0)
1260                 return (-1);
1262 commit {{ rc->commit_id }} {{ "\n" }}
1263 from: {{ rc->author | unsafe }} {{ "\n" }}
1264 {{ if strcmp(rc->committer, rc->author) != 0 }}
1265 via: {{ rc->committer | unsafe }} {{ "\n" }}
1266 {{ end }}
1267 date: {{ datebuf }} {{ "\n" }}
1268 {{ "\n" }}
1269 {{ rc->commit_msg | unsafe }} {{ "\n" }}
1271         if (template_flush(tp) == -1)
1272                 return (-1);
1273         for (;;) {
1274                 r = fread(buf, 1, sizeof(buf), t->fp);
1275                 if (fcgi_write(c, buf, r) == -1 ||
1276                     r != sizeof(buf))
1277                         break;
1278         }
1280 {{ end }}
1282 {{ define gotweb_render_rss(struct template *tp) }}
1284         struct request          *c = tp->tp_arg;
1285         struct server           *srv = c->srv;
1286         struct transport        *t = c->t;
1287         struct repo_dir         *repo_dir = t->repo_dir;
1288         struct repo_tag         *rt;
1289         struct gotweb_url        summary = {
1290                 .action = SUMMARY,
1291                 .index_page = -1,
1292                 .page = -1,
1293                 .path = repo_dir->name,
1294         };
1296 <?xml version="1.0" encoding="UTF-8"?>
1297 <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
1298   <channel>
1299     <title>Tags of {{ repo_dir->name }}</title>
1300     <link>
1301       <![CDATA[
1302         {{ render gotweb_render_absolute_url(c, &summary) }}
1303       ]]>
1304     </link>
1305     {{ if srv->show_repo_description }}
1306       <description>{{ repo_dir->description }}</description>
1307     {{ end }}
1308     {{ tailq-foreach rt &t->repo_tags entry }}
1309       {{ render rss_tag_item(tp, rt) }}
1310     {{ end }}
1311   </channel>
1312 </rss>
1313 {{ end }}
1315 {{ define rss_tag_item(struct template *tp, struct repo_tag *rt) }}
1317         struct request          *c = tp->tp_arg;
1318         struct transport        *t = c->t;
1319         struct repo_dir         *repo_dir = t->repo_dir;
1320         struct tm                tm;
1321         char                     rfc822[128];
1322         int                      r;
1323         char                    *tag_name = rt->tag_name;
1324         struct gotweb_url        tag = {
1325                 .action = TAG,
1326                 .index_page = -1,
1327                 .page = -1,
1328                 .path = repo_dir->name,
1329                 .commit = rt->commit_id,
1330         };
1332         if (strncmp(tag_name, "refs/tags/", 10) == 0)
1333                 tag_name += 10;
1335         if (gmtime_r(&rt->tagger_time, &tm) == NULL)
1336                 return -1;
1337         r = strftime(rfc822, sizeof(rfc822), "%a, %d %b %Y %H:%M:%S GMT", &tm);
1338         if (r == 0)
1339                 return 0;
1341 <item>
1342   <title>{{ repo_dir->name }} {{" "}} {{ tag_name }}</title>
1343   <link>
1344     <![CDATA[
1345       {{ render gotweb_render_absolute_url(c, &tag) }}
1346     ]]>
1347   </link>
1348   <description>
1349     <![CDATA[<pre>{{ rt->tag_commit }}</pre>]]>
1350   </description>
1351   {{ render rss_author(tp, rt->tagger) }}
1352   <guid isPermaLink="false">{{ rt->commit_id }}</guid>
1353   <pubDate>
1354     {{ rfc822 }}
1355   </pubDate>
1356 </item>
1357 {{ end }}
1359 {{ define rss_author(struct template *tp, char *author) }}
1361         char    *t, *mail;
1363         /* what to do if the author name contains a paren? */
1364         if (strchr(author, '(') != NULL || strchr(author, ')') != NULL)
1365                 return 0;
1367         t = strchr(author, '<');
1368         if (t == NULL)
1369                 return 0;
1370         *t = '\0';
1371         mail = t+1;
1373         while (isspace((unsigned char)*--t))
1374                 *t = '\0';
1376         t = strchr(mail, '>');
1377         if (t == NULL)
1378                 return 0;
1379         *t = '\0';
1381 <author>
1382   {{ mail }} {{" "}} ({{ author }})
1383 </author>
1384 {{ end }}