gotwebd: add breadcums to navigate the tree/blob views
[got-portable.git] / gotwebd / pages.tmpl
blob5de661cded74918826aa456f1e1036d8a1ab62de
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 diff_line(struct template *, char *);
55 static inline int tag_item(struct template *, struct repo_tag *);
56 static inline int branch(struct template *, struct got_reflist_entry *);
57 static inline int rss_tag_item(struct template *, struct repo_tag *);
58 static inline int rss_author(struct template *, char *);
60 static inline char *
61 nextsep(char *s, char **t)
63         char *q;
65         while (*s == '/')
66                 s++;
67         *t = s;
68         if (*s == '\0')
69                 return NULL;
71         q = strchr(s, '/');
72         if (q == NULL)
73                 q = strchr(s, '\0');
74         return q;
79 {{ define datetime(struct template *tp, time_t t, int fmt) }}
81         struct tm        tm;
82         char             rfc3339[64];
83         char             datebuf[64];
85         if (gmtime_r(&t, &tm) == NULL)
86                 return -1;
88         if (strftime(rfc3339, sizeof(rfc3339), "%FT%TZ", &tm) == 0)
89                 return -1;
91         if (fmt != TM_DIFF && asctime_r(&tm, datebuf) == NULL)
92                 return -1;
94 <time datetime="{{ rfc3339 }}">
95   {{ if fmt == TM_DIFF }}
96     {{ render gotweb_render_age(tp, t) }}
97   {{ else }}
98     {{ datebuf }} {{ " UTC" }}
99   {{ end }}
100 </time>
101 {{ end }}
103 {{ define breadcumbs(struct template *tp) }}
105         struct request          *c = tp->tp_arg;
106         struct querystring      *qs = c->t->qs;
107         struct gotweb_url        url;
108         const char              *folder = qs->folder;
109         char                    *t, *s = NULL, *dir = NULL;
110         char                     ch;
112         memset(&url, 0, sizeof(url));
113         url.index_page = -1;
114         url.page = -1;
115         url.action = TREE;
116         url.path = qs->path;
117         url.commit = qs->commit;
119         if (folder && *folder != '\0') {
120                 while (*folder == '/')
121                         folder++;
122                 dir = strdup(folder);
123                 if (dir == NULL)
124                         return (-1);
125                 s = dir;
126         }
128   {{ " / " }}
129   <a href="{{ render gotweb_render_url(c, &url) }}">tree</a>
130   {{ " / " }}
131   {{ if dir }}
132     {{ while (s = nextsep(s, &t)) != NULL }}
133       {!
134         ch = *s;
135         *s = '\0';
136         url.folder = dir;
137       !}
139       <a href="{{ render gotweb_render_url(c, &url) }}">
140         {{ t }}
141       </a>
142       {{ " / " }}
144       {! *s = ch; !}
145     {{ end }}
146   {{ end }}
148   {{ if qs->file }}
149     {{ qs->file }}
150   {{ end}}
152   {{ finally }}
153   {! free(dir); !}
154 {{ end }}
156 {{ define gotweb_render_page(struct template *tp,
157     int (*body)(struct template *)) }}
159         struct request          *c = tp->tp_arg;
160         struct server           *srv = c->srv;
161         struct querystring      *qs = c->t->qs;
162         struct gotweb_url        u_path;
163         const char              *prfx = c->document_uri;
164         const char              *css = srv->custom_css;
166         memset(&u_path, 0, sizeof(u_path));
167         u_path.index_page = -1;
168         u_path.page = -1;
169         u_path.action = SUMMARY;
171 <!doctype html>
172 <html>
173   <head>
174     <meta charset="utf-8" />
175     <title>{{ srv->site_name }}</title>
176     <meta name="viewport" content="initial-scale=1.0" />
177     <meta name="msapplication-TileColor" content="#da532c" />
178     <meta name="theme-color" content="#ffffff"/>
179     <link rel="apple-touch-icon" sizes="180x180" href="{{ prfx }}apple-touch-icon.png" />
180     <link rel="icon" type="image/png" sizes="32x32" href="{{ prfx }}favicon-32x32.png" />
181     <link rel="icon" type="image/png" sizes="16x16" href="{{ prfx }}favicon-16x16.png" />
182     <link rel="manifest" href="{{ prfx }}site.webmanifest"/>
183     <link rel="mask-icon" href="{{ prfx }}safari-pinned-tab.svg" />
184     <link rel="stylesheet" type="text/css" href="{{ prfx }}{{ css }}" />
185   </head>
186   <body>
187     <header id="header">
188       <div id="got_link">
189         <a href="{{ srv->logo_url }}" target="_blank">
190           <img src="{{ prfx }}{{ srv->logo }}" />
191         </a>
192       </div>
193     </header>
194     <nav id="site_path">
195       <div id="site_link">
196         <a href="?index_page={{ printf "%d", qs->index_page }}">
197           {{ srv->site_link }}
198         </a>
199         {{ if qs->path }}
200           {! u_path.path = qs->path; !}
201           {{ " / " }}
202           <a href="{{ render gotweb_render_url(tp->tp_arg, &u_path)}}">
203             {{ qs->path }}
204           </a>
205         {{ end }}
206         {{ if qs->action == TREE || qs->action == BLOB }}
207           {{ render breadcumbs(tp) }}
208         {{ else if qs->action != INDEX }}
209           {{ " / " }}{{ gotweb_action_name(qs->action) }}
210         {{ end }}
211       </div>
212     </nav>
213     <main>
214       {{ render body(tp) }}
215     </main>
216     <footer id="site_owner_wrapper">
217       <p id="site_owner">
218         {{ if srv->show_site_owner }}
219           {{ srv->site_owner }}
220         {{ end }}
221       </p>
222     </footer>
223   </body>
224 </html>
225 {{ end }}
227 {{ define gotweb_render_error(struct template *tp) }}
229         struct request          *c = tp->tp_arg;
230         struct transport        *t = c->t;
232 <div id="err_content">
233   {{ if t->error }}
234     {{ t->error->msg }}
235   {{ else }}
236     See daemon logs for details
237   {{ end }}
238 </div>
239 {{ end }}
241 {{ define gotweb_render_repo_table_hdr(struct template *tp) }}
243         struct request *c = tp->tp_arg;
244         struct server *srv = c->srv;
246 <div id="index_header">
247   <div class="index_project">
248     Project
249   </div>
250   {{ if srv->show_repo_description }}
251     <div class="index_project_description">
252       Description
253     </div>
254   {{ end }}
255   {{ if srv->show_repo_owner }}
256     <div class="index_project_owner">
257       Owner
258     </div>
259   {{ end }}
260   {{ if srv->show_repo_age }}
261     <div class="index_project_age">
262       Last Change
263     </div>
264   {{ end }}
265 </div>
266 {{ end }}
268 {{ define gotweb_render_repo_fragment(struct template *tp, struct repo_dir *repo_dir) }}
270         struct request *c = tp->tp_arg;
271         struct server *srv = c->srv;
272         struct gotweb_url summary = {
273                 .action = SUMMARY,
274                 .index_page = -1,
275                 .page = -1,
276                 .path = repo_dir->name,
277         }, briefs = {
278                 .action = BRIEFS,
279                 .index_page = -1,
280                 .page = -1,
281                 .path = repo_dir->name,
282         }, commits = {
283                 .action = COMMITS,
284                 .index_page = -1,
285                 .page = -1,
286                 .path = repo_dir->name,
287         }, tags = {
288                 .action = TAGS,
289                 .index_page = -1,
290                 .page = -1,
291                 .path = repo_dir->name,
292         }, tree = {
293                 .action = TREE,
294                 .index_page = -1,
295                 .page = -1,
296                 .path = repo_dir->name,
297         }, rss = {
298                 .action = RSS,
299                 .index_page = -1,
300                 .page = -1,
301                 .path = repo_dir->name,
302         };
304 <div class="index_wrapper">
305   <div class="index_project">
306     <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">{{ repo_dir->name }}</a>
307   </div>
308   {{ if srv->show_repo_description }}
309     <div class="index_project_description">
310       {{ repo_dir->description }}
311     </div>
312   {{ end }}
313   {{ if srv->show_repo_owner }}
314     <div class="index_project_owner">
315       {{ repo_dir->owner }}
316     </div>
317   {{ end }}
318   {{ if srv->show_repo_age }}
319     <div class="index_project_age">
320       {{ render datetime(tp, repo_dir->age, TM_DIFF) }}
321     </div>
322   {{ end }}
323   <div class="navs_wrapper">
324     <div class="navs">
325       <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">summary</a>
326       {{ " | " }}
327       <a href="{{ render gotweb_render_url(tp->tp_arg, &briefs) }}">briefs</a>
328       {{ " | " }}
329       <a href="{{ render gotweb_render_url(tp->tp_arg, &commits) }}">commits</a>
330       {{ " | " }}
331       <a href="{{ render gotweb_render_url(tp->tp_arg, &tags) }}">tags</a>
332       {{ " | " }}
333       <a href="{{ render gotweb_render_url(tp->tp_arg, &tree) }}">tree</a>
334       {{ " | " }}
335       <a href="{{ render gotweb_render_url(tp->tp_arg, &rss) }}">rss</a>
336     </div>
337     <hr />
338   </div>
339 </div>
340 {{ end }}
342 {{ define gotweb_render_briefs(struct template *tp) }}
344         struct request          *c = tp->tp_arg;
345         struct transport        *t = c->t;
346         struct querystring      *qs = c->t->qs;
347         struct repo_commit      *rc;
348         struct repo_dir         *repo_dir = t->repo_dir;
349         struct gotweb_url        diff_url, tree_url;
350         char                    *tmp;
352         diff_url = (struct gotweb_url){
353                 .action = DIFF,
354                 .index_page = -1,
355                 .page = -1,
356                 .path = repo_dir->name,
357                 .headref = qs->headref,
358         };
359         tree_url = (struct gotweb_url){
360                 .action = TREE,
361                 .index_page = -1,
362                 .page = -1,
363                 .path = repo_dir->name,
364                 .headref = qs->headref,
365         };
367 <header class='subtitle'>
368   <h2>Commit Briefs</h2>
369 </header>
370 <div id="briefs_content">
371   {{ tailq-foreach rc &t->repo_commits entry }}
372     {!
373         diff_url.commit = rc->commit_id;
374         tree_url.commit = rc->commit_id;
376         tmp = strchr(rc->committer, '<');
377         if (tmp)
378                 *tmp = '\0';
380         tmp = strchr(rc->commit_msg, '\n');
381         if (tmp)
382                 *tmp = '\0';
383     !}
384     <div class='brief'>
385       <p class='brief_meta'>
386         <span class='briefs_age'>
387           {{ render datetime(tp, rc->committer_time, TM_DIFF) }}
388         </span>
389         {{" "}}
390         <span class="briefs_author">
391           {{ rc->committer }}
392         </span>
393       </p>
394       <p class="briefs_log">
395         <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">
396           {{ rc->commit_msg }}
397         </a>
398         {{ if rc->refs_str }}
399           {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span>
400         {{ end }}
401         </a>
402       </p>
403     </div>
404     <div class="navs_wrapper">
405       <div class="navs">
406         <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">diff</a>
407         {{ " | " }}
408         <a href="{{ render gotweb_render_url(tp->tp_arg, &tree_url) }}">tree</a>
409       </div>
410     </div>
411     <hr />
412   {{ end }}
413   {{ render gotweb_render_more(tp, BRIEFS) }}
414 </div>
415 {{ end }}
417 {{ define gotweb_render_more(struct template *tp, int action) }}
419         struct request          *c = tp->tp_arg;
420         struct transport        *t = c->t;
421         struct querystring      *qs = t->qs;
422         struct gotweb_url        more = {
423                 .action = action,
424                 .index_page = -1,
425                 .path = qs->path,
426                 .commit = t->more_id,
427                 .headref = qs->headref,
428                 .file = qs->file,
429         };
431   {{ if t->more_id }}
432     <div id="np_wrapper">
433       <div id="nav_more">
434         <a href="{{ render gotweb_render_url(c, &more) }}">
435           More&nbsp;&darr;
436         </a>
437       </div>
438     </div>
439   {{ end }}
440 {{ end }}
442 {{ define gotweb_render_navs(struct template *tp) }}
444         struct request          *c = tp->tp_arg;
445         struct transport        *t = c->t;
446         struct gotweb_url        prev, next;
447         int                      have_prev, have_next;
449         gotweb_get_navs(c, &prev, &have_prev, &next, &have_next);
451 <div id="np_wrapper">
452   <div id="nav_prev">
453     {{ if have_prev }}
454       <a href="{{ render gotweb_render_url(c, &prev) }}">
455         Previous
456       </a>
457     {{ end }}
458   </div>
459   <div id="nav_next">
460     {{ if have_next }}
461       <a href="{{ render gotweb_render_url(c, &next) }}">
462         Next
463       </a>
464     {{ end }}
465   </div>
466 </div>
467 {{ finally }}
469         free(t->next_id);
470         t->next_id = NULL;
471         free(t->prev_id);
472         t->prev_id = NULL;
474 {{ end }}
476 {{ define gotweb_render_commits(struct template *tp) }}
478         struct request          *c = tp->tp_arg;
479         struct transport        *t = c->t;
480         struct repo_dir         *repo_dir = t->repo_dir;
481         struct repo_commit      *rc;
482         struct gotweb_url        diff, tree;
484         diff = (struct gotweb_url){
485                 .action = DIFF,
486                 .index_page = -1,
487                 .page = -1,
488                 .path = repo_dir->name,
489         };
490         tree = (struct gotweb_url){
491                 .action = TREE,
492                 .index_page = -1,
493                 .page = -1,
494                 .path = repo_dir->name,
495         };
497 <header class="subtitle">
498   <h2>Commits</h2>
499 </header>
500 <div class="commits_content">
501   {{ tailq-foreach rc &t->repo_commits entry }}
502     {!
503         diff.commit = rc->commit_id;
504         tree.commit = rc->commit_id;
505     !}
506     <div class="commits_header_wrapper">
507       <dl class="commits_header">
508         <dt>Commit:</dt>
509         <dd><code class="commit-id">{{ rc->commit_id }}</code></dd>
510         <dt>From:</dt>
511         <dd>{{ rc->author }}</dd>
512         {{ if strcmp(rc->committer, rc->author) != 0 }}
513           <dt>Via:</dt>
514           <dd>{{ rc->committer }}</dd>
515         {{ end }}
516         <dt>Date:</dt>
517         <dd>
518           {{ render datetime(tp, rc->committer_time, TM_LONG) }}
519         </dd>
520       </dl>
521     </div>
522     <hr />
523     <div class="commit">
524       {{ "\n" }}
525       {{ rc->commit_msg }}
526     </div>
527     <div class="navs_wrapper">
528       <div class="navs">
529         <a href="{{ render gotweb_render_url(c, &diff) }}">diff</a>
530         {{ " | " }}
531         <a href="{{ render gotweb_render_url(c, &tree) }}">tree</a>
532       </div>
533     </div>
534     <hr />
535   {{ end }}
536   {{ render gotweb_render_more(tp, COMMITS) }}
537 </div>
538 {{ end }}
540 {{ define gotweb_render_blob(struct template *tp) }}
542         struct request          *c = tp->tp_arg;
543         struct transport        *t = c->t;
544         struct got_blob_object  *blob = t->blob;
545         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
547 <header class="subtitle">
548   <h2>Blob</h2>
549 </header>
550 <div id="blob_content">
551   <div id="blob_header_wrapper">
552     <dl id="blob_header">
553       <dt>Date:</dt>
554       <dd>
555         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
556       </dd>
557       <dt>Message:</dt>
558       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
559     </dl>
560   </div>
561   <hr />
562   <div id="blob">
563     <pre>
564       {{ render got_output_blob_by_lines(tp, blob, gotweb_render_blob_line) }}
565     </pre>
566   </div>
567 </div>
568 {{ end }}
570 {{ define gotweb_render_blob_line(struct template *tp, const char *line,
571     size_t no) }}
573         char             lineno[16];
574         int              r;
576         r = snprintf(lineno, sizeof(lineno), "%zu", no);
577         if (r < 0 || (size_t)r >= sizeof(lineno))
578                 return -1;
580 <div class="blob_line" id="line{{ lineno }}">
581   <a href="#line{{ lineno }}">{{ lineno }}</a>
582   <span class="blob_code">{{ line }}</span>
583 </div>
584 {{ end }}
586 {{ define gotweb_render_tree(struct template *tp) }}
588         struct request          *c = tp->tp_arg;
589         struct transport        *t = c->t;
590         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
592 <header class='subtitle'>
593   <h2>Tree</h2>
594 </header>
595 <div id="tree_content">
596   <div id="tree_header_wrapper">
597     <dl id="tree_header">
598       <dt>Tree:</dt>
599       <dd><code class="commit-id">{{ rc->tree_id }}</code></dd>
600       <dt>Date:</dt>
601       <dd>
602         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
603       </dd>
604       <dt>Message:</dt>
605       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
606     </dl>
607   </div>
608   <hr />
609   <table id="tree">
610     {{ render got_output_repo_tree(c, gotweb_render_tree_item) }}
611   </table>
612 </div>
613 {{ end }}
615 {{ define gotweb_render_tree_item(struct template *tp,
616     struct got_tree_entry *te) }}
618         struct request          *c = tp->tp_arg;
619         struct transport        *t = c->t;
620         struct querystring      *qs = t->qs;
621         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
622         const char              *modestr = "";
623         const char              *name;
624         const char              *folder;
625         char                    *dir = NULL;
626         mode_t                   mode;
627         struct gotweb_url        url = {
628                .index_page = -1,
629                .page = -1,
630                .commit = rc->commit_id,
631                .path = qs->path,
632         };
634         name = got_tree_entry_get_name(te);
635         mode = got_tree_entry_get_mode(te);
637         folder = qs->folder ? qs->folder : "";
638         if (S_ISDIR(mode)) {
639                 if (asprintf(&dir, "%s/%s", folder, name) == -1)
640                         return (-1);
642                 url.action = TREE;
643                 url.folder = dir;
644         } else {
645                 url.action = BLOB;
646                 url.folder = folder;
647                 url.file = name;
648         }
650         if (got_object_tree_entry_is_submodule(te))
651                 modestr = "$";
652         else if (S_ISLNK(mode))
653                 modestr = "@";
654         else if (S_ISDIR(mode))
655                 modestr = "/";
656         else if (mode & S_IXUSR)
657                 modestr = "*";
659 <tr class="tree_wrapper">
660   {{ if S_ISDIR(mode) }}
661     <td class="tree_line" colspan=2>
662       <a href="{{ render gotweb_render_url(c, &url) }}">
663         {{ name }}{{ modestr }}
664       </a>
665     </td>
666   {{ else }}
667     <td class="tree_line">
668       <a href="{{ render gotweb_render_url(c, &url) }}">
669         {{ name }}{{ modestr }}
670       </a>
671     </td>
672     <td class="tree_line_blank">
673       {! url.action = COMMITS; !}
674       <a href="{{ render gotweb_render_url(c, &url) }}">
675         commits
676       </a>
677       {{ " | " }}
678       {! url.action = BLAME; !}
679       <a href="{{ render gotweb_render_url(c, &url) }}">
680         blame
681       </a>
682     </td>
683   {{ end }}
684 </tr>
685 {{ finally }}
687         free(dir);
689 {{ end }}
691 {{ define gotweb_render_tags(struct template *tp) }}
693         struct request          *c = tp->tp_arg;
694         struct transport        *t = c->t;
695         struct querystring      *qs = t->qs;
696         struct repo_tag         *rt;
697         int                      commit_found;
699         commit_found = qs->commit == NULL;
701 <header class='subtitle'>
702   <h2>Tags</h2>
703 </header>
704 <div id="tags_content">
705   {{ if t->tag_count == 0 }}
706     <div id="err_content">
707       This repository contains no tags
708     </div>
709   {{ else }}
710     {{ tailq-foreach rt &t->repo_tags entry }}
711       {{ if commit_found || !strcmp(qs->commit, rt->commit_id) }}
712         {! commit_found = 1; !}
713         {{ render tag_item(tp, rt) }}
714       {{ end }}
715     {{ end }}
716     {{ if t->next_id || t->prev_id }}
717       {! qs->action = TAGS; !}
718       {{ render gotweb_render_navs(tp) }}
719     {{ end }}
720   {{ end }}
721 </div>
722 {{ end }}
724 {{ define tag_item(struct template *tp, struct repo_tag *rt) }}
726         struct request          *c = tp->tp_arg;
727         struct transport        *t = c->t;
728         struct repo_dir         *repo_dir = t->repo_dir;
729         char                    *tag_name = rt->tag_name;
730         char                    *msg = rt->tag_commit;
731         char                    *nl;
732         struct gotweb_url        url = {
733                 .action = TAG,
734                 .index_page = -1,
735                 .page = -1,
736                 .path = repo_dir->name,
737                 .commit = rt->commit_id,
738         };
740         if (strncmp(tag_name, "refs/tags/", 10) == 0)
741                 tag_name += 10;
743         if (msg) {
744                 nl = strchr(msg, '\n');
745                 if (nl)
746                         *nl = '\0';
747         }
749 <div class="tag_age">
750   {{ render datetime(tp, rt->tagger_time, TM_DIFF) }}
751 </div>
752 <div class="tag_name">{{ tag_name }}</div>
753 <div class="tag_log">
754   <a href="{{ render gotweb_render_url(c, &url) }}">
755     {{ msg }}
756   </a>
757 </div>
758 <div class="navs_wrapper">
759   <div class="navs">
760     <a href="{{ render gotweb_render_url(c, &url) }}">tag</a>
761     {{ " | " }}
762     {! url.action = BRIEFS; !}
763     <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
764     {{ " | " }}
765     {! url.action = COMMITS; !}
766     <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
767   </div>
768 </div>
769 <hr />
770 {{ end }}
772 {{ define gotweb_render_tag(struct template *tp) }}
774         struct request          *c = tp->tp_arg;
775         struct transport        *t = c->t;
776         struct repo_tag         *rt;
777         const char              *tag_name;
779         rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
780         tag_name = rt->tag_name;
782         if (strncmp(tag_name, "refs/", 5) == 0)
783                 tag_name += 5;
785 <header class="subtitle">
786   <h2>Tag</h2>
787 </header>
788 <div id="tags_content">
789   <div id="tag_header_wrapper">
790     <dl id="tag_header">
791       <dt>Commit:</dt>
792       <dd>
793         <code class="commit-id">{{ rt->commit_id }}</code>
794         {{ " " }}
795         <span class="refs_str">({{ tag_name }})</span>
796       </dd>
797       <dt>Tagger:</dt>
798       <dd>{{ rt->tagger }}</dd>
799       <dt>Date:</dt>
800       <dd>
801         {{ render datetime(tp, rt->tagger_time, TM_LONG)}}
802       </dd>
803       <dt>Message:</dt>
804       <dd class="commit-msg">{{ rt->commit_msg }}</dd>
805     </dl>
806     <hr />
807     <pre id="tag_commit">
808       {{ rt->tag_commit }}
809     </pre>
810   </div>
811 </div>
812 {{ end }}
814 {{ define gotweb_render_diff(struct template *tp) }}
816         struct request          *c = tp->tp_arg;
817         struct transport        *t = c->t;
818         FILE                    *fp = t->fp;
819         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
820         char                    *line = NULL;
821         size_t                   linesize = 0;
822         ssize_t                  linelen;
824 <header class="subtitle">
825   <h2>Commit Diff</h2>
826 </header>
827 <div id="diff_content">
828   <div id="diff_header_wrapper">
829     <dl id="diff_header">
830       <dt>Commit:</dt>
831       <dd><code class="commit-id">{{ rc->commit_id }}</code></dd>
832       <dt>From:</dt>
833       <dd>{{ rc->author }}</dd>
834       {{ if strcmp(rc->committer, rc->author) != 0 }}
835         <dt>Via:</dt>
836         <dd>{{ rc->committer }}</dd>
837       {{ end }}
838       <dt>Date:</dt>
839       <dd>
840         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
841       </dd>
842       <dt>Message:</dt>
843       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
844     </dl>
845   </div>
846   <hr />
847   <pre id="diff">
848     {{ while (linelen = getline(&line, &linesize, fp)) != -1 }}
849       {{ render diff_line(tp, line) }}
850     {{ end }}
851   </pre>
852 </div>
853 {{ finally }}
854 {! free(line); !}
855 {{ end }}
857 {{ define diff_line(struct template *tp, char *line )}}
859         const char              *color = NULL;
860         char                    *nl;
862         if (!strncmp(line, "-", 1))
863                 color = "diff_minus";
864         else if (!strncmp(line, "+", 1))
865                 color = "diff_plus";
866         else if (!strncmp(line, "@@", 2))
867                 color = "diff_chunk_header";
868         else if (!strncmp(line, "commit +", 8) ||
869             !strncmp(line, "commit -", 8) ||
870             !strncmp(line, "blob +", 6) ||
871             !strncmp(line, "blob -", 6) ||
872             !strncmp(line, "file +", 6) ||
873             !strncmp(line, "file -", 6))
874                 color = "diff_meta";
875         else if (!strncmp(line, "from:", 5) || !strncmp(line, "via:", 4))
876                 color = "diff_author";
877         else if (!strncmp(line, "date:", 5))
878                 color = "diff_date";
880         nl = strchr(line, '\n');
881         if (nl)
882                 *nl = '\0';
884 <span class="diff_line {{ color }}">{{ line }}</span>{{"\n"}}
885 {{ end }}
887 {{ define gotweb_render_branches(struct template *tp,
888     struct got_reflist_head *refs) }}
890         struct got_reflist_entry        *re;
892 <header class='subtitle'>
893   <h2>Branches</h2>
894 </header>
895 <div id="branches_content">
896   {{ tailq-foreach re refs entry }}
897     {{ if !got_ref_is_symbolic(re->ref) }}
898       {{ render branch(tp, re) }}
899     {{ end }}
900   {{ end }}
901 </div>
902 {{ end }}
904 {{ define branch(struct template *tp, struct got_reflist_entry *re) }}
906         const struct got_error  *err;
907         struct request          *c = tp->tp_arg;
908         struct querystring      *qs = c->t->qs;
909         const char              *refname;
910         time_t                   age;
911         struct gotweb_url        url = {
912                 .action = SUMMARY,
913                 .index_page = -1,
914                 .page = -1,
915                 .path = qs->path,
916         };
918         refname = got_ref_get_name(re->ref);
920         err = got_get_repo_age(&age, c, refname);
921         if (err) {
922                 log_warnx("%s: %s", __func__, err->msg);
923                 return -1;
924         }
926         if (strncmp(refname, "refs/heads/", 11) == 0)
927                 refname += 11;
929         url.headref = refname;
931 <section class="branches_wrapper">
932   <div class="branches_age">
933     {{ render datetime(tp, age, TM_DIFF) }}
934   </div>
935   <div class="branch">
936     <a href="{{ render gotweb_render_url(c, &url) }}">{{ refname }}</a>
937   </div>
938   <div class="navs_wrapper">
939     <div class="navs">
940       <a href="{{ render gotweb_render_url(c, &url) }}">summary</a>
941       {{" | "}}
942       {! url.action = BRIEFS; !}
943       <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
944       {{" | "}}
945       {! url.action = COMMITS; !}
946       <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
947     </div>
948   </div>
949   <hr />
950 </section>
951 {{ end }}
953 {{ define gotweb_render_summary(struct template *tp) }}
955         struct request          *c = tp->tp_arg;
956         struct server           *srv = c->srv;
957         struct transport        *t = c->t;
958         struct got_reflist_head *refs = &t->refs;
960 <dl id="summary_wrapper">
961   {{ if srv->show_repo_description }}
962     <dt>Description:</dt>
963     <dd>{{ t->repo_dir->description }}</dd>
964   {{ end }}
965   {{ if srv->show_repo_owner }}
966     <dt>Owner:</dt>
967     <dd>{{ t->repo_dir->owner }}</dd>
968   {{ end }}
969   {{ if srv->show_repo_age }}
970     <dt>Last Change:</dt>
971     <dd>
972       {{ render datetime(tp, t->repo_dir->age, TM_DIFF) }}
973     </dd>
974   {{ end }}
975   {{ if srv->show_repo_cloneurl }}
976     <dt>Clone URL:</dt>
977     <dd><pre class="clone-url">{{ t->repo_dir->url }}</pre></dd>
978   {{ end }}
979 </dl>
980 {{ render gotweb_render_briefs(tp) }}
981 {{ render gotweb_render_tags(tp) }}
982 {{ render gotweb_render_branches(tp, refs) }}
983 {{ end }}
985 {{ define gotweb_render_blame(struct template *tp) }}
987         const struct got_error  *err;
988         struct request          *c = tp->tp_arg;
989         struct transport        *t = c->t;
990         struct repo_commit      *rc = TAILQ_FIRST(&t->repo_commits);
992 <header class="subtitle">
993   <h2>Blame</h2>
994 </header>
995 <div id="blame_content">
996   <div id="blame_header_wrapper">
997     <dl id="blame_header">
998       <dt>Date:</dt>
999       <dd>
1000         {{ render datetime(tp, rc->committer_time, TM_LONG) }}
1001       </dd>
1002       <dt>Message:</dt>
1003       <dd class="commit-msg">{{ rc->commit_msg }}</dd>
1004     </dl>
1005   </div>
1006   <hr />
1007   <pre id="blame">
1008     {!
1009         err = got_output_file_blame(c, &blame_line);
1010         if (err && err->code != GOT_ERR_CANCELLED)
1011                 log_warnx("%s: got_output_file_blame: %s", __func__,
1012                     err->msg);
1013         if (err)
1014                 return (-1);
1015     !}
1016   </pre>
1017 </div>
1018 {{ end }}
1020 {{ define blame_line(struct template *tp, const char *line,
1021     struct blame_line *bline, int lprec, int lcur) }}
1023         struct request          *c = tp->tp_arg;
1024         struct transport        *t = c->t;
1025         struct repo_dir         *repo_dir = t->repo_dir;
1026         char                    *committer, *s;
1027         struct gotweb_url        url = {
1028                 .action = DIFF,
1029                 .index_page = -1,
1030                 .page = -1,
1031                 .path = repo_dir->name,
1032                 .commit = bline->id_str,
1033         };
1035         s = strchr(bline->committer, '<');
1036         committer = s ? s + 1 : bline->committer;
1038         s = strchr(committer, '@');
1039         if (s)
1040                 *s = '\0';
1042 <div class="blame_wrapper">
1043   <div class="blame_number">{{ printf "%.*d", lprec, lcur }}</div>
1044   <div class="blame_hash">
1045     <a href="{{ render gotweb_render_url(c, &url) }}">
1046       {{ printf "%.8s", bline->id_str }}
1047     </a>
1048   </div>
1049   <div class="blame_date">{{ bline->datebuf }}</div>
1050   <div class="blame_author">{{ printf "%.9s", committer }}</div>
1051   <div class="blame_code">{{ line }}</div>
1052 </div>
1053 {{ end }}
1055 {{ define gotweb_render_rss(struct template *tp) }}
1057         struct request          *c = tp->tp_arg;
1058         struct server           *srv = c->srv;
1059         struct transport        *t = c->t;
1060         struct repo_dir         *repo_dir = t->repo_dir;
1061         struct repo_tag         *rt;
1062         struct gotweb_url        summary = {
1063                 .action = SUMMARY,
1064                 .index_page = -1,
1065                 .page = -1,
1066                 .path = repo_dir->name,
1067         };
1069 <?xml version="1.0" encoding="UTF-8"?>
1070 <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
1071   <channel>
1072     <title>Tags of {{ repo_dir->name }}</title>
1073     <link>
1074       <![CDATA[
1075         {{ render gotweb_render_absolute_url(c, &summary) }}
1076       ]]>
1077     </link>
1078     {{ if srv->show_repo_description }}
1079       <description>{{ repo_dir->description }}</description>
1080     {{ end }}
1081     {{ tailq-foreach rt &t->repo_tags entry }}
1082       {{ render rss_tag_item(tp, rt) }}
1083     {{ end }}
1084   </channel>
1085 </rss>
1086 {{ end }}
1088 {{ define rss_tag_item(struct template *tp, struct repo_tag *rt) }}
1090         struct request          *c = tp->tp_arg;
1091         struct transport        *t = c->t;
1092         struct repo_dir         *repo_dir = t->repo_dir;
1093         struct tm                tm;
1094         char                     rfc822[128];
1095         int                      r;
1096         char                    *tag_name = rt->tag_name;
1097         struct gotweb_url        tag = {
1098                 .action = TAG,
1099                 .index_page = -1,
1100                 .page = -1,
1101                 .path = repo_dir->name,
1102                 .commit = rt->commit_id,
1103         };
1105         if (strncmp(tag_name, "refs/tags/", 10) == 0)
1106                 tag_name += 10;
1108         if (gmtime_r(&rt->tagger_time, &tm) == NULL)
1109                 return -1;
1110         r = strftime(rfc822, sizeof(rfc822), "%a, %d %b %Y %H:%M:%S GMT", &tm);
1111         if (r == 0)
1112                 return 0;
1114 <item>
1115   <title>{{ repo_dir->name }} {{" "}} {{ tag_name }}</title>
1116   <link>
1117     <![CDATA[
1118       {{ render gotweb_render_absolute_url(c, &tag) }}
1119     ]]>
1120   </link>
1121   <description>
1122     <![CDATA[<pre>{{ rt->tag_commit }}</pre>]]>
1123   </description>
1124   {{ render rss_author(tp, rt->tagger) }}
1125   <guid isPermaLink="false">{{ rt->commit_id }}</guid>
1126   <pubDate>
1127     {{ rfc822 }}
1128   </pubDate>
1129 </item>
1130 {{ end }}
1132 {{ define rss_author(struct template *tp, char *author) }}
1134         char    *t, *mail;
1136         /* what to do if the author name contains a paren? */
1137         if (strchr(author, '(') != NULL || strchr(author, ')') != NULL)
1138                 return 0;
1140         t = strchr(author, '<');
1141         if (t == NULL)
1142                 return 0;
1143         *t = '\0';
1144         mail = t+1;
1146         while (isspace((unsigned char)*--t))
1147                 *t = '\0';
1149         t = strchr(mail, '>');
1150         if (t == NULL)
1151                 return 0;
1152         *t = '\0';
1154 <author>
1155   {{ mail }} {{" "}} ({{ author }})
1156 </author>
1157 {{ end }}