2 * Copyright (c) 2016, 2019, 2020-2022 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
4 * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
5 * Copyright (c) 2013 Florian Obser <florian@openbsd.org>
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 #include <netinet/in.h>
22 #include <sys/queue.h>
24 #include <sys/types.h>
36 #include "got_error.h"
37 #include "got_object.h"
38 #include "got_reference.h"
39 #include "got_repository.h"
41 #include "got_cancel.h"
42 #include "got_worktree.h"
44 #include "got_commit_graph.h"
45 #include "got_blame.h"
46 #include "got_privsep.h"
56 static const struct querystring_keys querystring_keys[] = {
61 { "headref", HEADREF },
62 { "index_page", INDEX_PAGE },
67 static const struct action_keys action_keys[] = {
71 { "commits", COMMITS },
75 { "summary", SUMMARY },
81 static const struct got_error *gotweb_init_querystring(struct querystring **);
82 static const struct got_error *gotweb_parse_querystring(struct querystring **,
84 static const struct got_error *gotweb_assign_querystring(struct querystring **,
86 static const struct got_error *gotweb_render_header(struct request *);
87 static const struct got_error *gotweb_render_footer(struct request *);
88 static const struct got_error *gotweb_render_index(struct request *);
89 static const struct got_error *gotweb_init_repo_dir(struct repo_dir **,
91 static const struct got_error *gotweb_load_got_path(struct request *c,
93 static const struct got_error *gotweb_get_repo_description(char **,
94 struct server *, char *);
95 static const struct got_error *gotweb_get_clone_url(char **, struct server *,
97 static const struct got_error *gotweb_render_navs(struct request *);
98 static const struct got_error *gotweb_render_blame(struct request *);
99 static const struct got_error *gotweb_render_briefs(struct request *);
100 static const struct got_error *gotweb_render_commits(struct request *);
101 static const struct got_error *gotweb_render_diff(struct request *);
102 static const struct got_error *gotweb_render_summary(struct request *);
103 static const struct got_error *gotweb_render_tag(struct request *);
104 static const struct got_error *gotweb_render_tags(struct request *);
105 static const struct got_error *gotweb_render_tree(struct request *);
106 static const struct got_error *gotweb_render_branches(struct request *);
108 static void gotweb_free_querystring(struct querystring *);
109 static void gotweb_free_repo_dir(struct repo_dir *);
111 struct server *gotweb_get_server(uint8_t *, uint8_t *, uint8_t *);
114 gotweb_process_request(struct request *c)
116 const struct got_error *error = NULL, *error2 = NULL;
117 struct server *srv = NULL;
118 struct querystring *qs = NULL;
119 struct repo_dir *repo_dir = NULL;
120 uint8_t err[] = "gotwebd experienced an error: ";
123 /* init the transport */
124 error = gotweb_init_transport(&c->t);
126 log_warnx("%s: %s", __func__, error->msg);
129 /* don't process any further if client disconnected */
130 if (c->sock->client_status == CLIENT_DISCONNECT)
132 /* get the gotwebd server */
133 srv = gotweb_get_server(c->server_name, c->document_root, c->http_host);
135 log_warnx("%s: error server is NULL", __func__);
139 /* parse our querystring */
140 error = gotweb_init_querystring(&qs);
142 log_warnx("%s: %s", __func__, error->msg);
146 error = gotweb_parse_querystring(&qs, c->querystring);
148 gotweb_free_querystring(qs);
149 log_warnx("%s: %s", __func__, error->msg);
154 * certain actions require a commit id in the querystring. this stops
155 * bad actors from exploiting this by manually manipulating the
159 if (qs->commit == NULL && (qs->action == BLAME || qs->action == BLOB ||
160 qs->action == DIFF)) {
161 error2 = got_error(GOT_ERR_QUERYSTRING);
165 if (qs->action != INDEX) {
166 error = gotweb_init_repo_dir(&repo_dir, qs->path);
169 error = gotweb_load_got_path(c, repo_dir);
170 c->t->repo_dir = repo_dir;
171 if (error && error->code != GOT_ERR_LONELY_PACKIDX)
175 /* render top of page */
176 if (qs != NULL && qs->action == BLOB) {
177 error = got_get_repo_commits(c, 1);
180 error = gotweb_render_content_type(c, "text/plain");
182 log_warnx("%s: %s", __func__, error->msg);
185 error = got_output_file_blob(c);
187 log_warnx("%s: %s", __func__, error->msg);
193 error = gotweb_render_content_type(c, "text/html");
195 log_warnx("%s: %s", __func__, error->msg);
201 error = gotweb_render_header(c);
203 log_warnx("%s: %s", __func__, error->msg);
214 error = gotweb_render_blame(c);
216 log_warnx("%s: %s", __func__, error->msg);
221 error = gotweb_render_briefs(c);
223 log_warnx("%s: %s", __func__, error->msg);
228 error = gotweb_render_commits(c);
230 log_warnx("%s: %s", __func__, error->msg);
235 error = gotweb_render_diff(c);
237 log_warnx("%s: %s", __func__, error->msg);
242 error = gotweb_render_index(c);
244 log_warnx("%s: %s", __func__, error->msg);
249 error = gotweb_render_summary(c);
251 log_warnx("%s: %s", __func__, error->msg);
256 error = gotweb_render_tag(c);
258 log_warnx("%s: %s", __func__, error->msg);
263 error = gotweb_render_tags(c);
265 log_warnx("%s: %s", __func__, error->msg);
270 error = gotweb_render_tree(c);
272 log_warnx("%s: %s", __func__, error->msg);
278 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
280 if (fcgi_gen_response(c, "Error: Bad Querystring\n") == -1)
282 if (fcgi_gen_response(c, "</div>\n") == -1)
289 if (html && fcgi_gen_response(c, "<div id='err_content'>") == -1)
291 if (fcgi_gen_response(c, err) == -1)
294 if (fcgi_gen_response(c, (uint8_t *)error->msg) == -1)
297 if (fcgi_gen_response(c, "see daemon logs for details") == -1)
300 if (html && fcgi_gen_response(c, "</div>\n") == -1)
303 if (c->t->repo != NULL && qs->action != INDEX)
304 got_repo_close(c->t->repo);
305 if (html && srv != NULL)
306 gotweb_render_footer(c);
310 gotweb_get_server(uint8_t *server_name, uint8_t *document_root,
313 struct server *srv = NULL;
315 /* check against document_root first */
316 if (strlen(server_name) > 0)
317 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
318 if (strcmp(srv->name, server_name) == 0)
321 /* check against document_root second */
322 if (strlen(document_root) > 0)
323 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
324 if (strcmp(srv->name, document_root) == 0)
327 /* check against subdomain third */
328 if (strlen(subdomain) > 0)
329 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
330 if (strcmp(srv->name, subdomain) == 0)
333 /* if those fail, send first server */
334 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
341 const struct got_error *
342 gotweb_init_transport(struct transport **t)
344 const struct got_error *error = NULL;
346 *t = calloc(1, sizeof(**t));
348 return got_error_from_errno2("%s: calloc", __func__);
350 TAILQ_INIT(&(*t)->repo_commits);
351 TAILQ_INIT(&(*t)->repo_tags);
354 (*t)->repo_dir = NULL;
356 (*t)->next_id = NULL;
357 (*t)->prev_id = NULL;
364 static const struct got_error *
365 gotweb_init_querystring(struct querystring **qs)
367 const struct got_error *error = NULL;
369 *qs = calloc(1, sizeof(**qs));
371 return got_error_from_errno2("%s: calloc", __func__);
373 (*qs)->action = INDEX;
374 (*qs)->commit = NULL;
376 (*qs)->folder = NULL;
377 (*qs)->headref = strdup("HEAD");
378 if ((*qs)->headref == NULL) {
379 return got_error_from_errno2("%s: strdup", __func__);
381 (*qs)->index_page = 0;
382 (*qs)->index_page_str = NULL;
388 static const struct got_error *
389 gotweb_parse_querystring(struct querystring **qs, char *qst)
391 const struct got_error *error = NULL;
392 char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL;
393 char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL;
400 return got_error_from_errno2("%s: strdup", __func__);
405 while (tok1_pair != NULL) {
406 strsep(&tok1_end, "&");
408 tok2 = strdup(tok1_pair);
411 return got_error_from_errno2("%s: strdup", __func__);
417 while (tok2_pair != NULL) {
418 strsep(&tok2_end, "=");
420 error = gotweb_assign_querystring(qs, tok2_pair,
425 tok2_pair = tok2_end;
428 tok1_pair = tok1_end;
438 static const struct got_error *
439 gotweb_assign_querystring(struct querystring **qs, char *key, char *value)
441 const struct got_error *error = NULL;
445 for (el_cnt = 0; el_cnt < QSELEM__MAX; el_cnt++) {
446 if (strcmp(key, querystring_keys[el_cnt].name) != 0)
449 switch (querystring_keys[el_cnt].element) {
451 for (a_cnt = 0; a_cnt < ACTIONS__MAX; a_cnt++) {
452 if (strcmp(value, action_keys[a_cnt].name) != 0)
454 else if (strcmp(value,
455 action_keys[a_cnt].name) == 0){
457 action_keys[a_cnt].action;
465 (*qs)->commit = strdup(value);
466 if ((*qs)->commit == NULL) {
467 error = got_error_from_errno2("%s: strdup",
473 (*qs)->file = strdup(value);
474 if ((*qs)->file == NULL) {
475 error = got_error_from_errno2("%s: strdup",
481 (*qs)->folder = strdup(value);
482 if ((*qs)->folder == NULL) {
483 error = got_error_from_errno2("%s: strdup",
489 (*qs)->headref = strdup(value);
490 if ((*qs)->headref == NULL) {
491 error = got_error_from_errno2("%s: strdup",
497 if (strlen(value) == 0)
499 (*qs)->index_page_str = strdup(value);
500 if ((*qs)->index_page_str == NULL) {
501 error = got_error_from_errno2("%s: strdup",
505 (*qs)->index_page = strtonum(value, INT64_MIN,
508 error = got_error_from_errno3("%s: strtonum %s",
512 if ((*qs)->index_page < 0) {
513 (*qs)->index_page = 0;
514 sprintf((*qs)->index_page_str, "%d", 0);
518 (*qs)->path = strdup(value);
519 if ((*qs)->path == NULL) {
520 error = got_error_from_errno2("%s: strdup",
526 if (strlen(value) == 0)
528 (*qs)->page_str = strdup(value);
529 if ((*qs)->page_str == NULL) {
530 error = got_error_from_errno2("%s: strdup",
534 (*qs)->page = strtonum(value, INT64_MIN,
537 error = got_error_from_errno3("%s: strtonum %s",
541 if ((*qs)->page < 0) {
543 sprintf((*qs)->page_str, "%d", 0);
555 gotweb_free_repo_tag(struct repo_tag *rt)
558 free(rt->commit_msg);
566 gotweb_free_repo_commit(struct repo_commit *rc)
576 free(rc->commit_msg);
582 gotweb_free_querystring(struct querystring *qs)
589 free(qs->index_page_str);
597 gotweb_free_repo_dir(struct repo_dir *repo_dir)
599 if (repo_dir != NULL) {
600 free(repo_dir->name);
601 free(repo_dir->owner);
602 free(repo_dir->description);
605 free(repo_dir->path);
611 gotweb_free_transport(struct transport *t)
613 struct repo_commit *rc = NULL, *trc = NULL;
614 struct repo_tag *rt = NULL, *trt = NULL;
616 TAILQ_FOREACH_SAFE(rc, &t->repo_commits, entry, trc) {
617 TAILQ_REMOVE(&t->repo_commits, rc, entry);
618 gotweb_free_repo_commit(rc);
620 TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
621 TAILQ_REMOVE(&t->repo_tags, rt, entry);
622 gotweb_free_repo_tag(rt);
624 gotweb_free_repo_dir(t->repo_dir);
625 gotweb_free_querystring(t->qs);
633 const struct got_error *
634 gotweb_render_content_type(struct request *c, const uint8_t *type)
636 const struct got_error *error = NULL;
639 if (asprintf(&h, "Content-type: %s\r\n\r\n", type) == -1) {
640 error = got_error_from_errno2("%s: asprintf", __func__);
644 fcgi_gen_response(c, h);
651 const struct got_error *
652 gotweb_render_content_type_file(struct request *c, const uint8_t *type,
655 const struct got_error *error = NULL;
658 if (asprintf(&h, "Content-type: %s\r\n"
659 "Content-disposition: attachment; filename=%s\r\n\r\n",
661 error = got_error_from_errno2("%s: asprintf", __func__);
665 fcgi_gen_response(c, h);
672 static const struct got_error *
673 gotweb_render_header(struct request *c)
675 const struct got_error *error = NULL;
676 struct server *srv = c->srv;
677 struct querystring *qs = c->t->qs;
678 char *title = NULL, *droot = NULL, *css = NULL, *gotlink = NULL;
679 char *gotimg = NULL, *sitelink = NULL, *summlink = NULL;
681 if (strlen(c->document_root) > 0) {
682 if (asprintf(&droot, "/%s/", c->document_root) == -1) {
683 error = got_error_from_errno2("%s: asprintf", __func__);
687 if (asprintf(&droot, "/") == -1) {
688 error = got_error_from_errno2("%s: asprintf", __func__);
693 if (asprintf(&title, "<title>%s</title>\n", srv->site_name) == -1) {
694 error = got_error_from_errno2("%s: asprintf", __func__);
698 "<link rel='stylesheet' type='text/css' href='%s%s'/>\n",
699 droot, srv->custom_css) == -1) {
700 error = got_error_from_errno2("%s: asprintf", __func__);
703 if (asprintf(&gotlink, "<a href='%s' target='_sotd'>",
704 srv->logo_url) == -1) {
705 error = got_error_from_errno2("%s: asprintf", __func__);
708 if (asprintf(&gotimg, "<img src='%s%s' alt='logo' id='logo'/></a>",
709 droot, srv->logo) == -1) {
710 error = got_error_from_errno2("%s: asprintf", __func__);
713 if (asprintf(&sitelink, "<a href='/%s?index_page=%d' "
714 "alt='sitelink'>%s</a>", c->document_root, qs->index_page,
715 srv->site_link) == -1) {
716 error = got_error_from_errno2("%s: asprintf", __func__);
719 if (asprintf(&summlink, "<a href='/%s?index_page=%d&path=%s"
720 "&action=summary' alt='summlink'>%s</a>", c->document_root,
721 qs->index_page, qs->path, qs->path) == -1) {
722 error = got_error_from_errno2("%s: asprintf", __func__);
726 if (fcgi_gen_response(c, "<!DOCTYPE html>\n<head>\n") == -1)
728 if (fcgi_gen_response(c, title) == -1)
730 if (fcgi_gen_response(c, "<meta name='viewport' "
731 "content='initial-scale=.75, user-scalable=yes'/>\n") == -1)
733 if (fcgi_gen_response(c, "<meta charset='utf-8'/>\n") == -1)
735 if (fcgi_gen_response(c, "<meta name='msapplication-TileColor' "
736 "content='#da532c'/>\n") == -1)
738 if (fcgi_gen_response(c,
739 "<meta name='theme-color' content='#ffffff'/>\n") == -1)
741 if (fcgi_gen_response(c, "<link rel='apple-touch-icon' sizes='180x180' "
742 "href='/apple-touch-icon.png'/>\n") == -1)
744 if (fcgi_gen_response(c,
745 "<link rel='icon' type='image/png' sizes='32x32' "
746 "href='/favicon-32x32.png'/>\n") == -1)
748 if (fcgi_gen_response(c, "<link rel='icon' type='image/png' "
749 "sizes='16x16' href='/favicon-16x16.png'/>\n") == -1)
751 if (fcgi_gen_response(c, "<link rel='manifest' "
752 "href='/site.webmanifest'/>\n") == -1)
754 if (fcgi_gen_response(c, "<link rel='mask-icon' "
755 "href='/safari-pinned-tab.svg'/>\n") == -1)
757 if (fcgi_gen_response(c, css) == -1)
759 if (fcgi_gen_response(c, "</head>\n<body>\n<div id='gw_body'>\n") == -1)
761 if (fcgi_gen_response(c,
762 "<div id='header'>\n<div id='got_link'>") == -1)
764 if (fcgi_gen_response(c, gotlink) == -1)
766 if (fcgi_gen_response(c, gotimg) == -1)
768 if (fcgi_gen_response(c, "</div>\n</div>\n") == -1)
770 if (fcgi_gen_response(c,
771 "<div id='site_path'>\n<div id='site_link'>") == -1)
773 if (fcgi_gen_response(c, sitelink) == -1)
776 if (qs->path != NULL) {
777 if (fcgi_gen_response(c, " / ") == -1)
779 if (fcgi_gen_response(c, summlink) == -1)
782 if (qs->action != INDEX) {
783 if (fcgi_gen_response(c, " / ") == -1)
787 if (fcgi_gen_response(c, "blame") == -1)
791 if (fcgi_gen_response(c, "briefs") == -1)
795 if (fcgi_gen_response(c, "commits") == -1)
799 if (fcgi_gen_response(c, "diff") == -1)
803 if (fcgi_gen_response(c, "summary") == -1)
807 if (fcgi_gen_response(c, "tag") == -1)
811 if (fcgi_gen_response(c, "tags") == -1)
815 if (fcgi_gen_response(c, "tree") == -1)
824 fcgi_gen_response(c, "</div>\n</div>\n<div id='content'>\n");
837 static const struct got_error *
838 gotweb_render_footer(struct request *c)
840 const struct got_error *error = NULL;
841 struct server *srv = c->srv;
842 char *siteowner = NULL;
844 if (fcgi_gen_response(c, "<div id='site_owner_wrapper'>\n") == -1)
846 if (fcgi_gen_response(c, "<div id='site_owner'>") == -1)
848 if (srv->show_site_owner) {
849 error = gotweb_escape_html(&siteowner, srv->site_owner);
852 if (fcgi_gen_response(c, siteowner) == -1)
855 if (fcgi_gen_response(c, " ") == -1)
857 fcgi_gen_response(c, "</div>\n</div>\n</div>\n</body>\n</html>");
864 static const struct got_error *
865 gotweb_render_navs(struct request *c)
867 const struct got_error *error = NULL;
868 struct transport *t = c->t;
869 struct querystring *qs = t->qs;
870 struct server *srv = c->srv;
871 char *nhref = NULL, *phref = NULL;
874 if (fcgi_gen_response(c, "<div id='np_wrapper'>\n") == -1)
876 if (fcgi_gen_response(c, "<div id='nav_prev'>") == -1)
881 if (qs->index_page > 0) {
882 if (asprintf(&phref, "index_page=%d",
883 qs->index_page - 1) == -1) {
884 error = got_error_from_errno2("%s: asprintf",
892 if (t->prev_id && qs->commit != NULL &&
893 strcmp(qs->commit, t->prev_id) != 0) {
894 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
895 "&action=briefs&commit=%s&headref=%s",
896 qs->index_page, qs->path, qs->page - 1, t->prev_id,
897 qs->headref) == -1) {
898 error = got_error_from_errno2("%s: asprintf",
906 if (t->prev_id && qs->commit != NULL &&
907 strcmp(qs->commit, t->prev_id) != 0) {
908 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
909 "&action=commits&commit=%s&headref=%s&folder=%s"
911 qs->index_page, qs->path, qs->page - 1, t->prev_id,
912 qs->headref, qs->folder ? qs->folder : "",
913 qs->file ? qs->file : "") == -1) {
914 error = got_error_from_errno2("%s: asprintf",
922 if (t->prev_id && qs->commit != NULL &&
923 strcmp(qs->commit, t->prev_id) != 0) {
924 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
925 "&action=tags&commit=%s&headref=%s",
926 qs->index_page, qs->path, qs->page - 1, t->prev_id,
927 qs->headref) == -1) {
928 error = got_error_from_errno2("%s: asprintf",
941 if (fcgi_gen_response(c, "<a href='?") == -1)
943 if (fcgi_gen_response(c, phref) == -1)
945 if (fcgi_gen_response(c, "'>Previous</a>") == -1)
948 if (fcgi_gen_response(c, "</div>\n") == -1)
950 if (fcgi_gen_response(c, "<div id='nav_next'>") == -1)
957 if (t->next_disp == srv->max_repos_display &&
958 t->repos_total != (qs->index_page + 1) *
959 srv->max_repos_display) {
960 if (asprintf(&nhref, "index_page=%d",
961 qs->index_page + 1) == -1) {
962 error = got_error_from_errno2("%s: asprintf",
971 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
972 "&action=briefs&commit=%s&headref=%s",
973 qs->index_page, qs->path, qs->page + 1, t->next_id,
974 qs->headref) == -1) {
975 error = got_error_from_errno2("%s: asprintf",
984 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
985 "&action=commits&commit=%s&headref=%s&folder=%s"
987 qs->index_page, qs->path, qs->page + 1, t->next_id,
988 qs->headref, qs->folder ? qs->folder : "",
989 qs->file ? qs->file : "") == -1) {
990 error = got_error_from_errno2("%s: asprintf",
999 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
1000 "&action=tags&commit=%s&headref=%s",
1001 qs->index_page, qs->path, qs->page + 1, t->next_id,
1002 qs->headref) == -1) {
1003 error = got_error_from_errno2("%s: asprintf",
1015 if (fcgi_gen_response(c, "<a href='?") == -1)
1017 if (fcgi_gen_response(c, nhref) == -1)
1019 if (fcgi_gen_response(c, "'>Next</a>") == -1)
1022 fcgi_gen_response(c, "</div>\n");
1033 static const struct got_error *
1034 gotweb_render_index(struct request *c)
1036 const struct got_error *error = NULL;
1037 struct server *srv = c->srv;
1038 struct transport *t = c->t;
1039 struct querystring *qs = t->qs;
1040 struct repo_dir *repo_dir = NULL;
1042 struct dirent **sd_dent;
1043 char *c_path = NULL;
1045 unsigned int d_cnt, d_i, d_disp = 0;
1047 d = opendir(srv->repos_path);
1049 error = got_error_from_errno2("opendir", srv->repos_path);
1053 d_cnt = scandir(srv->repos_path, &sd_dent, NULL, alphasort);
1055 error = got_error_from_errno2("scandir", srv->repos_path);
1059 /* get total count of repos */
1060 for (d_i = 0; d_i < d_cnt; d_i++) {
1061 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1062 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1065 if (asprintf(&c_path, "%s/%s", srv->repos_path,
1066 sd_dent[d_i]->d_name) == -1) {
1067 error = got_error_from_errno("asprintf");
1071 if (lstat(c_path, &st) == 0 && S_ISDIR(st.st_mode) &&
1072 !got_path_dir_is_empty(c_path))
1078 if (fcgi_gen_response(c, "<div id='index_header'>\n") == -1)
1080 if (fcgi_gen_response(c,
1081 "<div id='index_header_project'>Project</div>\n") == -1)
1083 if (srv->show_repo_description)
1084 if (fcgi_gen_response(c, "<div id='index_header_description'>"
1085 "Description</div>\n") == -1)
1087 if (srv->show_repo_owner)
1088 if (fcgi_gen_response(c, "<div id='index_header_owner'>"
1089 "Owner</div>\n") == -1)
1091 if (srv->show_repo_age)
1092 if (fcgi_gen_response(c, "<div id='index_header_age'>"
1093 "Last Change</div>\n") == -1)
1095 if (fcgi_gen_response(c, "</div>\n") == -1)
1098 for (d_i = 0; d_i < d_cnt; d_i++) {
1099 if (srv->max_repos > 0 && (d_i - 2) == srv->max_repos)
1100 break; /* account for parent and self */
1102 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1103 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1106 if (qs->index_page > 0 && (qs->index_page *
1107 srv->max_repos_display) > t->prev_disp) {
1112 error = gotweb_init_repo_dir(&repo_dir, sd_dent[d_i]->d_name);
1116 error = gotweb_load_got_path(c, repo_dir);
1117 if (error && error->code == GOT_ERR_NOT_GIT_REPO) {
1121 else if (error && error->code != GOT_ERR_LONELY_PACKIDX)
1124 if (lstat(repo_dir->path, &st) == 0 &&
1125 S_ISDIR(st.st_mode) &&
1126 !got_path_dir_is_empty(repo_dir->path))
1129 gotweb_free_repo_dir(repo_dir);
1136 if (fcgi_gen_response(c, "<div id='index_wrapper'>\n") == -1)
1138 if (fcgi_gen_response(c, "<div id='index_project'>") == -1)
1141 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1143 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1145 if (fcgi_gen_response(c, "&path=") == -1)
1147 if (fcgi_gen_response(c, repo_dir->name) == -1)
1149 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1151 if (fcgi_gen_response(c, repo_dir->name) == -1)
1153 if (fcgi_gen_response(c, "</a>") == -1)
1156 if (fcgi_gen_response(c, "</div>\n") == -1)
1159 if (srv->show_repo_description) {
1160 if (fcgi_gen_response(c,
1161 "<div id='index_project_description'>\n") == -1)
1163 if (fcgi_gen_response(c, repo_dir->description) == -1)
1165 if (fcgi_gen_response(c, "</div>\n") == -1)
1169 if (srv->show_repo_owner) {
1170 if (fcgi_gen_response(c,
1171 "<div id='index_project_owner'>") == -1)
1173 if (fcgi_gen_response(c, repo_dir->owner) == -1)
1175 if (fcgi_gen_response(c, "</div>\n") == -1)
1179 if (srv->show_repo_age) {
1180 if (fcgi_gen_response(c,
1181 "<div id='index_project_age'>") == -1)
1183 if (fcgi_gen_response(c, repo_dir->age) == -1)
1185 if (fcgi_gen_response(c, "</div>\n") == -1)
1189 if (fcgi_gen_response(c, "<div id='navs_wrapper'>") == -1)
1191 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1194 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1196 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1198 if (fcgi_gen_response(c, "&path=") == -1)
1200 if (fcgi_gen_response(c, repo_dir->name) == -1)
1202 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1204 if (fcgi_gen_response(c, "summary") == -1)
1206 if (fcgi_gen_response(c, "</a> | ") == -1)
1209 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1211 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1213 if (fcgi_gen_response(c, "&path=") == -1)
1215 if (fcgi_gen_response(c, repo_dir->name) == -1)
1217 if (fcgi_gen_response(c, "&action=briefs'>") == -1)
1219 if (fcgi_gen_response(c, "commit briefs") == -1)
1221 if (fcgi_gen_response(c, "</a> | ") == -1)
1224 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1226 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1228 if (fcgi_gen_response(c, "&path=") == -1)
1230 if (fcgi_gen_response(c, repo_dir->name) == -1)
1232 if (fcgi_gen_response(c, "&action=commits'>") == -1)
1234 if (fcgi_gen_response(c, "commits") == -1)
1236 if (fcgi_gen_response(c, "</a> | ") == -1)
1239 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1241 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1243 if (fcgi_gen_response(c, "&path=") == -1)
1245 if (fcgi_gen_response(c, repo_dir->name) == -1)
1247 if (fcgi_gen_response(c, "&action=tags'>") == -1)
1249 if (fcgi_gen_response(c, "tags") == -1)
1251 if (fcgi_gen_response(c, "</a> | ") == -1)
1254 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1256 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1258 if (fcgi_gen_response(c, "&path=") == -1)
1260 if (fcgi_gen_response(c, repo_dir->name) == -1)
1262 if (fcgi_gen_response(c, "&action=tree'>") == -1)
1264 if (fcgi_gen_response(c, "tree") == -1)
1266 if (fcgi_gen_response(c, "</a>") == -1)
1269 if (fcgi_gen_response(c, "</div>") == -1)
1271 if (fcgi_gen_response(c,
1272 "<div id='dotted_line'></div>\n") == -1)
1274 if (fcgi_gen_response(c, "</div>\n") == -1)
1276 if (fcgi_gen_response(c, "</div>\n") == -1)
1279 gotweb_free_repo_dir(repo_dir);
1281 error = got_repo_close(t->repo);
1285 if (d_disp == srv->max_repos_display)
1288 if (srv->max_repos_display == 0)
1290 if (srv->max_repos > 0 && srv->max_repos < srv->max_repos_display)
1292 if (t->repos_total <= srv->max_repos ||
1293 t->repos_total <= srv->max_repos_display)
1296 error = gotweb_render_navs(c);
1300 fcgi_gen_response(c, "</div>\n");
1302 if (d != NULL && closedir(d) == EOF && error == NULL)
1303 error = got_error_from_errno("closedir");
1307 static const struct got_error *
1308 gotweb_render_blame(struct request *c)
1310 const struct got_error *error = NULL;
1311 struct transport *t = c->t;
1312 struct repo_commit *rc = NULL;
1315 error = got_get_repo_commits(c, 1);
1319 rc = TAILQ_FIRST(&t->repo_commits);
1321 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1325 if (fcgi_gen_response(c, "<div id='blame_title_wrapper'>\n") == -1)
1327 if (fcgi_gen_response(c, "<div id='blame_title'>Blame</div>\n") == -1)
1329 if (fcgi_gen_response(c, "</div>\n") == -1)
1332 if (fcgi_gen_response(c, "<div id='blame_content'>\n") == -1)
1335 if (fcgi_gen_response(c, "<div id='blame_header_wrapper'>\n") == -1)
1337 if (fcgi_gen_response(c, "<div id='blame_header'>\n") == -1)
1340 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1343 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1345 if (fcgi_gen_response(c, age ? age : "") == -1)
1347 if (fcgi_gen_response(c, "</div>\n") == -1)
1350 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1353 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1355 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1357 if (fcgi_gen_response(c, "</div>\n") == -1)
1360 if (fcgi_gen_response(c, "</div>\n") == -1)
1362 if (fcgi_gen_response(c, "</div>\n") == -1)
1365 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1367 if (fcgi_gen_response(c, "<div id='blame'>\n") == -1)
1370 error = got_output_file_blame(c);
1374 fcgi_gen_response(c, "</div>\n");
1376 fcgi_gen_response(c, "</div>\n");
1380 static const struct got_error *
1381 gotweb_render_briefs(struct request *c)
1383 const struct got_error *error = NULL;
1384 struct repo_commit *rc = NULL;
1385 struct server *srv = c->srv;
1386 struct transport *t = c->t;
1387 struct querystring *qs = t->qs;
1388 struct repo_dir *repo_dir = t->repo_dir;
1389 char *smallerthan, *newline;
1392 if (fcgi_gen_response(c, "<div id='briefs_title_wrapper'>\n") == -1)
1394 if (fcgi_gen_response(c,
1395 "<div id='briefs_title'>Commit Briefs</div>\n") == -1)
1397 if (fcgi_gen_response(c, "</div>\n") == -1)
1400 if (fcgi_gen_response(c, "<div id='briefs_content'>\n") == -1)
1403 if (qs->action == SUMMARY) {
1404 qs->action = BRIEFS;
1405 error = got_get_repo_commits(c, D_MAXSLCOMMDISP);
1407 error = got_get_repo_commits(c, srv->max_commits_display);
1411 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1412 error = gotweb_get_time_str(&age, rc->committer_time, TM_DIFF);
1415 if (fcgi_gen_response(c, "<div id='briefs_age'>") == -1)
1417 if (fcgi_gen_response(c, age ? age : "") == -1)
1419 if (fcgi_gen_response(c, "</div>\n") == -1)
1422 if (fcgi_gen_response(c, "<div id='briefs_author'>") == -1)
1424 smallerthan = strchr(rc->author, '<');
1426 *smallerthan = '\0';
1427 if (fcgi_gen_response(c, rc->author) == -1)
1429 if (fcgi_gen_response(c, "</div>\n") == -1)
1432 if (fcgi_gen_response(c, "<div id='briefs_log'>") == -1)
1434 newline = strchr(rc->commit_msg, '\n');
1438 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1440 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1442 if (fcgi_gen_response(c, "&path=") == -1)
1444 if (fcgi_gen_response(c, repo_dir->name) == -1)
1446 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1448 if (fcgi_gen_response(c, rc->commit_id) == -1)
1450 if (fcgi_gen_response(c, "&headref=") == -1)
1452 if (fcgi_gen_response(c, qs->headref) == -1)
1454 if (fcgi_gen_response(c, "'>") == -1)
1456 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1458 if (fcgi_gen_response(c, "</a>") == -1)
1461 if (fcgi_gen_response(c,
1462 " <span id='refs_str'>(") == -1)
1464 if (fcgi_gen_response(c, rc->refs_str) == -1)
1466 if (fcgi_gen_response(c, ")</span>") == -1)
1469 if (fcgi_gen_response(c, "</div>\n") == -1)
1472 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1474 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1476 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1478 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1480 if (fcgi_gen_response(c, "&path=") == -1)
1482 if (fcgi_gen_response(c, repo_dir->name) == -1)
1484 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1486 if (fcgi_gen_response(c, rc->commit_id) == -1)
1488 if (fcgi_gen_response(c, "&headref=") == -1)
1490 if (fcgi_gen_response(c, qs->headref) == -1)
1492 if (fcgi_gen_response(c, "'>") == -1)
1494 if (fcgi_gen_response(c, "diff") == -1)
1496 if (fcgi_gen_response(c, "</a>") == -1)
1499 if (fcgi_gen_response(c, " | ") == -1)
1502 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1504 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1506 if (fcgi_gen_response(c, "&path=") == -1)
1508 if (fcgi_gen_response(c, repo_dir->name) == -1)
1510 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1512 if (fcgi_gen_response(c, rc->commit_id) == -1)
1514 if (fcgi_gen_response(c, "&headref=") == -1)
1516 if (fcgi_gen_response(c, qs->headref) == -1)
1518 if (fcgi_gen_response(c, "'>") == -1)
1520 if (fcgi_gen_response(c, "tree") == -1)
1522 if (fcgi_gen_response(c, "</a>") == -1)
1524 if (fcgi_gen_response(c, "</div>\n") == -1)
1526 if (fcgi_gen_response(c, "</div>\n") == -1)
1528 if (fcgi_gen_response(c,
1529 "<div id='dotted_line'></div>\n") == -1)
1536 if (t->next_id || t->prev_id) {
1537 error = gotweb_render_navs(c);
1541 fcgi_gen_response(c, "</div>\n");
1547 static const struct got_error *
1548 gotweb_render_commits(struct request *c)
1550 const struct got_error *error = NULL;
1551 struct repo_commit *rc = NULL;
1552 struct server *srv = c->srv;
1553 struct transport *t = c->t;
1554 struct querystring *qs = t->qs;
1555 struct repo_dir *repo_dir = t->repo_dir;
1556 char *age = NULL, *author = NULL;
1557 /* int commit_found = 0; */
1559 if (fcgi_gen_response(c, "<div id='commits_title_wrapper'>\n") == -1)
1561 if (fcgi_gen_response(c,
1562 "<div id='commits_title'>Commits</div>\n") == -1)
1564 if (fcgi_gen_response(c, "</div>\n") == -1)
1567 if (fcgi_gen_response(c, "<div id='commits_content'>\n") == -1)
1570 error = got_get_repo_commits(c, srv->max_commits_display);
1574 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1575 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1578 error = gotweb_escape_html(&author, rc->author);
1582 if (fcgi_gen_response(c,
1583 "<div id='commits_header_wrapper'>\n") == -1)
1585 if (fcgi_gen_response(c, "<div id='commits_header'>\n") == -1)
1589 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
1592 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
1594 if (fcgi_gen_response(c, rc->commit_id) == -1)
1596 if (fcgi_gen_response(c, "</div>\n") == -1)
1599 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
1602 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
1604 if (fcgi_gen_response(c, author ? author : "") == -1)
1606 if (fcgi_gen_response(c, "</div>\n") == -1)
1609 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1612 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1614 if (fcgi_gen_response(c, age ? age : "") == -1)
1616 if (fcgi_gen_response(c, "</div>\n") == -1)
1619 if (fcgi_gen_response(c, "</div>\n") == -1)
1621 if (fcgi_gen_response(c, "</div>\n") == -1)
1624 if (fcgi_gen_response(c,
1625 "<div id='dotted_line'></div>\n") == -1)
1627 if (fcgi_gen_response(c, "<div id='commit'>\n") == -1)
1630 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1633 if (fcgi_gen_response(c, "</div>\n") == -1)
1635 if (fcgi_gen_response(c, "</div>\n") == -1)
1638 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1640 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1642 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1644 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1646 if (fcgi_gen_response(c, "&path=") == -1)
1648 if (fcgi_gen_response(c, repo_dir->name) == -1)
1650 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1652 if (fcgi_gen_response(c, rc->commit_id) == -1)
1654 if (fcgi_gen_response(c, "'>") == -1)
1656 if (fcgi_gen_response(c, "diff") == -1)
1658 if (fcgi_gen_response(c, "</a>") == -1)
1661 if (fcgi_gen_response(c, " | ") == -1)
1664 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1666 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1668 if (fcgi_gen_response(c, "&path=") == -1)
1670 if (fcgi_gen_response(c, repo_dir->name) == -1)
1672 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1674 if (fcgi_gen_response(c, rc->commit_id) == -1)
1676 if (fcgi_gen_response(c, "'>") == -1)
1678 if (fcgi_gen_response(c, "tree") == -1)
1680 if (fcgi_gen_response(c, "</a>") == -1)
1682 if (fcgi_gen_response(c, "</div>\n") == -1)
1684 if (fcgi_gen_response(c, "</div>\n") == -1)
1686 if (fcgi_gen_response(c,
1687 "<div id='dotted_line'></div>\n") == -1)
1695 if (t->next_id || t->prev_id) {
1696 error = gotweb_render_navs(c);
1700 if (fcgi_gen_response(c, "</div>\n") == -1)
1702 fcgi_gen_response(c, "</div>\n");
1708 static const struct got_error *
1709 gotweb_render_branches(struct request *c)
1711 const struct got_error *error = NULL;
1712 struct got_reflist_head refs;
1713 struct got_reflist_entry *re;
1714 struct transport *t = c->t;
1715 struct querystring *qs = t->qs;
1716 struct got_repository *repo = t->repo;
1721 error = got_ref_list(&refs, repo, "refs/heads",
1722 got_ref_cmp_by_name, NULL);
1726 if (fcgi_gen_response(c, "<div id='branches_title_wrapper'>\n") == -1)
1728 if (fcgi_gen_response(c,
1729 "<div id='branches_title'>Branches</div>\n") == -1)
1731 if (fcgi_gen_response(c, "</div>\n") == -1)
1734 if (fcgi_gen_response(c, "<div id='branches_content'>\n") == -1)
1737 TAILQ_FOREACH(re, &refs, entry) {
1738 char *refname = NULL;
1740 if (got_ref_is_symbolic(re->ref))
1743 refname = strdup(got_ref_get_name(re->ref));
1744 if (refname == NULL) {
1745 error = got_error_from_errno("strdup");
1748 if (strncmp(refname, "refs/heads/", 11) != 0)
1751 error = got_get_repo_age(&age, c, qs->path, refname,
1756 if (strncmp(refname, "refs/heads/", 11) == 0)
1759 if (fcgi_gen_response(c, "<div id='branches_wrapper'>") == -1)
1762 if (fcgi_gen_response(c, "<div id='branches_age'>") == -1)
1764 if (fcgi_gen_response(c, age ? age : "") == -1)
1766 if (fcgi_gen_response(c, "</div>\n") == -1)
1769 if (fcgi_gen_response(c, "<div id='branches_space'>") == -1)
1771 if (fcgi_gen_response(c, " ") == -1)
1773 if (fcgi_gen_response(c, "</div>\n") == -1)
1776 if (fcgi_gen_response(c, "<div id='branch'>") == -1)
1778 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1780 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1782 if (fcgi_gen_response(c, "&path=") == -1)
1784 if (fcgi_gen_response(c, qs->path) == -1)
1786 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1788 if (fcgi_gen_response(c, refname) == -1)
1790 if (fcgi_gen_response(c, "'>") == -1)
1792 if (fcgi_gen_response(c, refname) == -1)
1794 if (fcgi_gen_response(c, "</a>") == -1)
1796 if (fcgi_gen_response(c, "</div>\n") == -1)
1799 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1801 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1804 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1806 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1808 if (fcgi_gen_response(c, "&path=") == -1)
1810 if (fcgi_gen_response(c, qs->path) == -1)
1812 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1814 if (fcgi_gen_response(c, refname) == -1)
1816 if (fcgi_gen_response(c, "'>") == -1)
1818 if (fcgi_gen_response(c, "summary") == -1)
1820 if (fcgi_gen_response(c, "</a>") == -1)
1823 if (fcgi_gen_response(c, " | ") == -1)
1826 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1828 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1830 if (fcgi_gen_response(c, "&path=") == -1)
1832 if (fcgi_gen_response(c, qs->path) == -1)
1834 if (fcgi_gen_response(c, "&action=briefs&headref=") == -1)
1836 if (fcgi_gen_response(c, refname) == -1)
1838 if (fcgi_gen_response(c, "'>") == -1)
1840 if (fcgi_gen_response(c, "commit briefs") == -1)
1842 if (fcgi_gen_response(c, "</a>") == -1)
1845 if (fcgi_gen_response(c, " | ") == -1)
1848 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1850 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1852 if (fcgi_gen_response(c, "&path=") == -1)
1854 if (fcgi_gen_response(c, qs->path) == -1)
1856 if (fcgi_gen_response(c, "&action=commits&headref=") == -1)
1858 if (fcgi_gen_response(c, refname) == -1)
1860 if (fcgi_gen_response(c, "'>") == -1)
1862 if (fcgi_gen_response(c, "commits") == -1)
1864 if (fcgi_gen_response(c, "</a>") == -1)
1867 if (fcgi_gen_response(c, "</div>\n") == -1)
1869 if (fcgi_gen_response(c, "</div>\n") == -1)
1872 if (fcgi_gen_response(c,
1873 "<div id='dotted_line'></div>\n") == -1)
1880 fcgi_gen_response(c, "</div>\n");
1885 static const struct got_error *
1886 gotweb_render_tree(struct request *c)
1888 const struct got_error *error = NULL;
1889 struct transport *t = c->t;
1890 struct repo_commit *rc = NULL;
1893 error = got_get_repo_commits(c, 1);
1897 rc = TAILQ_FIRST(&t->repo_commits);
1899 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1903 if (fcgi_gen_response(c, "<div id='tree_title_wrapper'>\n") == -1)
1905 if (fcgi_gen_response(c, "<div id='tree_title'>Tree</div>\n") == -1)
1907 if (fcgi_gen_response(c, "</div>\n") == -1)
1910 if (fcgi_gen_response(c, "<div id='tree_content'>\n") == -1)
1913 if (fcgi_gen_response(c, "<div id='tree_header_wrapper'>\n") == -1)
1915 if (fcgi_gen_response(c, "<div id='tree_header'>\n") == -1)
1918 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
1921 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
1923 if (fcgi_gen_response(c, rc->tree_id) == -1)
1925 if (fcgi_gen_response(c, "</div>\n") == -1)
1928 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1931 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1933 if (fcgi_gen_response(c, age ? age : "") == -1)
1935 if (fcgi_gen_response(c, "</div>\n") == -1)
1938 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1941 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1943 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1945 if (fcgi_gen_response(c, "</div>\n") == -1)
1948 if (fcgi_gen_response(c, "</div>\n") == -1)
1950 if (fcgi_gen_response(c, "</div>\n") == -1)
1953 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1955 if (fcgi_gen_response(c, "<div id='tree'>\n") == -1)
1958 error = got_output_repo_tree(c);
1962 fcgi_gen_response(c, "</div>\n");
1963 fcgi_gen_response(c, "</div>\n");
1968 static const struct got_error *
1969 gotweb_render_diff(struct request *c)
1971 const struct got_error *error = NULL;
1972 struct transport *t = c->t;
1973 struct repo_commit *rc = NULL;
1974 char *age = NULL, *author = NULL;
1976 error = got_get_repo_commits(c, 1);
1980 rc = TAILQ_FIRST(&t->repo_commits);
1982 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1985 error = gotweb_escape_html(&author, rc->author);
1989 if (fcgi_gen_response(c, "<div id='diff_title_wrapper'>\n") == -1)
1991 if (fcgi_gen_response(c,
1992 "<div id='diff_title'>Commit Diff</div>\n") == -1)
1994 if (fcgi_gen_response(c, "</div>\n") == -1)
1997 if (fcgi_gen_response(c, "<div id='diff_content'>\n") == -1)
1999 if (fcgi_gen_response(c, "<div id='diff_header_wrapper'>\n") == -1)
2001 if (fcgi_gen_response(c, "<div id='diff_header'>\n") == -1)
2004 if (fcgi_gen_response(c, "<div id='header_diff_title'>Diff:"
2007 if (fcgi_gen_response(c, "<div id='header_diff'>") == -1)
2009 if (fcgi_gen_response(c, rc->parent_id) == -1)
2011 if (fcgi_gen_response(c, "<br />") == -1)
2013 if (fcgi_gen_response(c, rc->commit_id) == -1)
2015 if (fcgi_gen_response(c, "</div>\n") == -1)
2018 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2021 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2023 if (fcgi_gen_response(c, rc->commit_id) == -1)
2025 if (fcgi_gen_response(c, "</div>\n") == -1)
2028 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
2031 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
2033 if (fcgi_gen_response(c, rc->tree_id) == -1)
2035 if (fcgi_gen_response(c, "</div>\n") == -1)
2038 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
2041 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2043 if (fcgi_gen_response(c, author ? author : "") == -1)
2045 if (fcgi_gen_response(c, "</div>\n") == -1)
2048 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2051 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2053 if (fcgi_gen_response(c, age ? age : "") == -1)
2055 if (fcgi_gen_response(c, "</div>\n") == -1)
2058 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2061 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2063 if (fcgi_gen_response(c, rc->commit_msg) == -1)
2065 if (fcgi_gen_response(c, "</div>\n") == -1)
2067 if (fcgi_gen_response(c, "</div>\n") == -1)
2069 if (fcgi_gen_response(c, "</div>\n") == -1)
2072 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2074 if (fcgi_gen_response(c, "<div id='diff'>\n") == -1)
2077 error = got_output_repo_diff(c);
2081 fcgi_gen_response(c, "</div>\n");
2083 fcgi_gen_response(c, "</div>\n");
2089 static const struct got_error *
2090 gotweb_render_summary(struct request *c)
2092 const struct got_error *error = NULL;
2093 struct transport *t = c->t;
2094 struct server *srv = c->srv;
2096 if (fcgi_gen_response(c, "<div id='summary_wrapper'>\n") == -1)
2099 if (!srv->show_repo_description)
2102 if (fcgi_gen_response(c, "<div id='description_title'>"
2103 "Description:</div>\n") == -1)
2105 if (fcgi_gen_response(c, "<div id='description'>") == -1)
2107 if (fcgi_gen_response(c, t->repo_dir->description) == -1)
2109 if (fcgi_gen_response(c, "</div>\n") == -1)
2112 if (!srv->show_repo_owner)
2115 if (fcgi_gen_response(c, "<div id='repo_owner_title'>"
2116 "Owner:</div>\n") == -1)
2118 if (fcgi_gen_response(c, "<div id='repo_owner'>") == -1)
2120 if (fcgi_gen_response(c, t->repo_dir->owner) == -1)
2122 if (fcgi_gen_response(c, "</div>\n") == -1)
2125 if (!srv->show_repo_age)
2128 if (fcgi_gen_response(c, "<div id='last_change_title'>"
2129 "Last Change:</div>\n") == -1)
2131 if (fcgi_gen_response(c, "<div id='last_change'>") == -1)
2133 if (fcgi_gen_response(c, t->repo_dir->age) == -1)
2135 if (fcgi_gen_response(c, "</div>\n") == -1)
2138 if (!srv->show_repo_cloneurl)
2141 if (fcgi_gen_response(c, "<div id='cloneurl_title'>"
2142 "Clone URL:</div>\n") == -1)
2144 if (fcgi_gen_response(c, "<div id='cloneurl'>") == -1)
2146 if (fcgi_gen_response(c, t->repo_dir->url) == -1)
2148 if (fcgi_gen_response(c, "</div>\n") == -1)
2151 if (fcgi_gen_response(c, "</div>\n") == -1)
2153 if (fcgi_gen_response(c, "</div>\n") == -1)
2156 error = gotweb_render_briefs(c);
2158 log_warnx("%s: %s", __func__, error->msg);
2162 error = gotweb_render_tags(c);
2164 log_warnx("%s: %s", __func__, error->msg);
2168 error = gotweb_render_branches(c);
2170 log_warnx("%s: %s", __func__, error->msg);
2175 static const struct got_error *
2176 gotweb_render_tag(struct request *c)
2178 const struct got_error *error = NULL;
2179 struct repo_tag *rt = NULL;
2180 struct transport *t = c->t;
2181 char *age = NULL, *author = NULL;
2183 error = got_get_repo_tags(c, 1);
2187 if (t->tag_count == 0) {
2188 error = got_error_set_errno(GOT_ERR_BAD_OBJ_ID,
2193 rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
2195 error = gotweb_get_time_str(&age, rt->tagger_time, TM_LONG);
2198 error = gotweb_escape_html(&author, rt->tagger);
2202 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2204 if (fcgi_gen_response(c, "<div id='tags_title'>Tag</div>\n") == -1)
2206 if (fcgi_gen_response(c, "</div>\n") == -1)
2209 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2211 if (fcgi_gen_response(c, "<div id='tag_header_wrapper'>\n") == -1)
2213 if (fcgi_gen_response(c, "<div id='tag_header'>\n") == -1)
2216 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2219 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2221 if (fcgi_gen_response(c, rt->commit_id) == -1)
2224 if (strncmp(rt->tag_name, "refs/", 5) == 0)
2227 if (fcgi_gen_response(c, " <span id='refs_str'>(") == -1)
2229 if (fcgi_gen_response(c, rt->tag_name) == -1)
2231 if (fcgi_gen_response(c, ")</span>") == -1)
2234 if (fcgi_gen_response(c, "</div>\n") == -1)
2237 if (fcgi_gen_response(c, "<div id='header_author_title'>Tagger:"
2240 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2242 if (fcgi_gen_response(c, author ? author : "") == -1)
2244 if (fcgi_gen_response(c, "</div>\n") == -1)
2247 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2250 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2252 if (fcgi_gen_response(c, age ? age : "") == -1)
2254 if (fcgi_gen_response(c, "</div>\n") == -1)
2257 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2260 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2262 if (fcgi_gen_response(c, rt->commit_msg) == -1)
2264 if (fcgi_gen_response(c, "</div>\n") == -1)
2266 if (fcgi_gen_response(c, "</div>\n") == -1)
2269 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2271 if (fcgi_gen_response(c, "<div id='tag_commit'>\n") == -1)
2274 if (fcgi_gen_response(c, rt->tag_commit) == -1)
2277 if (fcgi_gen_response(c, "</div>\n") == -1)
2279 fcgi_gen_response(c, "</div>\n");
2286 static const struct got_error *
2287 gotweb_render_tags(struct request *c)
2289 const struct got_error *error = NULL;
2290 struct repo_tag *rt = NULL;
2291 struct server *srv = c->srv;
2292 struct transport *t = c->t;
2293 struct querystring *qs = t->qs;
2294 struct repo_dir *repo_dir = t->repo_dir;
2297 int commit_found = 0;
2299 if (qs->action == BRIEFS) {
2301 error = got_get_repo_tags(c, D_MAXSLCOMMDISP);
2303 error = got_get_repo_tags(c, srv->max_commits_display);
2307 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2309 if (fcgi_gen_response(c,
2310 "<div id='tags_title'>Tags</div>\n") == -1)
2312 if (fcgi_gen_response(c, "</div>\n") == -1)
2315 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2318 if (t->tag_count == 0) {
2319 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
2321 if (fcgi_gen_response(c,
2322 "This repository contains no tags\n") == -1)
2324 if (fcgi_gen_response(c, "</div>\n") == -1)
2326 if (fcgi_gen_response(c, "</div>\n") == -1)
2330 TAILQ_FOREACH(rt, &t->repo_tags, entry) {
2331 if (commit_found == 0 && qs->commit != NULL) {
2332 if (strcmp(qs->commit, rt->commit_id) != 0)
2337 error = gotweb_get_time_str(&age, rt->tagger_time, TM_DIFF);
2340 if (fcgi_gen_response(c, "<div id='tag_age'>") == -1)
2342 if (fcgi_gen_response(c, age ? age : "") == -1)
2344 if (fcgi_gen_response(c, "</div>\n") == -1)
2347 if (fcgi_gen_response(c, "<div id='tag'>") == -1)
2349 if (strncmp(rt->tag_name, "refs/tags/", 10) == 0)
2351 if (fcgi_gen_response(c, rt->tag_name) == -1)
2353 if (fcgi_gen_response(c, "</div>\n") == -1)
2356 if (fcgi_gen_response(c, "<div id='tags_log'>") == -1)
2358 if (rt->tag_commit != NULL) {
2359 newline = strchr(rt->tag_commit, '\n');
2364 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2366 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2368 if (fcgi_gen_response(c, "&path=") == -1)
2370 if (fcgi_gen_response(c, repo_dir->name) == -1)
2372 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2374 if (fcgi_gen_response(c, rt->commit_id) == -1)
2376 if (fcgi_gen_response(c, "'>") == -1)
2378 if (rt->tag_commit != NULL &&
2379 fcgi_gen_response(c, rt->tag_commit) == -1)
2381 if (fcgi_gen_response(c, "</a>") == -1)
2383 if (fcgi_gen_response(c, "</div>\n") == -1)
2386 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
2388 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
2391 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2393 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2395 if (fcgi_gen_response(c, "&path=") == -1)
2397 if (fcgi_gen_response(c, repo_dir->name) == -1)
2399 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2401 if (fcgi_gen_response(c, rt->commit_id) == -1)
2403 if (fcgi_gen_response(c, "'>") == -1)
2405 if (fcgi_gen_response(c, "tag") == -1)
2407 if (fcgi_gen_response(c, "</a>") == -1)
2410 if (fcgi_gen_response(c, " | ") == -1)
2413 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2415 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2417 if (fcgi_gen_response(c, "&path=") == -1)
2419 if (fcgi_gen_response(c, repo_dir->name) == -1)
2421 if (fcgi_gen_response(c, "&action=briefs&commit=") == -1)
2423 if (fcgi_gen_response(c, rt->commit_id) == -1)
2425 if (fcgi_gen_response(c, "'>") == -1)
2427 if (fcgi_gen_response(c, "commit briefs") == -1)
2429 if (fcgi_gen_response(c, "</a>") == -1)
2432 if (fcgi_gen_response(c, " | ") == -1)
2435 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2437 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2439 if (fcgi_gen_response(c, "&path=") == -1)
2441 if (fcgi_gen_response(c, repo_dir->name) == -1)
2443 if (fcgi_gen_response(c, "&action=commits&commit=") == -1)
2445 if (fcgi_gen_response(c, rt->commit_id) == -1)
2447 if (fcgi_gen_response(c, "'>") == -1)
2449 if (fcgi_gen_response(c, "commits") == -1)
2451 if (fcgi_gen_response(c, "</a>") == -1)
2454 if (fcgi_gen_response(c, "</div>\n") == -1)
2456 if (fcgi_gen_response(c, "</div>\n") == -1)
2458 if (fcgi_gen_response(c,
2459 "<div id='dotted_line'></div>\n") == -1)
2465 if (t->next_id || t->prev_id) {
2466 error = gotweb_render_navs(c);
2470 fcgi_gen_response(c, "</div>\n");
2476 const struct got_error *
2477 gotweb_escape_html(char **escaped_html, const char *orig_html)
2479 const struct got_error *error = NULL;
2480 struct escape_pair {
2491 size_t orig_len, len;
2494 orig_len = strlen(orig_html);
2496 for (i = 0; i < orig_len; i++) {
2497 for (j = 0; j < nitems(esc); j++) {
2498 if (orig_html[i] != esc[j].c)
2500 len += strlen(esc[j].s) - 1 /* escaped char */;
2504 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
2505 if (*escaped_html == NULL)
2506 return got_error_from_errno("calloc");
2509 for (i = 0; i < orig_len; i++) {
2511 for (j = 0; j < nitems(esc); j++) {
2512 if (orig_html[i] != esc[j].c)
2515 if (strlcat(*escaped_html, esc[j].s, len + 1)
2517 error = got_error(GOT_ERR_NO_SPACE);
2520 x += strlen(esc[j].s);
2525 (*escaped_html)[x] = orig_html[i];
2531 free(*escaped_html);
2532 *escaped_html = NULL;
2534 (*escaped_html)[x] = '\0';
2540 static const struct got_error *
2541 gotweb_load_got_path(struct request *c, struct repo_dir *repo_dir)
2543 const struct got_error *error = NULL;
2544 struct socket *sock = c->sock;
2545 struct server *srv = c->srv;
2546 struct transport *t = c->t;
2551 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2552 GOTWEB_GIT_DIR) == -1)
2553 return got_error_from_errno("asprintf");
2555 dt = opendir(dir_test);
2559 repo_dir->path = strdup(dir_test);
2560 if (repo_dir->path == NULL) {
2562 error = got_error_from_errno("strdup");
2569 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2570 GOTWEB_GOT_DIR) == -1) {
2572 error = got_error_from_errno("asprintf");
2576 dt = opendir(dir_test);
2581 error = got_error(GOT_ERR_NOT_GIT_REPO);
2585 if (asprintf(&dir_test, "%s/%s", srv->repos_path,
2586 repo_dir->name) == -1) {
2587 error = got_error_from_errno("asprintf");
2592 repo_dir->path = strdup(dir_test);
2593 if (repo_dir->path == NULL) {
2595 error = got_error_from_errno("strdup");
2599 dt = opendir(dir_test);
2601 error = got_error_path(repo_dir->name, GOT_ERR_NOT_GIT_REPO);
2606 error = got_repo_open(&t->repo, repo_dir->path, NULL, sock->pack_fds);
2609 error = gotweb_get_repo_description(&repo_dir->description, srv,
2613 error = got_get_repo_owner(&repo_dir->owner, c, repo_dir->path);
2616 error = got_get_repo_age(&repo_dir->age, c, repo_dir->path,
2620 error = gotweb_get_clone_url(&repo_dir->url, srv, repo_dir->path);
2624 if (dt != NULL && closedir(dt) == EOF && error == NULL)
2625 error = got_error_from_errno("closedir");
2629 static const struct got_error *
2630 gotweb_init_repo_dir(struct repo_dir **repo_dir, const char *dir)
2632 const struct got_error *error;
2634 *repo_dir = calloc(1, sizeof(**repo_dir));
2635 if (*repo_dir == NULL)
2636 return got_error_from_errno("calloc");
2638 if (asprintf(&(*repo_dir)->name, "%s", dir) == -1) {
2639 error = got_error_from_errno("asprintf");
2644 (*repo_dir)->owner = NULL;
2645 (*repo_dir)->description = NULL;
2646 (*repo_dir)->url = NULL;
2647 (*repo_dir)->age = NULL;
2648 (*repo_dir)->path = NULL;
2653 static const struct got_error *
2654 gotweb_get_repo_description(char **description, struct server *srv, char *dir)
2656 const struct got_error *error = NULL;
2658 char *d_file = NULL;
2662 *description = NULL;
2663 if (srv->show_repo_description == 0)
2666 if (asprintf(&d_file, "%s/description", dir) == -1)
2667 return got_error_from_errno("asprintf");
2669 f = fopen(d_file, "r");
2671 if (errno == ENOENT || errno == EACCES)
2673 error = got_error_from_errno2("fopen", d_file);
2677 if (fseek(f, 0, SEEK_END) == -1) {
2678 error = got_ferror(f, GOT_ERR_IO);
2683 error = got_ferror(f, GOT_ERR_IO);
2690 if (fseek(f, 0, SEEK_SET) == -1) {
2691 error = got_ferror(f, GOT_ERR_IO);
2694 *description = calloc(len + 1, sizeof(**description));
2695 if (*description == NULL) {
2696 error = got_error_from_errno("calloc");
2700 n = fread(*description, 1, len, f);
2701 if (n == 0 && ferror(f))
2702 error = got_ferror(f, GOT_ERR_IO);
2704 if (f != NULL && fclose(f) == EOF && error == NULL)
2705 error = got_error_from_errno("fclose");
2710 static const struct got_error *
2711 gotweb_get_clone_url(char **url, struct server *srv, char *dir)
2713 const struct got_error *error = NULL;
2715 char *d_file = NULL;
2721 if (srv->show_repo_cloneurl == 0)
2724 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2725 return got_error_from_errno("asprintf");
2727 f = fopen(d_file, "r");
2729 if (errno != ENOENT && errno != EACCES)
2730 error = got_error_from_errno2("fopen", d_file);
2734 if (fseek(f, 0, SEEK_END) == -1) {
2735 error = got_ferror(f, GOT_ERR_IO);
2740 error = got_ferror(f, GOT_ERR_IO);
2746 if (fseek(f, 0, SEEK_SET) == -1) {
2747 error = got_ferror(f, GOT_ERR_IO);
2751 *url = calloc(len + 1, sizeof(**url));
2753 error = got_error_from_errno("calloc");
2757 n = fread(*url, 1, len, f);
2758 if (n == 0 && ferror(f))
2759 error = got_ferror(f, GOT_ERR_IO);
2761 if (f != NULL && fclose(f) == EOF && error == NULL)
2762 error = got_error_from_errno("fclose");
2767 const struct got_error *
2768 gotweb_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2772 const char *years = "years ago", *months = "months ago";
2773 const char *weeks = "weeks ago", *days = "days ago";
2774 const char *hours = "hours ago", *minutes = "minutes ago";
2775 const char *seconds = "seconds ago", *now = "right now";
2783 diff_time = time(NULL) - committer_time;
2784 if (diff_time > 60 * 60 * 24 * 365 * 2) {
2785 if (asprintf(repo_age, "%lld %s",
2786 (diff_time / 60 / 60 / 24 / 365), years) == -1)
2787 return got_error_from_errno("asprintf");
2788 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
2789 if (asprintf(repo_age, "%lld %s",
2790 (diff_time / 60 / 60 / 24 / (365 / 12)),
2792 return got_error_from_errno("asprintf");
2793 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
2794 if (asprintf(repo_age, "%lld %s",
2795 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
2796 return got_error_from_errno("asprintf");
2797 } else if (diff_time > 60 * 60 * 24 * 2) {
2798 if (asprintf(repo_age, "%lld %s",
2799 (diff_time / 60 / 60 / 24), days) == -1)
2800 return got_error_from_errno("asprintf");
2801 } else if (diff_time > 60 * 60 * 2) {
2802 if (asprintf(repo_age, "%lld %s",
2803 (diff_time / 60 / 60), hours) == -1)
2804 return got_error_from_errno("asprintf");
2805 } else if (diff_time > 60 * 2) {
2806 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
2808 return got_error_from_errno("asprintf");
2809 } else if (diff_time > 2) {
2810 if (asprintf(repo_age, "%lld %s", diff_time,
2812 return got_error_from_errno("asprintf");
2814 if (asprintf(repo_age, "%s", now) == -1)
2815 return got_error_from_errno("asprintf");
2819 if (gmtime_r(&committer_time, &tm) == NULL)
2820 return got_error_from_errno("gmtime_r");
2822 s = asctime_r(&tm, datebuf);
2824 return got_error_from_errno("asctime_r");
2826 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2827 return got_error_from_errno("asprintf");