3 * Copyright (c) 2022 Omar Polo <op@openbsd.org>
4 * Copyright (c) 2016, 2019, 2020-2022 Tracey Emery <tracey@traceyemery.net>
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.
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.
19 #include "got_compat.h"
21 #include <sys/types.h>
22 #include <sys/queue.h>
33 #include "got_error.h"
34 #include "got_object.h"
35 #include "got_reference.h"
42 static int gotweb_render_blob_line(struct template *, const char *, size_t);
43 static int gotweb_render_tree_item(struct template *, struct got_tree_entry *);
44 static int blame_line(struct template *, const char *, struct blame_line *,
47 static inline int gotweb_render_more(struct template *, int);
49 static inline int diff_line(struct template *, char *);
50 static inline int tag_item(struct template *, struct repo_tag *);
51 static inline int branch(struct template *, struct got_reflist_entry *);
52 static inline int rss_tag_item(struct template *, struct repo_tag *);
53 static inline int rss_author(struct template *, char *);
57 {{ define gotweb_render_page(struct template *tp,
58 int (*body)(struct template *)) }}
60 struct request *c = tp->tp_arg;
61 struct server *srv = c->srv;
62 struct querystring *qs = c->t->qs;
63 struct gotweb_url u_path;
64 const char *prfx = c->document_uri;
65 const char *css = srv->custom_css;
67 memset(&u_path, 0, sizeof(u_path));
68 u_path.index_page = -1;
70 u_path.action = SUMMARY;
75 <meta charset="utf-8" />
76 <title>{{ srv->site_name }}</title>
77 <meta name="viewport" content="initial-scale=.75" />
78 <meta name="msapplication-TileColor" content="#da532c" />
79 <meta name="theme-color" content="#ffffff"/>
80 <link rel="apple-touch-icon" sizes="180x180" href="{{ prfx }}apple-touch-icon.png" />
81 <link rel="icon" type="image/png" sizes="32x32" href="{{ prfx }}favicon-32x32.png" />
82 <link rel="icon" type="image/png" sizes="16x16" href="{{ prfx }}favicon-16x16.png" />
83 <link rel="manifest" href="{{ prfx }}site.webmanifest"/>
84 <link rel="mask-icon" href="{{ prfx }}safari-pinned-tab.svg" />
85 <link rel="stylesheet" type="text/css" href="{{ prfx }}{{ css }}" />
91 <a href="{{ srv->logo_url }}" target="_blank">
92 <img src="{{ prfx }}{{ srv->logo }}" />
98 <a href="?index_page={{ printf "%d", qs->index_page }}">
102 {! u_path.path = qs->path; !}
104 <a href="{{ render gotweb_render_url(tp->tp_arg, &u_path)}}">
108 {{ if qs->action != INDEX }}
109 {{ " / " }}{{ gotweb_action_name(qs->action) }}
114 {{ render body(tp) }}
115 <div id="site_owner_wrapper">
116 <div id="site_owner">
117 {{ if srv->show_site_owner }}
118 {{ srv->site_owner }}
128 {{ define gotweb_render_repo_table_hdr(struct template *tp) }}
130 struct request *c = tp->tp_arg;
131 struct server *srv = c->srv;
133 <div id="index_header">
134 <div id="index_header_project">
137 {{ if srv->show_repo_description }}
138 <div id="index_header_description">
142 {{ if srv->show_repo_owner }}
143 <div id="index_header_owner">
147 {{ if srv->show_repo_age }}
148 <div id="index_header_age">
155 {{ define gotweb_render_repo_fragment(struct template *tp, struct repo_dir *repo_dir) }}
157 struct request *c = tp->tp_arg;
158 struct server *srv = c->srv;
159 struct gotweb_url summary = {
163 .path = repo_dir->name,
168 .path = repo_dir->name,
173 .path = repo_dir->name,
178 .path = repo_dir->name,
183 .path = repo_dir->name,
188 .path = repo_dir->name,
191 <div class="index_wrapper">
192 <div class="index_project">
193 <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">{{ repo_dir->name }}</a>
195 {{ if srv->show_repo_description }}
196 <div class="index_project_description">
197 {{ repo_dir->description }}
200 {{ if srv->show_repo_owner }}
201 <div class="index_project_owner">
202 {{ repo_dir->owner }}
205 {{ if srv->show_repo_age }}
206 <div class="index_project_age">
207 {{ render gotweb_render_age(tp, repo_dir->age, TM_DIFF) }}
210 <div class="navs_wrapper">
212 <a href="{{ render gotweb_render_url(tp->tp_arg, &summary) }}">summary</a>
214 <a href="{{ render gotweb_render_url(tp->tp_arg, &briefs) }}">briefs</a>
216 <a href="{{ render gotweb_render_url(tp->tp_arg, &commits) }}">commits</a>
218 <a href="{{ render gotweb_render_url(tp->tp_arg, &tags) }}">tags</a>
220 <a href="{{ render gotweb_render_url(tp->tp_arg, &tree) }}">tree</a>
222 <a href="{{ render gotweb_render_url(tp->tp_arg, &rss) }}">rss</a>
224 <div class="dotted_line"></div>
229 {{ define gotweb_render_briefs(struct template *tp) }}
231 const struct got_error *error;
232 struct request *c = tp->tp_arg;
233 struct server *srv = c->srv;
234 struct transport *t = c->t;
235 struct querystring *qs = c->t->qs;
236 struct repo_commit *rc;
237 struct repo_dir *repo_dir = t->repo_dir;
238 struct gotweb_url diff_url, tree_url;
241 diff_url = (struct gotweb_url){
245 .path = repo_dir->name,
246 .headref = qs->headref,
248 tree_url = (struct gotweb_url){
252 .path = repo_dir->name,
253 .headref = qs->headref,
256 if (qs->action == SUMMARY) {
258 error = got_get_repo_commits(c, D_MAXSLCOMMDISP);
260 error = got_get_repo_commits(c, srv->max_commits_display);
264 <div id="briefs_title_wrapper">
265 <div id="briefs_title">Commit Briefs</div>
267 <div id="briefs_content">
268 {{ tailq-foreach rc &t->repo_commits entry }}
270 diff_url.commit = rc->commit_id;
271 tree_url.commit = rc->commit_id;
273 tmp = strchr(rc->committer, '<');
277 tmp = strchr(rc->commit_msg, '\n');
281 <div class="briefs_age">
282 {{ render gotweb_render_age(tp, rc->committer_time, TM_DIFF) }}
284 <div class="briefs_author">
287 <div class="briefs_log">
288 <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">
291 {{ if rc->refs_str }}
292 {{ " " }} <span class="refs_str">({{ rc->refs_str }})</span>
296 <div class="navs_wrapper">
298 <a href="{{ render gotweb_render_url(tp->tp_arg, &diff_url) }}">diff</a>
300 <a href="{{ render gotweb_render_url(tp->tp_arg, &tree_url) }}">tree</a>
303 <div class="dotted_line"></div>
305 {{ render gotweb_render_more(tp, BRIEFS) }}
309 {{ define gotweb_render_more(struct template *tp, int action) }}
311 struct request *c = tp->tp_arg;
312 struct transport *t = c->t;
313 struct querystring *qs = t->qs;
314 struct gotweb_url more = {
318 .commit = t->more_id,
319 .headref = qs->headref,
323 <div id="np_wrapper">
325 <a href="{{ render gotweb_render_url(c, &more) }}">
333 {{ define gotweb_render_navs(struct template *tp) }}
335 struct request *c = tp->tp_arg;
336 struct transport *t = c->t;
337 struct gotweb_url prev, next;
338 int have_prev, have_next;
340 gotweb_get_navs(c, &prev, &have_prev, &next, &have_next);
342 <div id="np_wrapper">
345 <a href="{{ render gotweb_render_url(c, &prev) }}">
352 <a href="{{ render gotweb_render_url(c, &next) }}">
367 {{ define gotweb_render_commits(struct template *tp) }}
369 struct request *c = tp->tp_arg;
370 struct transport *t = c->t;
371 struct repo_dir *repo_dir = t->repo_dir;
372 struct repo_commit *rc;
373 struct gotweb_url diff, tree;
375 diff = (struct gotweb_url){
379 .path = repo_dir->name,
381 tree = (struct gotweb_url){
385 .path = repo_dir->name,
388 <div class="commits_title_wrapper">
389 <div class="commits_title">Commits</div>
391 <div class="commits_content">
392 {{ tailq-foreach rc &t->repo_commits entry }}
394 diff.commit = rc->commit_id;
395 tree.commit = rc->commit_id;
397 <div class="commits_header_wrapper">
398 <div class="commits_header">
399 <div class="header_commit_title">Commit:</div>
400 <div class="header_commit">{{ rc->commit_id }}</div>
401 <div class="header_author_title">From:</div>
402 <div class="header_author">{{ rc->author }}</div>
403 {{ if strcmp(rc->committer, rc->author) != 0 }}
404 <div class="header_author_title">Via:</div>
405 <div class="header_author">{{ rc->committer }}</div>
407 <div class="header_age_title">Date:</div>
408 <div class="header_age">
409 {{ render gotweb_render_age(tp, rc->committer_time, TM_LONG) }}
413 <div class="dotted_line"></div>
418 <div class="navs_wrapper">
420 <a href="{{ render gotweb_render_url(c, &diff) }}">diff</a>
422 <a href="{{ render gotweb_render_url(c, &tree) }}">tree</a>
425 <div class="dotted_line"></div>
427 {{ render gotweb_render_more(tp, COMMITS) }}
431 {{ define gotweb_render_blob(struct template *tp) }}
433 struct request *c = tp->tp_arg;
434 struct transport *t = c->t;
435 struct got_blob_object *blob = t->blob;
436 struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits);
438 <div id="blob_title_wrapper">
439 <div id="blob_title">Blob</div>
441 <div id="blob_content">
442 <div id="blob_header_wrapper">
443 <div id="blob_header">
444 <div class="header_age_title">Date:</div>
445 <div class="header_age">
446 {{ render gotweb_render_age(tp, rc->committer_time, TM_LONG) }}
448 <div id="header_commit_msg_title">Message:</div>
449 <div id="header_commit_msg">{{ rc->commit_msg }}</div>
452 <div class="dotted_line"></div>
455 {{ render got_output_blob_by_lines(tp, blob, gotweb_render_blob_line) }}
461 {{ define gotweb_render_blob_line(struct template *tp, const char *line,
467 r = snprintf(lineno, sizeof(lineno), "%zu", no);
468 if (r < 0 || (size_t)r >= sizeof(lineno))
471 <div class="blob_line" id="line{{ lineno }}">
472 <div class="blob_number">
473 <a href="#line{{ lineno }}">{{ lineno }}</a>
475 <div class="blob_code">{{ line }}</div>
479 {{ define gotweb_render_tree(struct template *tp) }}
481 struct request *c = tp->tp_arg;
482 struct transport *t = c->t;
483 struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits);
485 <div id="tree_title_wrapper">
486 <div id="tree_title">Tree</div>
488 <div id="tree_content">
489 <div id="tree_header_wrapper">
490 <div id="tree_header">
491 <div id="header_tree_title">Tree:</div>
492 <div id="header_tree">{{ rc->tree_id }}</div>
493 <div class="header_age_title">Date:</div>
494 <div class="header_age">
495 {{ render gotweb_render_age(tp, rc->committer_time, TM_LONG) }}
497 <div id="header_commit_msg_title">Message:</div>
498 <div id="header_commit_msg">{{ rc->commit_msg }}</div>
501 <div class="dotted_line"></div>
503 {{ render got_output_repo_tree(c, gotweb_render_tree_item) }}
508 {{ define gotweb_render_tree_item(struct template *tp,
509 struct got_tree_entry *te) }}
511 struct request *c = tp->tp_arg;
512 struct transport *t = c->t;
513 struct querystring *qs = t->qs;
514 struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits);
515 const char *modestr = "";
520 struct gotweb_url url = {
523 .commit = rc->commit_id,
527 name = got_tree_entry_get_name(te);
528 mode = got_tree_entry_get_mode(te);
530 folder = qs->folder ? qs->folder : "";
532 if (asprintf(&dir, "%s/%s", folder, name) == -1)
543 if (got_object_tree_entry_is_submodule(te))
545 else if (S_ISLNK(mode))
547 else if (S_ISDIR(mode))
549 else if (mode & S_IXUSR)
552 <div class="tree_wrapper">
553 {{ if S_ISDIR(mode) }}
554 <div class="tree_line">
555 <a href="{{ render gotweb_render_url(c, &url) }}">
556 {{ name }}{{ modestr }}
559 <div class="tree_line_blank"> </div>
561 <div class="tree_line">
562 <a href="{{ render gotweb_render_url(c, &url) }}">
563 {{ name }}{{ modestr }}
566 <div class="tree_line_blank">
567 {! url.action = COMMITS; !}
568 <a href="{{ render gotweb_render_url(c, &url) }}">
572 {! url.action = BLAME; !}
573 <a href="{{ render gotweb_render_url(c, &url) }}">
585 {{ define gotweb_render_tags(struct template *tp) }}
587 struct request *c = tp->tp_arg;
588 struct transport *t = c->t;
589 struct querystring *qs = t->qs;
593 commit_found = qs->commit == NULL;
595 <div id="tags_title_wrapper">
596 <div id="tags_title">Tags</div>
598 <div id="tags_content">
599 {{ if t->tag_count == 0 }}
600 <div id="err_content">
601 This repository contains no tags
604 {{ tailq-foreach rt &t->repo_tags entry }}
605 {{ if commit_found || !strcmp(qs->commit, rt->commit_id) }}
606 {! commit_found = 1; !}
607 {{ render tag_item(tp, rt) }}
610 {{ if t->next_id || t->prev_id }}
611 {! qs->action = TAGS; !}
612 {{ render gotweb_render_navs(tp) }}
618 {{ define tag_item(struct template *tp, struct repo_tag *rt) }}
620 struct request *c = tp->tp_arg;
621 struct transport *t = c->t;
622 struct repo_dir *repo_dir = t->repo_dir;
623 char *tag_name = rt->tag_name;
624 char *msg = rt->tag_commit;
626 struct gotweb_url url = {
630 .path = repo_dir->name,
631 .commit = rt->commit_id,
634 if (strncmp(tag_name, "refs/tags/", 10) == 0)
638 nl = strchr(msg, '\n');
643 <div class="tag_age">
644 {{ render gotweb_render_age(tp, rt->tagger_time, TM_DIFF) }}
646 <div class="tag">{{ tag_name }}</div>
647 <div class="tag_log">
648 <a href="{{ render gotweb_render_url(c, &url) }}">
652 <div class="navs_wrapper">
654 <a href="{{ render gotweb_render_url(c, &url) }}">tag</a>
656 {! url.action = BRIEFS; !}
657 <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
659 {! url.action = COMMITS; !}
660 <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
663 <div class="dotted_line"></div>
666 {{ define gotweb_render_tag(struct template *tp) }}
668 struct request *c = tp->tp_arg;
669 struct transport *t = c->t;
671 const char *tag_name;
673 rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
674 tag_name = rt->tag_name;
676 if (strncmp(tag_name, "refs/", 5) == 0)
679 <div id="tags_title_wrapper">
680 <div id="tags_title">Tag</div>
682 <div id="tags_content">
683 <div id="tag_header_wrapper">
684 <div id="tag_header">
685 <div class="header_commit_title">Commit:</div>
686 <div class="header_commit">
689 <span class="refs_str">({{ tag_name }})</span>
691 <div class="header_author_title">Tagger:</div>
692 <div class="header_author">{{ rt->tagger }}</div>
693 <div class="header_age_title">Date:</div>
694 <div class="header_age">
695 {{ render gotweb_render_age(tp, rt->tagger_time, TM_LONG)}}
697 <div id="header_commit_msg_title">Message:</div>
698 <div id="header_commit_msg">{{ rt->commit_msg }}</div>
700 <div class="dotted_line"></div>
701 <div id="tag_commit">
709 {{ define gotweb_render_diff(struct template *tp) }}
711 struct request *c = tp->tp_arg;
712 struct transport *t = c->t;
714 struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits);
719 <div id="diff_title_wrapper">
720 <div id="diff_title">Commit Diff</div>
722 <div id="diff_content">
723 <div id="diff_header_wrapper">
724 <div id="diff_header">
725 <div class="header_commit_title">Commit:</div>
726 <div class="header_commit">{{ rc->commit_id }}</div>
727 <div class="header_author_title">From:</div>
728 <div class="header_author">{{ rc->author }}</div>
729 {{ if strcmp(rc->committer, rc->author) != 0 }}
730 <div class="header_author_title">Via:</div>
731 <div class="header_author">{{ rc->committer }}</div>
733 <div class="header_age_title">Date:</div>
734 <div class="header_age">
735 {{ render gotweb_render_age(tp, rc->committer_time, TM_LONG) }}
737 <div id="header_commit_msg_title">Message:</div>
738 <div id="header_commit_msg">{{ rc->commit_msg }}</div>
741 <div class="dotted_line"></div>
744 {{ while (linelen = getline(&line, &linesize, fp)) != -1 }}
745 {{ render diff_line(tp, line) }}
753 {{ define diff_line(struct template *tp, char *line )}}
755 const char *color = NULL;
758 if (!strncmp(line, "-", 1))
759 color = "diff_minus";
760 else if (!strncmp(line, "+", 1))
762 else if (!strncmp(line, "@@", 2))
763 color = "diff_chunk_header";
764 else if (!strncmp(line, "commit +", 8) ||
765 !strncmp(line, "commit -", 8) ||
766 !strncmp(line, "blob +", 6) ||
767 !strncmp(line, "blob -", 6) ||
768 !strncmp(line, "file +", 6) ||
769 !strncmp(line, "file -", 6))
771 else if (!strncmp(line, "from:", 5) || !strncmp(line, "via:", 4))
772 color = "diff_author";
773 else if (!strncmp(line, "date:", 5))
776 nl = strchr(line, '\n');
780 <div class="diff_line {{ color }}">{{ line }}</div>
783 {{ define gotweb_render_branches(struct template *tp,
784 struct got_reflist_head *refs) }}
786 struct got_reflist_entry *re;
788 <div id="branches_title_wrapper">
789 <div id="branches_title">Branches</div>
791 <div id="branches_content">
792 {{ tailq-foreach re refs entry }}
793 {{ if !got_ref_is_symbolic(re->ref) }}
794 {{ render branch(tp, re) }}
800 {{ define branch(struct template *tp, struct got_reflist_entry *re) }}
802 const struct got_error *err;
803 struct request *c = tp->tp_arg;
804 struct querystring *qs = c->t->qs;
807 struct gotweb_url url = {
814 refname = got_ref_get_name(re->ref);
816 err = got_get_repo_age(&age, c, refname);
818 log_warnx("%s: %s", __func__, err->msg);
822 if (strncmp(refname, "refs/heads/", 11) == 0)
825 url.headref = refname;
827 <div class="branches_wrapper">
828 <div class="branches_age">
829 {{ render gotweb_render_age(tp, age, TM_DIFF) }}
831 <div class="branches_space"> </div>
833 <a href="{{ render gotweb_render_url(c, &url) }}">{{ refname }}</a>
835 <div class="navs_wrapper">
837 <a href="{{ render gotweb_render_url(c, &url) }}">summary</a>
839 {! url.action = BRIEFS; !}
840 <a href="{{ render gotweb_render_url(c, &url) }}">commit briefs</a>
842 {! url.action = COMMITS; !}
843 <a href="{{ render gotweb_render_url(c, &url) }}">commits</a>
846 <div class="dotted_line"></div>
850 {{ define gotweb_render_summary(struct template *tp) }}
852 struct request *c = tp->tp_arg;
853 struct server *srv = c->srv;
854 struct transport *t = c->t;
855 struct got_reflist_head *refs = &t->refs;
857 <div id="summary_wrapper">
858 {{ if srv->show_repo_description }}
859 <div id="description_title">Description:</div>
860 <div id="description">{{ t->repo_dir->description }}</div>
862 {{ if srv->show_repo_owner }}
863 <div id="repo_owner_title">Owner:</div>
864 <div id="repo_owner">{{ t->repo_dir->owner }}</div>
866 {{ if srv->show_repo_age }}
867 <div id="last_change_title">Last Change:</div>
868 <div id="last_change">
869 {{ render gotweb_render_age(tp, t->repo_dir->age, TM_DIFF) }}
872 {{ if srv->show_repo_cloneurl }}
873 <div id="cloneurl_title">Clone URL:</div>
874 <div id="cloneurl">{{ t->repo_dir->url }}</div>
877 {{ render gotweb_render_briefs(tp) }}
878 {{ render gotweb_render_tags(tp) }}
879 {{ render gotweb_render_branches(tp, refs) }}
882 {{ define gotweb_render_blame(struct template *tp) }}
884 const struct got_error *err;
885 struct request *c = tp->tp_arg;
886 struct transport *t = c->t;
887 struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits);
889 <div id="blame_title_wrapper">
890 <div id="blame_title">Blame</div>
892 <div id="blame_content">
893 <div id="blame_header_wrapper">
894 <div id="blame_header">
895 <div class="header_age_title">Date:</div>
896 <div class="header_age">
897 {{ render gotweb_render_age(tp, rc->committer_time, TM_LONG) }}
899 <div id="header_commit_msg_title">Message:</div>
900 <div id="header_commit_msg">{{ rc->commit_msg }}</div>
903 <div class="dotted_line"></div>
907 err = got_output_file_blame(c, &blame_line);
909 log_warnx("%s: got_output_file_blame: %s", __func__,
918 {{ define blame_line(struct template *tp, const char *line,
919 struct blame_line *bline, int lprec, int lcur) }}
921 struct request *c = tp->tp_arg;
922 struct transport *t = c->t;
923 struct repo_dir *repo_dir = t->repo_dir;
925 struct gotweb_url url = {
929 .path = repo_dir->name,
930 .commit = bline->id_str,
933 s = strchr(bline->committer, '<');
934 committer = s ? s + 1 : bline->committer;
936 s = strchr(committer, '@');
940 <div class="blame_wrapper">
941 <div class="blame_number">{{ printf "%.*d", lprec, lcur }}</div>
942 <div class="blame_hash">
943 <a href="{{ render gotweb_render_url(c, &url) }}">
944 {{ printf "%.8s", bline->id_str }}
947 <div class="blame_date">{{ bline->datebuf }}</div>
948 <div class="blame_author">{{ printf "%.9s", committer }}</div>
949 <div class="blame_code">{{ line }}</div>
953 {{ define gotweb_render_rss(struct template *tp) }}
955 struct request *c = tp->tp_arg;
956 struct server *srv = c->srv;
957 struct transport *t = c->t;
958 struct repo_dir *repo_dir = t->repo_dir;
960 struct gotweb_url summary = {
964 .path = repo_dir->name,
967 <?xml version="1.0" encoding="UTF-8"?>
968 <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
970 <title>Tags of {{ repo_dir->name }}</title>
973 {{ render gotweb_render_absolute_url(c, &summary) }}
976 {{ if srv->show_repo_description }}
977 <description>{{ repo_dir->description }}</description>
979 {{ tailq-foreach rt &t->repo_tags entry }}
980 {{ render rss_tag_item(tp, rt) }}
986 {{ define rss_tag_item(struct template *tp, struct repo_tag *rt) }}
988 struct request *c = tp->tp_arg;
989 struct transport *t = c->t;
990 struct repo_dir *repo_dir = t->repo_dir;
991 char *tag_name = rt->tag_name;
992 struct gotweb_url tag = {
996 .path = repo_dir->name,
997 .commit = rt->commit_id,
1000 if (strncmp(tag_name, "refs/tags/", 10) == 0)
1004 <title>{{ repo_dir->name }} {{" "}} {{ tag_name }}</title>
1007 {{ render gotweb_render_absolute_url(c, &tag) }}
1011 <![CDATA[<pre>{{ rt->tag_commit }}</pre>]]>
1013 {{ render rss_author(tp, rt->tagger) }}
1014 <guid isPermaLink="false">{{ rt->commit_id }}</guid>
1016 {{ render gotweb_render_age(tp, rt->tagger_time, TM_RFC822) }}
1021 {{ define rss_author(struct template *tp, char *author) }}
1025 /* what to do if the author name contains a paren? */
1026 if (strchr(author, '(') != NULL || strchr(author, ')') != NULL)
1029 t = strchr(author, '<');
1035 while (isspace((unsigned char)*--t))
1038 t = strchr(mail, '>');
1044 {{ mail }} {{" "}} ({{ author }})