portable: remove SA_LEN/SS_LEN
[got-portable.git] / gotwebd / pages.tmpl
blobdf7d9bbde93143299844fd34ee8240f2d6c4e653
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>
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_author">
400           {{ rc->committer }}
401         </span>
402       </p>
403       {{ if body && *body != '\0' }}
404         <details class="briefs_log">
405           <summary>
406             <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">
407               {{ rc->commit_msg }}
408             </a>
409             {{ if rc->refs_str }}
410               {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span>
411             {{ end }}
412             {{ " " }}
413             <span class="briefs_toggle" aria-hidden="true">
414               {{ " â‹…â‹…â‹… " }}
415             </span>
416           </summary>
417           {{ "\n" }}
418           <p>{{ body }}</p>
419         </details>
420       {{ else }}
421         <p class="briefs_log">
422           <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">
423             {{ rc->commit_msg }}
424           </a>
425           {{ if rc->refs_str }}
426             {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span>
427           {{ end }}
428         </p>
429       {{ end }}
430     </div>
431     <div class="navs_wrapper">
432       <div class="navs">
433         <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">diff</a>
434         {{ " | " }}
435         <a href="{{ render gotweb_render_url(tp->tp_arg, &patch_url) }}">patch</a>
436         {{ " | " }}
437         <a href="{{ render gotweb_render_url(tp->tp_arg, &tree_url) }}">tree</a>
438       </div>
439     </div>
440     <hr />
441   {{ end }}
442   {{ render gotweb_render_more(tp, BRIEFS) }}
443 </div>
444 {{ end }}
446 {{ define gotweb_render_more(struct template *tp, int action) }}
448         struct request          *c = tp->tp_arg;
449         struct transport        *t = c->t;
450         struct querystring      *qs = t->qs;
451         struct gotweb_url        more = {
452                 .action = action,
453                 .index_page = -1,
454                 .path = qs->path,
455                 .commit = t->more_id,
456                 .headref = qs->headref,
457                 .file = qs->file,
458         };
460         if (action == TAGS)
461                 more.commit = t->tags_more_id;
463   {{ if more.commit }}
464     <div id="np_wrapper">
465       <div id="nav_more">
466         <a href="{{ render gotweb_render_url(c, &more) }}">
467           More&nbsp;&darr;
468         </a>
469       </div>
470     </div>
471   {{ end }}
472 {{ end }}
474 {{ define gotweb_render_navs(struct template *tp) }}
476         struct request          *c = tp->tp_arg;
477         struct gotweb_url        prev, next;
478         int                      have_prev, have_next;
480         gotweb_index_navs(c, &prev, &have_prev, &next, &have_next);
482 <div id="np_wrapper">
483   <div id="nav_prev">
484     {{ if have_prev }}
485       <a href="{{ render gotweb_render_url(c, &prev) }}">
486         Previous
487       </a>
488     {{ end }}
489   </div>
490   <div id="nav_next">
491     {{ if have_next }}
492       <a href="{{ render gotweb_render_url(c, &next) }}">
493         Next
494       </a>
495     {{ end }}
496   </div>
497 </div>
498 {{ end }}
500 {{ define gotweb_render_commits(struct template *tp) }}
502         struct request          *c = tp->tp_arg;
503         struct transport        *t = c->t;
504         struct repo_dir         *repo_dir = t->repo_dir;
505         struct repo_commit      *rc;
506         struct gotweb_url        diff, patch, tree;
508         diff = (struct gotweb_url){
509                 .action = DIFF,
510                 .index_page = -1,
511                 .path = repo_dir->name,
512         };
513         patch = (struct gotweb_url){
514                 .action = PATCH,
515                 .index_page = -1,
516                 .path = repo_dir->name,
517         };
518         tree = (struct gotweb_url){
519                 .action = TREE,
520                 .index_page = -1,
521                 .path = repo_dir->name,
522         };
524 <header class="subtitle">
525   <h2>Commits</h2>
526 </header>
527 <div class="commits_content">
528   {{ tailq-foreach rc &t->repo_commits entry }}
529     {!
530         diff.commit = rc->commit_id;
531         patch.commit = rc->commit_id;
532         tree.commit = rc->commit_id;
533     !}
534     <div class="page_header_wrapper">
535       <dl>
536         <dt>Commit:</dt>
537         <dd><code class="commit-id">{{ rc->commit_id }}</code></dd>
538         <dt>From:</dt>
539         <dd>{{ rc->author }}</dd>
540         {{ if strcmp(rc->committer, rc->author) != 0 }}
541           <dt>Via:</dt>
542           <dd>{{ rc->committer }}</dd>
543         {{ end }}
544         <dt>Date:</dt>
545         <dd>
546           {{ render datetime(tp, rc->committer_time, TM_LONG) }}
547         </dd>
548       </dl>
549     </div>
550     <hr />
551     <div class="commit">
552       {{ "\n" }}
553       {{ rc->commit_msg }}
554     </div>
555     <div class="navs_wrapper">
556       <div class="navs">
557         <a href="{{ render gotweb_render_url(c, &diff) }}">diff</a>
558         {{ " | " }}
559         <a href="{{ render gotweb_render_url(c, &patch) }}">patch</a>
560         {{ " | " }}
561         <a href="{{ render gotweb_render_url(c, &tree) }}">tree</a>
562       </div>
563     </div>
564     <hr />
565   {{ end }}
566   {{ render gotweb_render_more(tp, COMMITS) }}
567 </div>
568 {{ end }}
570 {{ define gotweb_render_blob(struct template *tp) }}
572         struct request          *c = tp->tp_arg;
573         struct transport        *t = c->t;
574         struct querystring      *qs = t->qs;
575         struct got_blob_object  *blob = t->blob;
576         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
577         struct gotweb_url        briefs_url, blame_url, raw_url;
579         memset(&briefs_url, 0, sizeof(briefs_url));
580         briefs_url.index_page = -1,
581         briefs_url.action = BRIEFS,
582         briefs_url.path = qs->path,
583         briefs_url.commit = qs->commit,
584         briefs_url.folder = qs->folder,
585         briefs_url.file = qs->file,
587         memcpy(&blame_url, &briefs_url, sizeof(blame_url));
588         blame_url.action = BLAME;
590         memcpy(&raw_url, &briefs_url, sizeof(raw_url));
591         raw_url.action = BLOBRAW;
593 <header class="subtitle">
594   <h2>Blob</h2>
595 </header>
596 <div id="blob_content">
597   <div class="page_header_wrapper">
598     <dl>
599       <dt>Date:</dt>
600       <dd>
601         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
602       </dd>
603       <dt>Message:</dt>
604       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
605       <dt>Actions:</dt>
606       <dd>
607         <a href="{{ render gotweb_render_url(c, &briefs_url) }}">
608           History
609         </a>
610         {{" | "}}
611         <a href="{{ render gotweb_render_url(c, &blame_url) }}">
612           Blame
613         </a>
614         {{" | "}}
615         <a href="{{ render gotweb_render_url(c, &raw_url) }}">
616           Raw File
617         </a>
618       </dd>
619     </dl>
620   </div>
621   <hr />
622   <div id="blob">
623     <pre>
624       {{ render got_output_blob_by_lines(tp, blob, gotweb_render_blob_line) }}
625     </pre>
626   </div>
627 </div>
628 {{ end }}
630 {{ define gotweb_render_blob_line(struct template *tp, const char *line,
631     size_t no) }}
633         char             lineno[16];
634         int              r;
636         r = snprintf(lineno, sizeof(lineno), "%zu", no);
637         if (r < 0 || (size_t)r >= sizeof(lineno))
638                 return -1;
640 <div class="blob_line" id="line{{ lineno }}">
641   <a href="#line{{ lineno }}">{{ lineno }}{{" "}}</a>
642   <span class="blob_code">{{ line }}</span>
643 </div>
644 {{ end }}
646 {{ define tree_listing(struct template *tp) }}
648         const struct got_error  *error;
649         struct request          *c = tp->tp_arg;
650         struct transport        *t = c->t;
651         struct querystring      *qs = c->t->qs;
652         struct gotweb_url        url;
653         char                    *readme = NULL;
654         int                      binary;
655         const uint8_t           *buf;
656         size_t                   len;
658   <table id="tree">
659     {{ render got_output_repo_tree(c, &readme, gotweb_render_tree_item) }}
660   </table>
661   {{ if readme }}
662     {!
663         error = got_open_blob_for_output(&t->blob, &t->fd, &binary, c,
664             qs->folder, readme, qs->commit);
665         if (error) {
666                 free(readme);
667                 return (-1);
668         }
670         memset(&url, 0, sizeof(url));
671         url.index_page = -1;
672         url.action = BLOB;
673         url.path = t->qs->path;
674         url.file = readme;
675         url.folder = t->qs->folder;
676         url.commit = t->qs->commit;
677     !}
678     {{ if !binary }}
679       <h2>
680         <a href="{{ render gotweb_render_url(c, &url) }}">
681           {{ readme }}
682         </a>
683       </h2>
684       <pre>
685         {!
686                 for (;;) {
687                         error = got_object_blob_read_block(&len, t->blob);
688                         if (error) {
689                                 free(readme);
690                                 return (-1);
691                         }
692                         if (len == 0)
693                                 break;
694                         buf = got_object_blob_get_read_buf(t->blob);
695                         if (tp_write_htmlescape(tp, buf, len) == -1) {
696                                 free(readme);
697                                 return (-1);
698                         }
699                 }
700         !}
701       </pre>
702     {{ end }}
703   {{ end }}
704 {{ finally }}
705   {! free(readme); !}
706 {{ end }}
708 {{ define gotweb_render_tree(struct template *tp) }}
710         struct request          *c = tp->tp_arg;
711         struct transport        *t = c->t;
712         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
714 <header class='subtitle'>
715   <h2>Tree</h2>
716 </header>
717 <div id="tree_content">
718   <div class="page_header_wrapper">
719     <dl>
720       <dt>Tree:</dt>
721       <dd><code class="commit-id">{{ rc->tree_id }}</code></dd>
722       <dt>Date:</dt>
723       <dd>
724         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
725       </dd>
726       <dt>Message:</dt>
727       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
728     </dl>
729   </div>
730   <hr />
731   {{ render tree_listing(tp) }}
732 </div>
733 {{ end }}
735 {{ define gotweb_render_tree_item(struct template *tp,
736     struct got_tree_entry *te) }}
738         struct request          *c = tp->tp_arg;
739         struct transport        *t = c->t;
740         struct querystring      *qs = t->qs;
741         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
742         const char              *modestr = "";
743         const char              *name;
744         const char              *folder;
745         char                    *dir = NULL;
746         mode_t                   mode;
747         struct gotweb_url        url = {
748                .index_page = -1,
749                .commit = rc->commit_id,
750                .path = qs->path,
751         };
753         name = got_tree_entry_get_name(te);
754         mode = got_tree_entry_get_mode(te);
756         folder = qs->folder ? qs->folder : "";
757         if (S_ISDIR(mode)) {
758                 if (asprintf(&dir, "%s/%s", folder, name) == -1)
759                         return (-1);
761                 url.action = TREE;
762                 url.folder = dir;
763         } else {
764                 url.action = BLOB;
765                 url.folder = folder;
766                 url.file = name;
767         }
769         if (got_object_tree_entry_is_submodule(te))
770                 modestr = "$";
771         else if (S_ISLNK(mode))
772                 modestr = "@";
773         else if (S_ISDIR(mode))
774                 modestr = "/";
775         else if (mode & S_IXUSR)
776                 modestr = "*";
778 <tr class="tree_wrapper">
779   {{ if S_ISDIR(mode) }}
780     <td class="tree_line" colspan=2>
781       <a href="{{ render gotweb_render_url(c, &url) }}">
782         {{ name }}{{ modestr }}
783       </a>
784     </td>
785   {{ else }}
786     <td class="tree_line">
787       <a href="{{ render gotweb_render_url(c, &url) }}">
788         {{ name }}{{ modestr }}
789       </a>
790     </td>
791     <td class="tree_line_blank">
792       {! url.action = COMMITS; !}
793       <a href="{{ render gotweb_render_url(c, &url) }}">
794         commits
795       </a>
796       {{ " | " }}
797       {! url.action = BLAME; !}
798       <a href="{{ render gotweb_render_url(c, &url) }}">
799         blame
800       </a>
801     </td>
802   {{ end }}
803 </tr>
804 {{ finally }}
806         free(dir);
808 {{ end }}
810 {{ define gotweb_render_tags(struct template *tp) }}
812         struct request          *c = tp->tp_arg;
813         struct transport        *t = c->t;
814         struct querystring      *qs = t->qs;
815         struct repo_tag         *rt;
816         int                      commit_found;
818         commit_found = qs->commit == NULL;
820 <header class='subtitle'>
821   <h2>Tags</h2>
822 </header>
823 <div id="tags_content">
824   {{ if t->tag_count == 0 }}
825     <div id="err_content">
826       This repository contains no tags
827     </div>
828   {{ else }}
829     {{ tailq-foreach rt &t->repo_tags entry }}
830       {{ if commit_found || !strcmp(qs->commit, rt->commit_id) }}
831         {! commit_found = 1; !}
832         {{ render tag_item(tp, rt) }}
833       {{ end }}
834     {{ end }}
835     {{ render gotweb_render_more(tp, TAGS) }}
836   {{ end }}
837 </div>
838 {{ end }}
840 {{ define tag_item(struct template *tp, struct repo_tag *rt) }}
842         struct request          *c = tp->tp_arg;
843         struct transport        *t = c->t;
844         struct repo_dir         *repo_dir = t->repo_dir;
845         char                    *tag_name = rt->tag_name;
846         char                    *msg = rt->tag_commit;
847         char                    *nl;
848         struct gotweb_url        url = {
849                 .action = TAG,
850                 .index_page = -1,
851                 .path = repo_dir->name,
852                 .commit = rt->commit_id,
853         };
855         if (strncmp(tag_name, "refs/tags/", 10) == 0)
856                 tag_name += 10;
858         if (msg) {
859                 nl = strchr(msg, '\n');
860                 if (nl)
861                         *nl = '\0';
862         }
864 <div class="tag_age">
865   {{ render datetime(tp, rt->tagger_time, TM_DIFF) }}
866 </div>
867 <div class="tag_name">{{ tag_name }}</div>
868 <div class="tag_log">
869   <a href="{{ render gotweb_render_url(c, &url) }}">
870     {{ msg }}
871   </a>
872 </div>
873 <div class="navs_wrapper">
874   <div class="navs">
875     <a href="{{ render gotweb_render_url(c, &url) }}">tag</a>
876     {{ " | " }}
877     {! url.action = BRIEFS; !}
878     <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
879     {{ " | " }}
880     {! url.action = COMMITS; !}
881     <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
882   </div>
883 </div>
884 <hr />
885 {{ end }}
887 {{ define gotweb_render_tag(struct template *tp) }}
889         struct request          *c = tp->tp_arg;
890         struct transport        *t = c->t;
891         struct repo_tag         *rt;
892         const char              *tag_name;
894         rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
895         tag_name = rt->tag_name;
897         if (strncmp(tag_name, "refs/", 5) == 0)
898                 tag_name += 5;
900 <header class="subtitle">
901   <h2>Tag</h2>
902 </header>
903 <div id="tags_content">
904   <div class="page_header_wrapper">
905     <dl>
906       <dt>Commit:</dt>
907       <dd>
908         <code class="commit-id">{{ rt->commit_id }}</code>
909         {{ " " }}
910         <span class="refs_str">({{ tag_name }})</span>
911       </dd>
912       <dt>Tagger:</dt>
913       <dd>{{ rt->tagger }}</dd>
914       <dt>Date:</dt>
915       <dd>
916         {{ render datetime(tp, rt->tagger_time, TM_LONG)}}
917       </dd>
918       <dt>Message:</dt>
919       <dd class="commit-msg">{{ rt->commit_msg }}</dd>
920     </dl>
921     <hr />
922     <pre id="tag_commit">
923       {{ rt->tag_commit }}
924     </pre>
925   </div>
926 </div>
927 {{ end }}
929 {{ define gotweb_render_diff(struct template *tp) }}
931         struct request          *c = tp->tp_arg;
932         struct transport        *t = c->t;
933         struct querystring      *qs = t->qs;
934         FILE                    *fp = t->fp;
935         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
936         char                    *line = NULL;
937         size_t                   linesize = 0;
938         ssize_t                  linelen;
939         struct gotweb_url        patch_url, tree_url = {
940                 .action = TREE,
941                 .index_page = -1,
942                 .path = qs->path,
943                 .commit = rc->commit_id,
944         };
946         memcpy(&patch_url, &tree_url, sizeof(patch_url));
947         patch_url.action = PATCH;
949 <header class="subtitle">
950   <h2>Commit Diff</h2>
951 </header>
952 <div id="diff_content">
953   <div class="page_header_wrapper">
954     <dl>
955       <dt>Commit:</dt>
956       <dd><code class="commit-id">{{ rc->commit_id }}</code></dd>
957       <dt>From:</dt>
958       <dd>{{ rc->author }}</dd>
959       {{ if strcmp(rc->committer, rc->author) != 0 }}
960         <dt>Via:</dt>
961         <dd>{{ rc->committer }}</dd>
962       {{ end }}
963       <dt>Date:</dt>
964       <dd>
965         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
966       </dd>
967       <dt>Message:</dt>
968       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
969       <dt>Actions:</dt>
970       <dd>
971         <a href="{{ render gotweb_render_url(c, &patch_url) }}">
972           Patch
973         </a>
974         {{" | "}}
975         <a href="{{ render gotweb_render_url(c, &tree_url) }}">
976           Tree
977         </a>
978       </dd>
979     </dl>
980   </div>
981   <hr />
982   <pre id="diff">
983     {{ while (linelen = getline(&line, &linesize, fp)) != -1 }}
984       {{ render diff_line(tp, line) }}
985     {{ end }}
986   </pre>
987 </div>
988 {{ finally }}
989 {! free(line); !}
990 {{ end }}
992 {{ define diff_line(struct template *tp, char *line )}}
994         const char              *color = NULL;
995         char                    *nl;
997         if (!strncmp(line, "-", 1))
998                 color = "diff_minus";
999         else if (!strncmp(line, "+", 1))
1000                 color = "diff_plus";
1001         else if (!strncmp(line, "@@", 2))
1002                 color = "diff_chunk_header";
1003         else if (!strncmp(line, "commit +", 8) ||
1004             !strncmp(line, "commit -", 8) ||
1005             !strncmp(line, "blob +", 6) ||
1006             !strncmp(line, "blob -", 6) ||
1007             !strncmp(line, "file +", 6) ||
1008             !strncmp(line, "file -", 6))
1009                 color = "diff_meta";
1010         else if (!strncmp(line, "from:", 5) || !strncmp(line, "via:", 4))
1011                 color = "diff_author";
1012         else if (!strncmp(line, "date:", 5))
1013                 color = "diff_date";
1015         nl = strchr(line, '\n');
1016         if (nl)
1017                 *nl = '\0';
1019 <span class="diff_line {{ color }}">{{ line }}</span>{{"\n"}}
1020 {{ end }}
1022 {{ define gotweb_render_branches(struct template *tp,
1023     struct got_reflist_head *refs) }}
1025         struct got_reflist_entry        *re;
1027 <header class='subtitle'>
1028   <h2>Branches</h2>
1029 </header>
1030 <div id="branches_content">
1031   {{ tailq-foreach re refs entry }}
1032     {{ if !got_ref_is_symbolic(re->ref) }}
1033       {{ render branch(tp, re) }}
1034     {{ end }}
1035   {{ end }}
1036 </div>
1037 {{ end }}
1039 {{ define branch(struct template *tp, struct got_reflist_entry *re) }}
1041         const struct got_error  *err;
1042         struct request          *c = tp->tp_arg;
1043         struct querystring      *qs = c->t->qs;
1044         const char              *refname;
1045         time_t                   age;
1046         struct gotweb_url        url = {
1047                 .action = SUMMARY,
1048                 .index_page = -1,
1049                 .path = qs->path,
1050         };
1052         refname = got_ref_get_name(re->ref);
1054         err = got_get_repo_age(&age, c, refname);
1055         if (err) {
1056                 log_warnx("%s: %s", __func__, err->msg);
1057                 return -1;
1058         }
1060         if (strncmp(refname, "refs/heads/", 11) == 0)
1061                 refname += 11;
1063         url.headref = refname;
1065 <section class="branches_wrapper">
1066   <div class="branches_age">
1067     {{ render datetime(tp, age, TM_DIFF) }}
1068   </div>
1069   <div class="branch">
1070     <a href="{{ render gotweb_render_url(c, &url) }}">{{ refname }}</a>
1071   </div>
1072   <div class="navs_wrapper">
1073     <div class="navs">
1074       <a href="{{ render gotweb_render_url(c, &url) }}">summary</a>
1075       {{" | "}}
1076       {! url.action = BRIEFS; !}
1077       <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
1078       {{" | "}}
1079       {! url.action = COMMITS; !}
1080       <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
1081     </div>
1082   </div>
1083   <hr />
1084 </section>
1085 {{ end }}
1087 {{ define gotweb_render_summary(struct template *tp) }}
1089         struct request          *c = tp->tp_arg;
1090         struct server           *srv = c->srv;
1091         struct transport        *t = c->t;
1092         struct got_reflist_head *refs = &t->refs;
1094 <dl id="summary_wrapper" class="page_header_wrapper">
1095   {{ if srv->show_repo_description }}
1096     <dt>Description:</dt>
1097     <dd>{{ t->repo_dir->description }}</dd>
1098   {{ end }}
1099   {{ if srv->show_repo_owner }}
1100     <dt>Owner:</dt>
1101     <dd>{{ t->repo_dir->owner }}</dd>
1102   {{ end }}
1103   {{ if srv->show_repo_age }}
1104     <dt>Last Change:</dt>
1105     <dd>
1106       {{ render datetime(tp, t->repo_dir->age, TM_DIFF) }}
1107     </dd>
1108   {{ end }}
1109   {{ if srv->show_repo_cloneurl }}
1110     <dt>Clone URL:</dt>
1111     <dd><pre class="clone-url">{{ t->repo_dir->url }}</pre></dd>
1112   {{ end }}
1113 </dl>
1114 {{ render gotweb_render_briefs(tp) }}
1115 {{ render gotweb_render_branches(tp, refs) }}
1116 {{ render gotweb_render_tags(tp) }}
1117 <header class='subtitle'>
1118   <h2>Tree</h2>
1119 </header>
1120 <div id="tree_content">
1121   {{ render tree_listing(tp) }}
1122 </div>
1123 {{ end }}
1125 {{ define gotweb_render_blame(struct template *tp) }}
1127         const struct got_error  *err;
1128         struct request          *c = tp->tp_arg;
1129         struct transport        *t = c->t;
1130         struct querystring      *qs = t->qs;
1131         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
1132         struct gotweb_url        briefs_url, blob_url, raw_url;
1134         memset(&briefs_url, 0, sizeof(briefs_url));
1135         briefs_url.index_page = -1,
1136         briefs_url.action = BRIEFS,
1137         briefs_url.path = qs->path,
1138         briefs_url.commit = qs->commit,
1139         briefs_url.folder = qs->folder,
1140         briefs_url.file = qs->file,
1142         memcpy(&blob_url, &briefs_url, sizeof(blob_url));
1143         blob_url.action = BLOB;
1145         memcpy(&raw_url, &briefs_url, sizeof(raw_url));
1146         raw_url.action = BLOBRAW;
1148 <header class="subtitle">
1149   <h2>Blame</h2>
1150 </header>
1151 <div id="blame_content">
1152   <div class="page_header_wrapper">
1153     <dl>
1154       <dt>Date:</dt>
1155       <dd>
1156         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
1157       </dd>
1158       <dt>Message:</dt>
1159       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
1160       <dt>Actions:</dt>
1161       <dd>
1162         <a href="{{ render gotweb_render_url(c, &briefs_url) }}">
1163           History
1164         </a>
1165         {{" | "}}
1166         <a href="{{ render gotweb_render_url(c, &blob_url) }}">
1167           Blob
1168         </a>
1169         {{" | "}}
1170         <a href="{{ render gotweb_render_url(c, &raw_url) }}">
1171           Raw File
1172         </a>
1173       </dd>
1174     </dl>
1175   </div>
1176   <hr />
1177   <pre id="blame">
1178     {!
1179         err = got_output_file_blame(c, &blame_line);
1180         if (err && err->code != GOT_ERR_CANCELLED)
1181                 log_warnx("%s: got_output_file_blame: %s", __func__,
1182                     err->msg);
1183         if (err)
1184                 return (-1);
1185     !}
1186   </pre>
1187 </div>
1188 {{ end }}
1190 {{ define blame_line(struct template *tp, const char *line,
1191     struct blame_line *bline, int lprec, int lcur) }}
1193         struct request          *c = tp->tp_arg;
1194         struct transport        *t = c->t;
1195         struct repo_dir         *repo_dir = t->repo_dir;
1196         char                    *committer, *s;
1197         struct gotweb_url        url = {
1198                 .action = DIFF,
1199                 .index_page = -1,
1200                 .path = repo_dir->name,
1201                 .commit = bline->id_str,
1202         };
1204         s = strchr(bline->committer, '<');
1205         committer = s ? s + 1 : bline->committer;
1207         s = strchr(committer, '@');
1208         if (s)
1209                 *s = '\0';
1211 <div class="blame_line">
1212   <span class="blame_number">{{ printf "%*d ", lprec, lcur }}</span>
1213   <span class="blame_hash">
1214     <a href="{{ render gotweb_render_url(c, &url) }}">
1215       {{ printf "%.8s", bline->id_str }}
1216     </a>
1217   </span>
1218   {{" "}}
1219   <span class="blame_date">{{ bline->datebuf }}</span>
1220   {{" "}}
1221   <span class="blame_author">{{ printf "%.9s", committer }}</span>
1222   {{" "}}
1223   <span class="blame_code">{{ line }}</span>
1224 </div>
1225 {{ end }}
1227 {{ define gotweb_render_patch(struct template *tp) }}
1229         struct request          *c = tp->tp_arg;
1230         struct transport        *t = c->t;
1231         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
1232         struct tm                tm;
1233         char                     buf[BUFSIZ], datebuf[64];
1234         size_t                   r;
1236         if (gmtime_r(&rc->committer_time, &tm) == NULL ||
1237             strftime(datebuf, sizeof(datebuf), "%a %b %d %T %Y UTC", &tm) == 0)
1238                 return (-1);
1240 commit {{ rc->commit_id }} {{ "\n" }}
1241 from: {{ rc->author | unsafe }} {{ "\n" }}
1242 {{ if strcmp(rc->committer, rc->author) != 0 }}
1243 via: {{ rc->committer | unsafe }} {{ "\n" }}
1244 {{ end }}
1245 date: {{ datebuf }} {{ "\n" }}
1246 {{ "\n" }}
1247 {{ rc->commit_msg | unsafe }} {{ "\n" }}
1249         if (template_flush(tp) == -1)
1250                 return (-1);
1251         for (;;) {
1252                 r = fread(buf, 1, sizeof(buf), t->fp);
1253                 if (fcgi_write(c, buf, r) == -1 ||
1254                     r != sizeof(buf))
1255                         break;
1256         }
1258 {{ end }}
1260 {{ define gotweb_render_rss(struct template *tp) }}
1262         struct request          *c = tp->tp_arg;
1263         struct server           *srv = c->srv;
1264         struct transport        *t = c->t;
1265         struct repo_dir         *repo_dir = t->repo_dir;
1266         struct repo_tag         *rt;
1267         struct gotweb_url        summary = {
1268                 .action = SUMMARY,
1269                 .index_page = -1,
1270                 .path = repo_dir->name,
1271         };
1273 <?xml version="1.0" encoding="UTF-8"?>
1274 <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
1275   <channel>
1276     <title>Tags of {{ repo_dir->name }}</title>
1277     <link>
1278       <![CDATA[
1279         {{ render gotweb_render_absolute_url(c, &summary) }}
1280       ]]>
1281     </link>
1282     {{ if srv->show_repo_description }}
1283       <description>{{ repo_dir->description }}</description>
1284     {{ end }}
1285     {{ tailq-foreach rt &t->repo_tags entry }}
1286       {{ render rss_tag_item(tp, rt) }}
1287     {{ end }}
1288   </channel>
1289 </rss>
1290 {{ end }}
1292 {{ define rss_tag_item(struct template *tp, struct repo_tag *rt) }}
1294         struct request          *c = tp->tp_arg;
1295         struct transport        *t = c->t;
1296         struct repo_dir         *repo_dir = t->repo_dir;
1297         struct tm                tm;
1298         char                     rfc822[128];
1299         int                      r;
1300         char                    *tag_name = rt->tag_name;
1301         struct gotweb_url        tag = {
1302                 .action = TAG,
1303                 .index_page = -1,
1304                 .path = repo_dir->name,
1305                 .commit = rt->commit_id,
1306         };
1308         if (strncmp(tag_name, "refs/tags/", 10) == 0)
1309                 tag_name += 10;
1311         if (gmtime_r(&rt->tagger_time, &tm) == NULL)
1312                 return -1;
1313         r = strftime(rfc822, sizeof(rfc822), "%a, %d %b %Y %H:%M:%S GMT", &tm);
1314         if (r == 0)
1315                 return 0;
1317 <item>
1318   <title>{{ repo_dir->name }} {{" "}} {{ tag_name }}</title>
1319   <link>
1320     <![CDATA[
1321       {{ render gotweb_render_absolute_url(c, &tag) }}
1322     ]]>
1323   </link>
1324   <description>
1325     <![CDATA[<pre>{{ rt->tag_commit }}</pre>]]>
1326   </description>
1327   {{ render rss_author(tp, rt->tagger) }}
1328   <guid isPermaLink="false">{{ rt->commit_id }}</guid>
1329   <pubDate>
1330     {{ rfc822 }}
1331   </pubDate>
1332 </item>
1333 {{ end }}
1335 {{ define rss_author(struct template *tp, char *author) }}
1337         char    *t, *mail;
1339         /* what to do if the author name contains a paren? */
1340         if (strchr(author, '(') != NULL || strchr(author, ')') != NULL)
1341                 return 0;
1343         t = strchr(author, '<');
1344         if (t == NULL)
1345                 return 0;
1346         *t = '\0';
1347         mail = t+1;
1349         while (isspace((unsigned char)*--t))
1350                 *t = '\0';
1352         t = strchr(mail, '>');
1353         if (t == NULL)
1354                 return 0;
1355         *t = '\0';
1357 <author>
1358   {{ mail }} {{" "}} ({{ author }})
1359 </author>
1360 {{ end }}