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>
23 #include <sys/types.h>
33 #include "got_error.h"
34 #include "got_object.h"
35 #include "got_reference.h"
36 #include "got_repository.h"
38 #include "got_cancel.h"
39 #include "got_worktree.h"
41 #include "got_commit_graph.h"
42 #include "got_blame.h"
43 #include "got_privsep.h"
48 #include "got_compat.h"
55 static const struct querystring_keys querystring_keys[] = {
60 { "headref", HEADREF },
61 { "index_page", INDEX_PAGE },
66 static const struct action_keys action_keys[] = {
70 { "commits", COMMITS },
74 { "summary", SUMMARY },
80 static const struct got_error *gotweb_init_querystring(struct querystring **);
81 static const struct got_error *gotweb_parse_querystring(struct querystring **,
83 static const struct got_error *gotweb_assign_querystring(struct querystring **,
85 static const struct got_error *gotweb_render_header(struct request *);
86 static const struct got_error *gotweb_render_footer(struct request *);
87 static const struct got_error *gotweb_render_index(struct request *);
88 static const struct got_error *gotweb_init_repo_dir(struct repo_dir **,
90 static const struct got_error *gotweb_load_got_path(struct request *c,
92 static const struct got_error *gotweb_get_repo_description(char **,
93 struct server *, char *);
94 static const struct got_error *gotweb_get_clone_url(char **, struct server *,
96 static const struct got_error *gotweb_render_navs(struct request *);
97 static const struct got_error *gotweb_render_blame(struct request *);
98 static const struct got_error *gotweb_render_briefs(struct request *);
99 static const struct got_error *gotweb_render_commits(struct request *);
100 static const struct got_error *gotweb_render_diff(struct request *);
101 static const struct got_error *gotweb_render_summary(struct request *);
102 static const struct got_error *gotweb_render_tag(struct request *);
103 static const struct got_error *gotweb_render_tags(struct request *);
104 static const struct got_error *gotweb_render_tree(struct request *);
105 static const struct got_error *gotweb_render_branches(struct request *);
107 static void gotweb_free_querystring(struct querystring *);
108 static void gotweb_free_repo_dir(struct repo_dir *);
110 struct server *gotweb_get_server(uint8_t *, uint8_t *, uint8_t *);
113 gotweb_process_request(struct request *c)
115 const struct got_error *error = NULL, *error2 = NULL;
116 struct server *srv = NULL;
117 struct querystring *qs = NULL;
118 struct repo_dir *repo_dir = NULL;
119 uint8_t err[] = "gotwebd experienced an error: ";
122 /* init the transport */
123 error = gotweb_init_transport(&c->t);
125 log_warnx("%s: %s", __func__, error->msg);
128 /* don't process any further if client disconnected */
129 if (c->sock->client_status == CLIENT_DISCONNECT)
131 /* get the gotwebd server */
132 srv = gotweb_get_server(c->server_name, c->document_root, c->http_host);
134 log_warnx("%s: error server is NULL", __func__);
138 /* parse our querystring */
139 error = gotweb_init_querystring(&qs);
141 log_warnx("%s: %s", __func__, error->msg);
145 error = gotweb_parse_querystring(&qs, c->querystring);
147 gotweb_free_querystring(qs);
148 log_warnx("%s: %s", __func__, error->msg);
153 * certain actions require a commit id in the querystring. this stops
154 * bad actors from exploiting this by manually manipulating the
158 if (qs->commit == NULL && (qs->action == BLAME || qs->action == BLOB ||
159 qs->action == DIFF)) {
160 error2 = got_error(GOT_ERR_QUERYSTRING);
164 if (qs->action != INDEX) {
165 error = gotweb_init_repo_dir(&repo_dir, qs->path);
168 error = gotweb_load_got_path(c, repo_dir);
169 c->t->repo_dir = repo_dir;
170 if (error && error->code != GOT_ERR_LONELY_PACKIDX)
174 /* render top of page */
175 if (qs != NULL && qs->action == BLOB) {
176 error = got_get_repo_commits(c, 1);
179 error = gotweb_render_content_type(c, "text/plain");
181 log_warnx("%s: %s", __func__, error->msg);
184 error = got_output_file_blob(c);
186 log_warnx("%s: %s", __func__, error->msg);
192 error = gotweb_render_content_type(c, "text/html");
194 log_warnx("%s: %s", __func__, error->msg);
200 error = gotweb_render_header(c);
202 log_warnx("%s: %s", __func__, error->msg);
213 error = gotweb_render_blame(c);
215 log_warnx("%s: %s", __func__, error->msg);
220 error = gotweb_render_briefs(c);
222 log_warnx("%s: %s", __func__, error->msg);
227 error = gotweb_render_commits(c);
229 log_warnx("%s: %s", __func__, error->msg);
234 error = gotweb_render_diff(c);
236 log_warnx("%s: %s", __func__, error->msg);
241 error = gotweb_render_index(c);
243 log_warnx("%s: %s", __func__, error->msg);
248 error = gotweb_render_summary(c);
250 log_warnx("%s: %s", __func__, error->msg);
255 error = gotweb_render_tag(c);
257 log_warnx("%s: %s", __func__, error->msg);
262 error = gotweb_render_tags(c);
264 log_warnx("%s: %s", __func__, error->msg);
269 error = gotweb_render_tree(c);
271 log_warnx("%s: %s", __func__, error->msg);
277 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
279 if (fcgi_gen_response(c, "Error: Bad Querystring\n") == -1)
281 if (fcgi_gen_response(c, "</div>\n") == -1)
288 if (html && fcgi_gen_response(c, "<div id='err_content'>") == -1)
290 if (fcgi_gen_response(c, err) == -1)
293 if (fcgi_gen_response(c, (uint8_t *)error->msg) == -1)
296 if (fcgi_gen_response(c, "see daemon logs for details") == -1)
299 if (html && fcgi_gen_response(c, "</div>\n") == -1)
302 if (c->t->repo != NULL && qs->action != INDEX)
303 got_repo_close(c->t->repo);
304 if (html && srv != NULL)
305 gotweb_render_footer(c);
309 gotweb_get_server(uint8_t *server_name, uint8_t *document_root,
312 struct server *srv = NULL;
314 /* check against document_root first */
315 if (strlen(server_name) > 0)
316 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
317 if (strcmp(srv->name, server_name) == 0)
320 /* check against document_root second */
321 if (strlen(document_root) > 0)
322 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
323 if (strcmp(srv->name, document_root) == 0)
326 /* check against subdomain third */
327 if (strlen(subdomain) > 0)
328 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
329 if (strcmp(srv->name, subdomain) == 0)
332 /* if those fail, send first server */
333 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
340 const struct got_error *
341 gotweb_init_transport(struct transport **t)
343 const struct got_error *error = NULL;
345 *t = calloc(1, sizeof(**t));
347 return got_error_from_errno2("%s: calloc", __func__);
349 TAILQ_INIT(&(*t)->repo_commits);
350 TAILQ_INIT(&(*t)->repo_tags);
353 (*t)->repo_dir = NULL;
355 (*t)->next_id = NULL;
356 (*t)->prev_id = NULL;
363 static const struct got_error *
364 gotweb_init_querystring(struct querystring **qs)
366 const struct got_error *error = NULL;
368 *qs = calloc(1, sizeof(**qs));
370 return got_error_from_errno2("%s: calloc", __func__);
372 (*qs)->action = INDEX;
373 (*qs)->commit = NULL;
375 (*qs)->folder = NULL;
376 (*qs)->headref = strdup("HEAD");
377 if ((*qs)->headref == NULL) {
378 return got_error_from_errno2("%s: strdup", __func__);
380 (*qs)->index_page = 0;
381 (*qs)->index_page_str = NULL;
387 static const struct got_error *
388 gotweb_parse_querystring(struct querystring **qs, char *qst)
390 const struct got_error *error = NULL;
391 char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL;
392 char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL;
399 return got_error_from_errno2("%s: strdup", __func__);
404 while (tok1_pair != NULL) {
405 strsep(&tok1_end, "&");
407 tok2 = strdup(tok1_pair);
410 return got_error_from_errno2("%s: strdup", __func__);
416 while (tok2_pair != NULL) {
417 strsep(&tok2_end, "=");
419 error = gotweb_assign_querystring(qs, tok2_pair,
424 tok2_pair = tok2_end;
427 tok1_pair = tok1_end;
437 static const struct got_error *
438 gotweb_assign_querystring(struct querystring **qs, char *key, char *value)
440 const struct got_error *error = NULL;
444 for (el_cnt = 0; el_cnt < QSELEM__MAX; el_cnt++) {
445 if (strcmp(key, querystring_keys[el_cnt].name) != 0)
448 switch (querystring_keys[el_cnt].element) {
450 for (a_cnt = 0; a_cnt < ACTIONS__MAX; a_cnt++) {
451 if (strcmp(value, action_keys[a_cnt].name) != 0)
453 else if (strcmp(value,
454 action_keys[a_cnt].name) == 0){
456 action_keys[a_cnt].action;
464 (*qs)->commit = strdup(value);
465 if ((*qs)->commit == NULL) {
466 error = got_error_from_errno2("%s: strdup",
472 (*qs)->file = strdup(value);
473 if ((*qs)->file == NULL) {
474 error = got_error_from_errno2("%s: strdup",
480 (*qs)->folder = strdup(value);
481 if ((*qs)->folder == NULL) {
482 error = got_error_from_errno2("%s: strdup",
488 (*qs)->headref = strdup(value);
489 if ((*qs)->headref == NULL) {
490 error = got_error_from_errno2("%s: strdup",
496 if (strlen(value) == 0)
498 (*qs)->index_page_str = strdup(value);
499 if ((*qs)->index_page_str == NULL) {
500 error = got_error_from_errno2("%s: strdup",
504 (*qs)->index_page = strtonum(value, INT64_MIN,
507 error = got_error_from_errno3("%s: strtonum %s",
511 if ((*qs)->index_page < 0) {
512 (*qs)->index_page = 0;
513 sprintf((*qs)->index_page_str, "%d", 0);
517 (*qs)->path = strdup(value);
518 if ((*qs)->path == NULL) {
519 error = got_error_from_errno2("%s: strdup",
525 if (strlen(value) == 0)
527 (*qs)->page_str = strdup(value);
528 if ((*qs)->page_str == NULL) {
529 error = got_error_from_errno2("%s: strdup",
533 (*qs)->page = strtonum(value, INT64_MIN,
536 error = got_error_from_errno3("%s: strtonum %s",
540 if ((*qs)->page < 0) {
542 sprintf((*qs)->page_str, "%d", 0);
554 gotweb_free_repo_tag(struct repo_tag *rt)
557 free(rt->commit_msg);
565 gotweb_free_repo_commit(struct repo_commit *rc)
575 free(rc->commit_msg);
581 gotweb_free_querystring(struct querystring *qs)
588 free(qs->index_page_str);
596 gotweb_free_repo_dir(struct repo_dir *repo_dir)
598 if (repo_dir != NULL) {
599 free(repo_dir->name);
600 free(repo_dir->owner);
601 free(repo_dir->description);
604 free(repo_dir->path);
610 gotweb_free_transport(struct transport *t)
612 struct repo_commit *rc = NULL, *trc = NULL;
613 struct repo_tag *rt = NULL, *trt = NULL;
615 TAILQ_FOREACH_SAFE(rc, &t->repo_commits, entry, trc) {
616 TAILQ_REMOVE(&t->repo_commits, rc, entry);
617 gotweb_free_repo_commit(rc);
619 TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
620 TAILQ_REMOVE(&t->repo_tags, rt, entry);
621 gotweb_free_repo_tag(rt);
623 gotweb_free_repo_dir(t->repo_dir);
624 gotweb_free_querystring(t->qs);
632 const struct got_error *
633 gotweb_render_content_type(struct request *c, const uint8_t *type)
635 const struct got_error *error = NULL;
638 if (asprintf(&h, "Content-type: %s\r\n\r\n", type) == -1) {
639 error = got_error_from_errno2("%s: asprintf", __func__);
643 fcgi_gen_response(c, h);
650 const struct got_error *
651 gotweb_render_content_type_file(struct request *c, const uint8_t *type,
654 const struct got_error *error = NULL;
657 if (asprintf(&h, "Content-type: %s\r\n"
658 "Content-disposition: attachment; filename=%s\r\n\r\n",
660 error = got_error_from_errno2("%s: asprintf", __func__);
664 fcgi_gen_response(c, h);
671 static const struct got_error *
672 gotweb_render_header(struct request *c)
674 const struct got_error *error = NULL;
675 struct server *srv = c->srv;
676 struct querystring *qs = c->t->qs;
677 char *title = NULL, *droot = NULL, *css = NULL, *gotlink = NULL;
678 char *gotimg = NULL, *sitelink = NULL, *summlink = NULL;
680 if (strlen(c->document_root) > 0) {
681 if (asprintf(&droot, "/%s/", c->document_root) == -1) {
682 error = got_error_from_errno2("%s: asprintf", __func__);
686 if (asprintf(&droot, "/") == -1) {
687 error = got_error_from_errno2("%s: asprintf", __func__);
692 if (asprintf(&title, "<title>%s</title>\n", srv->site_name) == -1) {
693 error = got_error_from_errno2("%s: asprintf", __func__);
697 "<link rel='stylesheet' type='text/css' href='%s%s'/>\n",
698 droot, srv->custom_css) == -1) {
699 error = got_error_from_errno2("%s: asprintf", __func__);
702 if (asprintf(&gotlink, "<a href='%s' target='_sotd'>",
703 srv->logo_url) == -1) {
704 error = got_error_from_errno2("%s: asprintf", __func__);
707 if (asprintf(&gotimg, "<img src='%s%s' alt='logo' id='logo'/></a>",
708 droot, srv->logo) == -1) {
709 error = got_error_from_errno2("%s: asprintf", __func__);
712 if (asprintf(&sitelink, "<a href='/%s?index_page=%d' "
713 "alt='sitelink'>%s</a>", c->document_root, qs->index_page,
714 srv->site_link) == -1) {
715 error = got_error_from_errno2("%s: asprintf", __func__);
718 if (asprintf(&summlink, "<a href='/%s?index_page=%d&path=%s"
719 "&action=summary' alt='summlink'>%s</a>", c->document_root,
720 qs->index_page, qs->path, qs->path) == -1) {
721 error = got_error_from_errno2("%s: asprintf", __func__);
725 if (fcgi_gen_response(c, "<!DOCTYPE html>\n<head>\n") == -1)
727 if (fcgi_gen_response(c, title) == -1)
729 if (fcgi_gen_response(c, "<meta name='viewport' "
730 "content='initial-scale=.75, user-scalable=yes'/>\n") == -1)
732 if (fcgi_gen_response(c, "<meta charset='utf-8'/>\n") == -1)
734 if (fcgi_gen_response(c, "<meta name='msapplication-TileColor' "
735 "content='#da532c'/>\n") == -1)
737 if (fcgi_gen_response(c,
738 "<meta name='theme-color' content='#ffffff'/>\n") == -1)
740 if (fcgi_gen_response(c, "<link rel='apple-touch-icon' sizes='180x180' "
741 "href='/apple-touch-icon.png'/>\n") == -1)
743 if (fcgi_gen_response(c,
744 "<link rel='icon' type='image/png' sizes='32x32' "
745 "href='/favicon-32x32.png'/>\n") == -1)
747 if (fcgi_gen_response(c, "<link rel='icon' type='image/png' "
748 "sizes='16x16' href='/favicon-16x16.png'/>\n") == -1)
750 if (fcgi_gen_response(c, "<link rel='manifest' "
751 "href='/site.webmanifest'/>\n") == -1)
753 if (fcgi_gen_response(c, "<link rel='mask-icon' "
754 "href='/safari-pinned-tab.svg'/>\n") == -1)
756 if (fcgi_gen_response(c, css) == -1)
758 if (fcgi_gen_response(c, "</head>\n<body>\n<div id='gw_body'>\n") == -1)
760 if (fcgi_gen_response(c,
761 "<div id='header'>\n<div id='got_link'>") == -1)
763 if (fcgi_gen_response(c, gotlink) == -1)
765 if (fcgi_gen_response(c, gotimg) == -1)
767 if (fcgi_gen_response(c, "</div>\n</div>\n") == -1)
769 if (fcgi_gen_response(c,
770 "<div id='site_path'>\n<div id='site_link'>") == -1)
772 if (fcgi_gen_response(c, sitelink) == -1)
775 if (qs->path != NULL) {
776 if (fcgi_gen_response(c, " / ") == -1)
778 if (fcgi_gen_response(c, summlink) == -1)
781 if (qs->action != INDEX) {
782 if (fcgi_gen_response(c, " / ") == -1)
786 if (fcgi_gen_response(c, "blame") == -1)
790 if (fcgi_gen_response(c, "briefs") == -1)
794 if (fcgi_gen_response(c, "commits") == -1)
798 if (fcgi_gen_response(c, "diff") == -1)
802 if (fcgi_gen_response(c, "summary") == -1)
806 if (fcgi_gen_response(c, "tag") == -1)
810 if (fcgi_gen_response(c, "tags") == -1)
814 if (fcgi_gen_response(c, "tree") == -1)
823 fcgi_gen_response(c, "</div>\n</div>\n<div id='content'>\n");
836 static const struct got_error *
837 gotweb_render_footer(struct request *c)
839 const struct got_error *error = NULL;
840 struct server *srv = c->srv;
841 char *siteowner = NULL;
843 if (fcgi_gen_response(c, "<div id='site_owner_wrapper'>\n") == -1)
845 if (fcgi_gen_response(c, "<div id='site_owner'>") == -1)
847 if (srv->show_site_owner) {
848 error = gotweb_escape_html(&siteowner, srv->site_owner);
851 if (fcgi_gen_response(c, siteowner) == -1)
854 if (fcgi_gen_response(c, " ") == -1)
856 fcgi_gen_response(c, "</div>\n</div>\n</div>\n</body>\n</html>");
863 static const struct got_error *
864 gotweb_render_navs(struct request *c)
866 const struct got_error *error = NULL;
867 struct transport *t = c->t;
868 struct querystring *qs = t->qs;
869 struct server *srv = c->srv;
870 char *nhref = NULL, *phref = NULL;
873 if (fcgi_gen_response(c, "<div id='np_wrapper'>\n") == -1)
875 if (fcgi_gen_response(c, "<div id='nav_prev'>") == -1)
880 if (qs->index_page > 0) {
881 if (asprintf(&phref, "index_page=%d",
882 qs->index_page - 1) == -1) {
883 error = got_error_from_errno2("%s: asprintf",
891 if (t->prev_id && qs->commit != NULL &&
892 strcmp(qs->commit, t->prev_id) != 0) {
893 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
894 "&action=briefs&commit=%s&headref=%s",
895 qs->index_page, qs->path, qs->page - 1, t->prev_id,
896 qs->headref) == -1) {
897 error = got_error_from_errno2("%s: asprintf",
905 if (t->prev_id && qs->commit != NULL &&
906 strcmp(qs->commit, t->prev_id) != 0) {
907 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
908 "&action=commits&commit=%s&headref=%s&folder=%s"
910 qs->index_page, qs->path, qs->page - 1, t->prev_id,
911 qs->headref, qs->folder ? qs->folder : "",
912 qs->file ? qs->file : "") == -1) {
913 error = got_error_from_errno2("%s: asprintf",
921 if (t->prev_id && qs->commit != NULL &&
922 strcmp(qs->commit, t->prev_id) != 0) {
923 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
924 "&action=tags&commit=%s&headref=%s",
925 qs->index_page, qs->path, qs->page - 1, t->prev_id,
926 qs->headref) == -1) {
927 error = got_error_from_errno2("%s: asprintf",
940 if (fcgi_gen_response(c, "<a href='?") == -1)
942 if (fcgi_gen_response(c, phref) == -1)
944 if (fcgi_gen_response(c, "'>Previous</a>") == -1)
947 if (fcgi_gen_response(c, "</div>\n") == -1)
949 if (fcgi_gen_response(c, "<div id='nav_next'>") == -1)
956 if (t->next_disp == srv->max_repos_display &&
957 t->repos_total != (qs->index_page + 1) *
958 srv->max_repos_display) {
959 if (asprintf(&nhref, "index_page=%d",
960 qs->index_page + 1) == -1) {
961 error = got_error_from_errno2("%s: asprintf",
970 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
971 "&action=briefs&commit=%s&headref=%s",
972 qs->index_page, qs->path, qs->page + 1, t->next_id,
973 qs->headref) == -1) {
974 error = got_error_from_errno2("%s: asprintf",
983 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
984 "&action=commits&commit=%s&headref=%s&folder=%s"
986 qs->index_page, qs->path, qs->page + 1, t->next_id,
987 qs->headref, qs->folder ? qs->folder : "",
988 qs->file ? qs->file : "") == -1) {
989 error = got_error_from_errno2("%s: asprintf",
998 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
999 "&action=tags&commit=%s&headref=%s",
1000 qs->index_page, qs->path, qs->page + 1, t->next_id,
1001 qs->headref) == -1) {
1002 error = got_error_from_errno2("%s: asprintf",
1014 if (fcgi_gen_response(c, "<a href='?") == -1)
1016 if (fcgi_gen_response(c, nhref) == -1)
1018 if (fcgi_gen_response(c, "'>Next</a>") == -1)
1021 fcgi_gen_response(c, "</div>\n");
1032 static const struct got_error *
1033 gotweb_render_index(struct request *c)
1035 const struct got_error *error = NULL;
1036 struct server *srv = c->srv;
1037 struct transport *t = c->t;
1038 struct querystring *qs = t->qs;
1039 struct repo_dir *repo_dir = NULL;
1041 struct dirent **sd_dent;
1042 char *c_path = NULL;
1044 unsigned int d_cnt, d_i, d_disp = 0;
1046 d = opendir(srv->repos_path);
1048 error = got_error_from_errno2("opendir", srv->repos_path);
1052 d_cnt = scandir(srv->repos_path, &sd_dent, NULL, alphasort);
1054 error = got_error_from_errno2("scandir", srv->repos_path);
1058 /* get total count of repos */
1059 for (d_i = 0; d_i < d_cnt; d_i++) {
1060 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1061 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1064 if (asprintf(&c_path, "%s/%s", srv->repos_path,
1065 sd_dent[d_i]->d_name) == -1) {
1066 error = got_error_from_errno("asprintf");
1070 if (lstat(c_path, &st) == 0 && S_ISDIR(st.st_mode) &&
1071 !got_path_dir_is_empty(c_path))
1077 if (fcgi_gen_response(c, "<div id='index_header'>\n") == -1)
1079 if (fcgi_gen_response(c,
1080 "<div id='index_header_project'>Project</div>\n") == -1)
1082 if (srv->show_repo_description)
1083 if (fcgi_gen_response(c, "<div id='index_header_description'>"
1084 "Description</div>\n") == -1)
1086 if (srv->show_repo_owner)
1087 if (fcgi_gen_response(c, "<div id='index_header_owner'>"
1088 "Owner</div>\n") == -1)
1090 if (srv->show_repo_age)
1091 if (fcgi_gen_response(c, "<div id='index_header_age'>"
1092 "Last Change</div>\n") == -1)
1094 if (fcgi_gen_response(c, "</div>\n") == -1)
1097 for (d_i = 0; d_i < d_cnt; d_i++) {
1098 if (srv->max_repos > 0 && (d_i - 2) == srv->max_repos)
1099 break; /* account for parent and self */
1101 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1102 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1105 if (qs->index_page > 0 && (qs->index_page *
1106 srv->max_repos_display) > t->prev_disp) {
1111 error = gotweb_init_repo_dir(&repo_dir, sd_dent[d_i]->d_name);
1115 error = gotweb_load_got_path(c, repo_dir);
1116 if (error && error->code == GOT_ERR_NOT_GIT_REPO) {
1120 else if (error && error->code != GOT_ERR_LONELY_PACKIDX)
1123 if (lstat(repo_dir->path, &st) == 0 &&
1124 S_ISDIR(st.st_mode) &&
1125 !got_path_dir_is_empty(repo_dir->path))
1128 gotweb_free_repo_dir(repo_dir);
1135 if (fcgi_gen_response(c, "<div id='index_wrapper'>\n") == -1)
1137 if (fcgi_gen_response(c, "<div id='index_project'>") == -1)
1140 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1142 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1144 if (fcgi_gen_response(c, "&path=") == -1)
1146 if (fcgi_gen_response(c, repo_dir->name) == -1)
1148 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1150 if (fcgi_gen_response(c, repo_dir->name) == -1)
1152 if (fcgi_gen_response(c, "</a>") == -1)
1155 if (fcgi_gen_response(c, "</div>\n") == -1)
1158 if (srv->show_repo_description) {
1159 if (fcgi_gen_response(c,
1160 "<div id='index_project_description'>\n") == -1)
1162 if (fcgi_gen_response(c, repo_dir->description) == -1)
1164 if (fcgi_gen_response(c, "</div>\n") == -1)
1168 if (srv->show_repo_owner) {
1169 if (fcgi_gen_response(c,
1170 "<div id='index_project_owner'>") == -1)
1172 if (fcgi_gen_response(c, repo_dir->owner) == -1)
1174 if (fcgi_gen_response(c, "</div>\n") == -1)
1178 if (srv->show_repo_age) {
1179 if (fcgi_gen_response(c,
1180 "<div id='index_project_age'>") == -1)
1182 if (fcgi_gen_response(c, repo_dir->age) == -1)
1184 if (fcgi_gen_response(c, "</div>\n") == -1)
1188 if (fcgi_gen_response(c, "<div id='navs_wrapper'>") == -1)
1190 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1193 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1195 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1197 if (fcgi_gen_response(c, "&path=") == -1)
1199 if (fcgi_gen_response(c, repo_dir->name) == -1)
1201 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1203 if (fcgi_gen_response(c, "summary") == -1)
1205 if (fcgi_gen_response(c, "</a> | ") == -1)
1208 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1210 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1212 if (fcgi_gen_response(c, "&path=") == -1)
1214 if (fcgi_gen_response(c, repo_dir->name) == -1)
1216 if (fcgi_gen_response(c, "&action=briefs'>") == -1)
1218 if (fcgi_gen_response(c, "commit briefs") == -1)
1220 if (fcgi_gen_response(c, "</a> | ") == -1)
1223 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1225 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1227 if (fcgi_gen_response(c, "&path=") == -1)
1229 if (fcgi_gen_response(c, repo_dir->name) == -1)
1231 if (fcgi_gen_response(c, "&action=commits'>") == -1)
1233 if (fcgi_gen_response(c, "commits") == -1)
1235 if (fcgi_gen_response(c, "</a> | ") == -1)
1238 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1240 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1242 if (fcgi_gen_response(c, "&path=") == -1)
1244 if (fcgi_gen_response(c, repo_dir->name) == -1)
1246 if (fcgi_gen_response(c, "&action=tags'>") == -1)
1248 if (fcgi_gen_response(c, "tags") == -1)
1250 if (fcgi_gen_response(c, "</a> | ") == -1)
1253 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1255 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1257 if (fcgi_gen_response(c, "&path=") == -1)
1259 if (fcgi_gen_response(c, repo_dir->name) == -1)
1261 if (fcgi_gen_response(c, "&action=tree'>") == -1)
1263 if (fcgi_gen_response(c, "tree") == -1)
1265 if (fcgi_gen_response(c, "</a>") == -1)
1268 if (fcgi_gen_response(c, "</div>") == -1)
1270 if (fcgi_gen_response(c,
1271 "<div id='dotted_line'></div>\n") == -1)
1273 if (fcgi_gen_response(c, "</div>\n") == -1)
1275 if (fcgi_gen_response(c, "</div>\n") == -1)
1278 gotweb_free_repo_dir(repo_dir);
1280 error = got_repo_close(t->repo);
1284 if (d_disp == srv->max_repos_display)
1287 if (srv->max_repos_display == 0)
1289 if (srv->max_repos > 0 && srv->max_repos < srv->max_repos_display)
1291 if (t->repos_total <= srv->max_repos ||
1292 t->repos_total <= srv->max_repos_display)
1295 error = gotweb_render_navs(c);
1299 fcgi_gen_response(c, "</div>\n");
1301 if (d != NULL && closedir(d) == EOF && error == NULL)
1302 error = got_error_from_errno("closedir");
1306 static const struct got_error *
1307 gotweb_render_blame(struct request *c)
1309 const struct got_error *error = NULL;
1310 struct transport *t = c->t;
1311 struct repo_commit *rc = NULL;
1314 error = got_get_repo_commits(c, 1);
1318 rc = TAILQ_FIRST(&t->repo_commits);
1320 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1324 if (fcgi_gen_response(c, "<div id='blame_title_wrapper'>\n") == -1)
1326 if (fcgi_gen_response(c, "<div id='blame_title'>Blame</div>\n") == -1)
1328 if (fcgi_gen_response(c, "</div>\n") == -1)
1331 if (fcgi_gen_response(c, "<div id='blame_content'>\n") == -1)
1334 if (fcgi_gen_response(c, "<div id='blame_header_wrapper'>\n") == -1)
1336 if (fcgi_gen_response(c, "<div id='blame_header'>\n") == -1)
1339 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1342 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1344 if (fcgi_gen_response(c, age ? age : "") == -1)
1346 if (fcgi_gen_response(c, "</div>\n") == -1)
1349 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1352 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1354 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1356 if (fcgi_gen_response(c, "</div>\n") == -1)
1359 if (fcgi_gen_response(c, "</div>\n") == -1)
1361 if (fcgi_gen_response(c, "</div>\n") == -1)
1364 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1366 if (fcgi_gen_response(c, "<div id='blame'>\n") == -1)
1369 error = got_output_file_blame(c);
1373 fcgi_gen_response(c, "</div>\n");
1375 fcgi_gen_response(c, "</div>\n");
1379 static const struct got_error *
1380 gotweb_render_briefs(struct request *c)
1382 const struct got_error *error = NULL;
1383 struct repo_commit *rc = NULL;
1384 struct server *srv = c->srv;
1385 struct transport *t = c->t;
1386 struct querystring *qs = t->qs;
1387 struct repo_dir *repo_dir = t->repo_dir;
1388 char *smallerthan, *newline;
1391 if (fcgi_gen_response(c, "<div id='briefs_title_wrapper'>\n") == -1)
1393 if (fcgi_gen_response(c,
1394 "<div id='briefs_title'>Commit Briefs</div>\n") == -1)
1396 if (fcgi_gen_response(c, "</div>\n") == -1)
1399 if (fcgi_gen_response(c, "<div id='briefs_content'>\n") == -1)
1402 if (qs->action == SUMMARY) {
1403 qs->action = BRIEFS;
1404 error = got_get_repo_commits(c, D_MAXSLCOMMDISP);
1406 error = got_get_repo_commits(c, srv->max_commits_display);
1410 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1411 error = gotweb_get_time_str(&age, rc->committer_time, TM_DIFF);
1414 if (fcgi_gen_response(c, "<div id='briefs_age'>") == -1)
1416 if (fcgi_gen_response(c, age ? age : "") == -1)
1418 if (fcgi_gen_response(c, "</div>\n") == -1)
1421 if (fcgi_gen_response(c, "<div id='briefs_author'>") == -1)
1423 smallerthan = strchr(rc->author, '<');
1425 *smallerthan = '\0';
1426 if (fcgi_gen_response(c, rc->author) == -1)
1428 if (fcgi_gen_response(c, "</div>\n") == -1)
1431 if (fcgi_gen_response(c, "<div id='briefs_log'>") == -1)
1433 newline = strchr(rc->commit_msg, '\n');
1437 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1439 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1441 if (fcgi_gen_response(c, "&path=") == -1)
1443 if (fcgi_gen_response(c, repo_dir->name) == -1)
1445 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1447 if (fcgi_gen_response(c, rc->commit_id) == -1)
1449 if (fcgi_gen_response(c, "&headref=") == -1)
1451 if (fcgi_gen_response(c, qs->headref) == -1)
1453 if (fcgi_gen_response(c, "'>") == -1)
1455 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1457 if (fcgi_gen_response(c, "</a>") == -1)
1460 if (fcgi_gen_response(c,
1461 " <span id='refs_str'>(") == -1)
1463 if (fcgi_gen_response(c, rc->refs_str) == -1)
1465 if (fcgi_gen_response(c, ")</span>") == -1)
1468 if (fcgi_gen_response(c, "</div>\n") == -1)
1471 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1473 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1475 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1477 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1479 if (fcgi_gen_response(c, "&path=") == -1)
1481 if (fcgi_gen_response(c, repo_dir->name) == -1)
1483 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1485 if (fcgi_gen_response(c, rc->commit_id) == -1)
1487 if (fcgi_gen_response(c, "&headref=") == -1)
1489 if (fcgi_gen_response(c, qs->headref) == -1)
1491 if (fcgi_gen_response(c, "'>") == -1)
1493 if (fcgi_gen_response(c, "diff") == -1)
1495 if (fcgi_gen_response(c, "</a>") == -1)
1498 if (fcgi_gen_response(c, " | ") == -1)
1501 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1503 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1505 if (fcgi_gen_response(c, "&path=") == -1)
1507 if (fcgi_gen_response(c, repo_dir->name) == -1)
1509 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1511 if (fcgi_gen_response(c, rc->commit_id) == -1)
1513 if (fcgi_gen_response(c, "&headref=") == -1)
1515 if (fcgi_gen_response(c, qs->headref) == -1)
1517 if (fcgi_gen_response(c, "'>") == -1)
1519 if (fcgi_gen_response(c, "tree") == -1)
1521 if (fcgi_gen_response(c, "</a>") == -1)
1523 if (fcgi_gen_response(c, "</div>\n") == -1)
1525 if (fcgi_gen_response(c, "</div>\n") == -1)
1527 if (fcgi_gen_response(c,
1528 "<div id='dotted_line'></div>\n") == -1)
1535 if (t->next_id || t->prev_id) {
1536 error = gotweb_render_navs(c);
1540 fcgi_gen_response(c, "</div>\n");
1546 static const struct got_error *
1547 gotweb_render_commits(struct request *c)
1549 const struct got_error *error = NULL;
1550 struct repo_commit *rc = NULL;
1551 struct server *srv = c->srv;
1552 struct transport *t = c->t;
1553 struct querystring *qs = t->qs;
1554 struct repo_dir *repo_dir = t->repo_dir;
1555 char *age = NULL, *author = NULL;
1556 /* int commit_found = 0; */
1558 if (fcgi_gen_response(c, "<div id='commits_title_wrapper'>\n") == -1)
1560 if (fcgi_gen_response(c,
1561 "<div id='commits_title'>Commits</div>\n") == -1)
1563 if (fcgi_gen_response(c, "</div>\n") == -1)
1566 if (fcgi_gen_response(c, "<div id='commits_content'>\n") == -1)
1569 error = got_get_repo_commits(c, srv->max_commits_display);
1573 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1574 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1577 error = gotweb_escape_html(&author, rc->author);
1581 if (fcgi_gen_response(c,
1582 "<div id='commits_header_wrapper'>\n") == -1)
1584 if (fcgi_gen_response(c, "<div id='commits_header'>\n") == -1)
1588 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
1591 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
1593 if (fcgi_gen_response(c, rc->commit_id) == -1)
1595 if (fcgi_gen_response(c, "</div>\n") == -1)
1598 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
1601 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
1603 if (fcgi_gen_response(c, author ? author : "") == -1)
1605 if (fcgi_gen_response(c, "</div>\n") == -1)
1608 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1611 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1613 if (fcgi_gen_response(c, age ? age : "") == -1)
1615 if (fcgi_gen_response(c, "</div>\n") == -1)
1618 if (fcgi_gen_response(c, "</div>\n") == -1)
1620 if (fcgi_gen_response(c, "</div>\n") == -1)
1623 if (fcgi_gen_response(c,
1624 "<div id='dotted_line'></div>\n") == -1)
1626 if (fcgi_gen_response(c, "<div id='commit'>\n") == -1)
1629 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1632 if (fcgi_gen_response(c, "</div>\n") == -1)
1634 if (fcgi_gen_response(c, "</div>\n") == -1)
1637 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1639 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1641 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1643 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1645 if (fcgi_gen_response(c, "&path=") == -1)
1647 if (fcgi_gen_response(c, repo_dir->name) == -1)
1649 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1651 if (fcgi_gen_response(c, rc->commit_id) == -1)
1653 if (fcgi_gen_response(c, "'>") == -1)
1655 if (fcgi_gen_response(c, "diff") == -1)
1657 if (fcgi_gen_response(c, "</a>") == -1)
1660 if (fcgi_gen_response(c, " | ") == -1)
1663 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1665 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1667 if (fcgi_gen_response(c, "&path=") == -1)
1669 if (fcgi_gen_response(c, repo_dir->name) == -1)
1671 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1673 if (fcgi_gen_response(c, rc->commit_id) == -1)
1675 if (fcgi_gen_response(c, "'>") == -1)
1677 if (fcgi_gen_response(c, "tree") == -1)
1679 if (fcgi_gen_response(c, "</a>") == -1)
1681 if (fcgi_gen_response(c, "</div>\n") == -1)
1683 if (fcgi_gen_response(c, "</div>\n") == -1)
1685 if (fcgi_gen_response(c,
1686 "<div id='dotted_line'></div>\n") == -1)
1694 if (t->next_id || t->prev_id) {
1695 error = gotweb_render_navs(c);
1699 if (fcgi_gen_response(c, "</div>\n") == -1)
1701 fcgi_gen_response(c, "</div>\n");
1707 static const struct got_error *
1708 gotweb_render_branches(struct request *c)
1710 const struct got_error *error = NULL;
1711 struct got_reflist_head refs;
1712 struct got_reflist_entry *re;
1713 struct transport *t = c->t;
1714 struct querystring *qs = t->qs;
1715 struct got_repository *repo = t->repo;
1720 error = got_ref_list(&refs, repo, "refs/heads",
1721 got_ref_cmp_by_name, NULL);
1725 if (fcgi_gen_response(c, "<div id='branches_title_wrapper'>\n") == -1)
1727 if (fcgi_gen_response(c,
1728 "<div id='branches_title'>Branches</div>\n") == -1)
1730 if (fcgi_gen_response(c, "</div>\n") == -1)
1733 if (fcgi_gen_response(c, "<div id='branches_content'>\n") == -1)
1736 TAILQ_FOREACH(re, &refs, entry) {
1737 char *refname = NULL;
1739 if (got_ref_is_symbolic(re->ref))
1742 refname = strdup(got_ref_get_name(re->ref));
1743 if (refname == NULL) {
1744 error = got_error_from_errno("strdup");
1747 if (strncmp(refname, "refs/heads/", 11) != 0)
1750 error = got_get_repo_age(&age, c, qs->path, refname,
1755 if (strncmp(refname, "refs/heads/", 11) == 0)
1758 if (fcgi_gen_response(c, "<div id='branches_wrapper'>") == -1)
1761 if (fcgi_gen_response(c, "<div id='branches_age'>") == -1)
1763 if (fcgi_gen_response(c, age ? age : "") == -1)
1765 if (fcgi_gen_response(c, "</div>\n") == -1)
1768 if (fcgi_gen_response(c, "<div id='branches_space'>") == -1)
1770 if (fcgi_gen_response(c, " ") == -1)
1772 if (fcgi_gen_response(c, "</div>\n") == -1)
1775 if (fcgi_gen_response(c, "<div id='branch'>") == -1)
1777 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1779 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1781 if (fcgi_gen_response(c, "&path=") == -1)
1783 if (fcgi_gen_response(c, qs->path) == -1)
1785 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1787 if (fcgi_gen_response(c, refname) == -1)
1789 if (fcgi_gen_response(c, "'>") == -1)
1791 if (fcgi_gen_response(c, refname) == -1)
1793 if (fcgi_gen_response(c, "</a>") == -1)
1795 if (fcgi_gen_response(c, "</div>\n") == -1)
1798 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1800 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1803 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1805 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1807 if (fcgi_gen_response(c, "&path=") == -1)
1809 if (fcgi_gen_response(c, qs->path) == -1)
1811 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1813 if (fcgi_gen_response(c, refname) == -1)
1815 if (fcgi_gen_response(c, "'>") == -1)
1817 if (fcgi_gen_response(c, "summary") == -1)
1819 if (fcgi_gen_response(c, "</a>") == -1)
1822 if (fcgi_gen_response(c, " | ") == -1)
1825 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1827 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1829 if (fcgi_gen_response(c, "&path=") == -1)
1831 if (fcgi_gen_response(c, qs->path) == -1)
1833 if (fcgi_gen_response(c, "&action=briefs&headref=") == -1)
1835 if (fcgi_gen_response(c, refname) == -1)
1837 if (fcgi_gen_response(c, "'>") == -1)
1839 if (fcgi_gen_response(c, "commit briefs") == -1)
1841 if (fcgi_gen_response(c, "</a>") == -1)
1844 if (fcgi_gen_response(c, " | ") == -1)
1847 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1849 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1851 if (fcgi_gen_response(c, "&path=") == -1)
1853 if (fcgi_gen_response(c, qs->path) == -1)
1855 if (fcgi_gen_response(c, "&action=commits&headref=") == -1)
1857 if (fcgi_gen_response(c, refname) == -1)
1859 if (fcgi_gen_response(c, "'>") == -1)
1861 if (fcgi_gen_response(c, "commits") == -1)
1863 if (fcgi_gen_response(c, "</a>") == -1)
1866 if (fcgi_gen_response(c, "</div>\n") == -1)
1868 if (fcgi_gen_response(c, "</div>\n") == -1)
1871 if (fcgi_gen_response(c,
1872 "<div id='dotted_line'></div>\n") == -1)
1879 fcgi_gen_response(c, "</div>\n");
1884 static const struct got_error *
1885 gotweb_render_tree(struct request *c)
1887 const struct got_error *error = NULL;
1888 struct transport *t = c->t;
1889 struct repo_commit *rc = NULL;
1892 error = got_get_repo_commits(c, 1);
1896 rc = TAILQ_FIRST(&t->repo_commits);
1898 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1902 if (fcgi_gen_response(c, "<div id='tree_title_wrapper'>\n") == -1)
1904 if (fcgi_gen_response(c, "<div id='tree_title'>Tree</div>\n") == -1)
1906 if (fcgi_gen_response(c, "</div>\n") == -1)
1909 if (fcgi_gen_response(c, "<div id='tree_content'>\n") == -1)
1912 if (fcgi_gen_response(c, "<div id='tree_header_wrapper'>\n") == -1)
1914 if (fcgi_gen_response(c, "<div id='tree_header'>\n") == -1)
1917 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
1920 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
1922 if (fcgi_gen_response(c, rc->tree_id) == -1)
1924 if (fcgi_gen_response(c, "</div>\n") == -1)
1927 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1930 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1932 if (fcgi_gen_response(c, age ? age : "") == -1)
1934 if (fcgi_gen_response(c, "</div>\n") == -1)
1937 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1940 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1942 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1944 if (fcgi_gen_response(c, "</div>\n") == -1)
1947 if (fcgi_gen_response(c, "</div>\n") == -1)
1949 if (fcgi_gen_response(c, "</div>\n") == -1)
1952 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1954 if (fcgi_gen_response(c, "<div id='tree'>\n") == -1)
1957 error = got_output_repo_tree(c);
1961 fcgi_gen_response(c, "</div>\n");
1962 fcgi_gen_response(c, "</div>\n");
1967 static const struct got_error *
1968 gotweb_render_diff(struct request *c)
1970 const struct got_error *error = NULL;
1971 struct transport *t = c->t;
1972 struct repo_commit *rc = NULL;
1973 char *age = NULL, *author = NULL;
1975 error = got_get_repo_commits(c, 1);
1979 rc = TAILQ_FIRST(&t->repo_commits);
1981 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1984 error = gotweb_escape_html(&author, rc->author);
1988 if (fcgi_gen_response(c, "<div id='diff_title_wrapper'>\n") == -1)
1990 if (fcgi_gen_response(c,
1991 "<div id='diff_title'>Commit Diff</div>\n") == -1)
1993 if (fcgi_gen_response(c, "</div>\n") == -1)
1996 if (fcgi_gen_response(c, "<div id='diff_content'>\n") == -1)
1998 if (fcgi_gen_response(c, "<div id='diff_header_wrapper'>\n") == -1)
2000 if (fcgi_gen_response(c, "<div id='diff_header'>\n") == -1)
2003 if (fcgi_gen_response(c, "<div id='header_diff_title'>Diff:"
2006 if (fcgi_gen_response(c, "<div id='header_diff'>") == -1)
2008 if (fcgi_gen_response(c, rc->parent_id) == -1)
2010 if (fcgi_gen_response(c, "<br />") == -1)
2012 if (fcgi_gen_response(c, rc->commit_id) == -1)
2014 if (fcgi_gen_response(c, "</div>\n") == -1)
2017 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2020 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2022 if (fcgi_gen_response(c, rc->commit_id) == -1)
2024 if (fcgi_gen_response(c, "</div>\n") == -1)
2027 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
2030 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
2032 if (fcgi_gen_response(c, rc->tree_id) == -1)
2034 if (fcgi_gen_response(c, "</div>\n") == -1)
2037 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
2040 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2042 if (fcgi_gen_response(c, author ? author : "") == -1)
2044 if (fcgi_gen_response(c, "</div>\n") == -1)
2047 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2050 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2052 if (fcgi_gen_response(c, age ? age : "") == -1)
2054 if (fcgi_gen_response(c, "</div>\n") == -1)
2057 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2060 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2062 if (fcgi_gen_response(c, rc->commit_msg) == -1)
2064 if (fcgi_gen_response(c, "</div>\n") == -1)
2066 if (fcgi_gen_response(c, "</div>\n") == -1)
2068 if (fcgi_gen_response(c, "</div>\n") == -1)
2071 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2073 if (fcgi_gen_response(c, "<div id='diff'>\n") == -1)
2076 error = got_output_repo_diff(c);
2080 fcgi_gen_response(c, "</div>\n");
2082 fcgi_gen_response(c, "</div>\n");
2088 static const struct got_error *
2089 gotweb_render_summary(struct request *c)
2091 const struct got_error *error = NULL;
2092 struct transport *t = c->t;
2093 struct server *srv = c->srv;
2095 if (fcgi_gen_response(c, "<div id='summary_wrapper'>\n") == -1)
2098 if (!srv->show_repo_description)
2101 if (fcgi_gen_response(c, "<div id='description_title'>"
2102 "Description:</div>\n") == -1)
2104 if (fcgi_gen_response(c, "<div id='description'>") == -1)
2106 if (fcgi_gen_response(c, t->repo_dir->description) == -1)
2108 if (fcgi_gen_response(c, "</div>\n") == -1)
2111 if (!srv->show_repo_owner)
2114 if (fcgi_gen_response(c, "<div id='repo_owner_title'>"
2115 "Owner:</div>\n") == -1)
2117 if (fcgi_gen_response(c, "<div id='repo_owner'>") == -1)
2119 if (fcgi_gen_response(c, t->repo_dir->owner) == -1)
2121 if (fcgi_gen_response(c, "</div>\n") == -1)
2124 if (!srv->show_repo_age)
2127 if (fcgi_gen_response(c, "<div id='last_change_title'>"
2128 "Last Change:</div>\n") == -1)
2130 if (fcgi_gen_response(c, "<div id='last_change'>") == -1)
2132 if (fcgi_gen_response(c, t->repo_dir->age) == -1)
2134 if (fcgi_gen_response(c, "</div>\n") == -1)
2137 if (!srv->show_repo_cloneurl)
2140 if (fcgi_gen_response(c, "<div id='cloneurl_title'>"
2141 "Clone URL:</div>\n") == -1)
2143 if (fcgi_gen_response(c, "<div id='cloneurl'>") == -1)
2145 if (fcgi_gen_response(c, t->repo_dir->url) == -1)
2147 if (fcgi_gen_response(c, "</div>\n") == -1)
2150 if (fcgi_gen_response(c, "</div>\n") == -1)
2152 if (fcgi_gen_response(c, "</div>\n") == -1)
2155 error = gotweb_render_briefs(c);
2157 log_warnx("%s: %s", __func__, error->msg);
2161 error = gotweb_render_tags(c);
2163 log_warnx("%s: %s", __func__, error->msg);
2167 error = gotweb_render_branches(c);
2169 log_warnx("%s: %s", __func__, error->msg);
2174 static const struct got_error *
2175 gotweb_render_tag(struct request *c)
2177 const struct got_error *error = NULL;
2178 struct repo_tag *rt = NULL;
2179 struct transport *t = c->t;
2180 char *age = NULL, *author = NULL;
2182 error = got_get_repo_tags(c, 1);
2186 if (t->tag_count == 0) {
2187 error = got_error_set_errno(GOT_ERR_BAD_OBJ_ID,
2192 rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
2194 error = gotweb_get_time_str(&age, rt->tagger_time, TM_LONG);
2197 error = gotweb_escape_html(&author, rt->tagger);
2201 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2203 if (fcgi_gen_response(c, "<div id='tags_title'>Tag</div>\n") == -1)
2205 if (fcgi_gen_response(c, "</div>\n") == -1)
2208 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2210 if (fcgi_gen_response(c, "<div id='tag_header_wrapper'>\n") == -1)
2212 if (fcgi_gen_response(c, "<div id='tag_header'>\n") == -1)
2215 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2218 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2220 if (fcgi_gen_response(c, rt->commit_id) == -1)
2223 if (strncmp(rt->tag_name, "refs/", 5) == 0)
2226 if (fcgi_gen_response(c, " <span id='refs_str'>(") == -1)
2228 if (fcgi_gen_response(c, rt->tag_name) == -1)
2230 if (fcgi_gen_response(c, ")</span>") == -1)
2233 if (fcgi_gen_response(c, "</div>\n") == -1)
2236 if (fcgi_gen_response(c, "<div id='header_author_title'>Tagger:"
2239 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2241 if (fcgi_gen_response(c, author ? author : "") == -1)
2243 if (fcgi_gen_response(c, "</div>\n") == -1)
2246 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2249 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2251 if (fcgi_gen_response(c, age ? age : "") == -1)
2253 if (fcgi_gen_response(c, "</div>\n") == -1)
2256 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2259 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2261 if (fcgi_gen_response(c, rt->commit_msg) == -1)
2263 if (fcgi_gen_response(c, "</div>\n") == -1)
2265 if (fcgi_gen_response(c, "</div>\n") == -1)
2268 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2270 if (fcgi_gen_response(c, "<div id='tag_commit'>\n") == -1)
2273 if (fcgi_gen_response(c, rt->tag_commit) == -1)
2276 if (fcgi_gen_response(c, "</div>\n") == -1)
2278 fcgi_gen_response(c, "</div>\n");
2285 static const struct got_error *
2286 gotweb_render_tags(struct request *c)
2288 const struct got_error *error = NULL;
2289 struct repo_tag *rt = NULL;
2290 struct server *srv = c->srv;
2291 struct transport *t = c->t;
2292 struct querystring *qs = t->qs;
2293 struct repo_dir *repo_dir = t->repo_dir;
2296 int commit_found = 0;
2298 if (qs->action == BRIEFS) {
2300 error = got_get_repo_tags(c, D_MAXSLCOMMDISP);
2302 error = got_get_repo_tags(c, srv->max_commits_display);
2306 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2308 if (fcgi_gen_response(c,
2309 "<div id='tags_title'>Tags</div>\n") == -1)
2311 if (fcgi_gen_response(c, "</div>\n") == -1)
2314 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2317 if (t->tag_count == 0) {
2318 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
2320 if (fcgi_gen_response(c,
2321 "This repository contains no tags\n") == -1)
2323 if (fcgi_gen_response(c, "</div>\n") == -1)
2325 if (fcgi_gen_response(c, "</div>\n") == -1)
2329 TAILQ_FOREACH(rt, &t->repo_tags, entry) {
2330 if (commit_found == 0 && qs->commit != NULL) {
2331 if (strcmp(qs->commit, rt->commit_id) != 0)
2336 error = gotweb_get_time_str(&age, rt->tagger_time, TM_DIFF);
2339 if (fcgi_gen_response(c, "<div id='tag_age'>") == -1)
2341 if (fcgi_gen_response(c, age ? age : "") == -1)
2343 if (fcgi_gen_response(c, "</div>\n") == -1)
2346 if (fcgi_gen_response(c, "<div id='tag'>") == -1)
2348 if (strncmp(rt->tag_name, "refs/tags/", 10) == 0)
2350 if (fcgi_gen_response(c, rt->tag_name) == -1)
2352 if (fcgi_gen_response(c, "</div>\n") == -1)
2355 if (fcgi_gen_response(c, "<div id='tags_log'>") == -1)
2357 if (rt->tag_commit != NULL) {
2358 newline = strchr(rt->tag_commit, '\n');
2363 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2365 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2367 if (fcgi_gen_response(c, "&path=") == -1)
2369 if (fcgi_gen_response(c, repo_dir->name) == -1)
2371 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2373 if (fcgi_gen_response(c, rt->commit_id) == -1)
2375 if (fcgi_gen_response(c, "'>") == -1)
2377 if (rt->tag_commit != NULL &&
2378 fcgi_gen_response(c, rt->tag_commit) == -1)
2380 if (fcgi_gen_response(c, "</a>") == -1)
2382 if (fcgi_gen_response(c, "</div>\n") == -1)
2385 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
2387 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
2390 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2392 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2394 if (fcgi_gen_response(c, "&path=") == -1)
2396 if (fcgi_gen_response(c, repo_dir->name) == -1)
2398 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2400 if (fcgi_gen_response(c, rt->commit_id) == -1)
2402 if (fcgi_gen_response(c, "'>") == -1)
2404 if (fcgi_gen_response(c, "tag") == -1)
2406 if (fcgi_gen_response(c, "</a>") == -1)
2409 if (fcgi_gen_response(c, " | ") == -1)
2412 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2414 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2416 if (fcgi_gen_response(c, "&path=") == -1)
2418 if (fcgi_gen_response(c, repo_dir->name) == -1)
2420 if (fcgi_gen_response(c, "&action=briefs&commit=") == -1)
2422 if (fcgi_gen_response(c, rt->commit_id) == -1)
2424 if (fcgi_gen_response(c, "'>") == -1)
2426 if (fcgi_gen_response(c, "commit briefs") == -1)
2428 if (fcgi_gen_response(c, "</a>") == -1)
2431 if (fcgi_gen_response(c, " | ") == -1)
2434 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2436 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2438 if (fcgi_gen_response(c, "&path=") == -1)
2440 if (fcgi_gen_response(c, repo_dir->name) == -1)
2442 if (fcgi_gen_response(c, "&action=commits&commit=") == -1)
2444 if (fcgi_gen_response(c, rt->commit_id) == -1)
2446 if (fcgi_gen_response(c, "'>") == -1)
2448 if (fcgi_gen_response(c, "commits") == -1)
2450 if (fcgi_gen_response(c, "</a>") == -1)
2453 if (fcgi_gen_response(c, "</div>\n") == -1)
2455 if (fcgi_gen_response(c, "</div>\n") == -1)
2457 if (fcgi_gen_response(c,
2458 "<div id='dotted_line'></div>\n") == -1)
2464 if (t->next_id || t->prev_id) {
2465 error = gotweb_render_navs(c);
2469 fcgi_gen_response(c, "</div>\n");
2475 const struct got_error *
2476 gotweb_escape_html(char **escaped_html, const char *orig_html)
2478 const struct got_error *error = NULL;
2479 struct escape_pair {
2490 size_t orig_len, len;
2493 orig_len = strlen(orig_html);
2495 for (i = 0; i < orig_len; i++) {
2496 for (j = 0; j < nitems(esc); j++) {
2497 if (orig_html[i] != esc[j].c)
2499 len += strlen(esc[j].s) - 1 /* escaped char */;
2503 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
2504 if (*escaped_html == NULL)
2505 return got_error_from_errno("calloc");
2508 for (i = 0; i < orig_len; i++) {
2510 for (j = 0; j < nitems(esc); j++) {
2511 if (orig_html[i] != esc[j].c)
2514 if (strlcat(*escaped_html, esc[j].s, len + 1)
2516 error = got_error(GOT_ERR_NO_SPACE);
2519 x += strlen(esc[j].s);
2524 (*escaped_html)[x] = orig_html[i];
2530 free(*escaped_html);
2531 *escaped_html = NULL;
2533 (*escaped_html)[x] = '\0';
2539 static const struct got_error *
2540 gotweb_load_got_path(struct request *c, struct repo_dir *repo_dir)
2542 const struct got_error *error = NULL;
2543 struct socket *sock = c->sock;
2544 struct server *srv = c->srv;
2545 struct transport *t = c->t;
2550 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2551 GOTWEB_GIT_DIR) == -1)
2552 return got_error_from_errno("asprintf");
2554 dt = opendir(dir_test);
2558 repo_dir->path = strdup(dir_test);
2559 if (repo_dir->path == NULL) {
2561 error = got_error_from_errno("strdup");
2568 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2569 GOTWEB_GOT_DIR) == -1) {
2571 error = got_error_from_errno("asprintf");
2575 dt = opendir(dir_test);
2580 error = got_error(GOT_ERR_NOT_GIT_REPO);
2584 if (asprintf(&dir_test, "%s/%s", srv->repos_path,
2585 repo_dir->name) == -1) {
2586 error = got_error_from_errno("asprintf");
2591 repo_dir->path = strdup(dir_test);
2592 if (repo_dir->path == NULL) {
2594 error = got_error_from_errno("strdup");
2598 dt = opendir(dir_test);
2600 error = got_error_path(repo_dir->name, GOT_ERR_NOT_GIT_REPO);
2605 error = got_repo_open(&t->repo, repo_dir->path, NULL, sock->pack_fds);
2608 error = gotweb_get_repo_description(&repo_dir->description, srv,
2612 error = got_get_repo_owner(&repo_dir->owner, c, repo_dir->path);
2615 error = got_get_repo_age(&repo_dir->age, c, repo_dir->path,
2619 error = gotweb_get_clone_url(&repo_dir->url, srv, repo_dir->path);
2623 if (dt != NULL && closedir(dt) == EOF && error == NULL)
2624 error = got_error_from_errno("closedir");
2628 static const struct got_error *
2629 gotweb_init_repo_dir(struct repo_dir **repo_dir, const char *dir)
2631 const struct got_error *error;
2633 *repo_dir = calloc(1, sizeof(**repo_dir));
2634 if (*repo_dir == NULL)
2635 return got_error_from_errno("calloc");
2637 if (asprintf(&(*repo_dir)->name, "%s", dir) == -1) {
2638 error = got_error_from_errno("asprintf");
2643 (*repo_dir)->owner = NULL;
2644 (*repo_dir)->description = NULL;
2645 (*repo_dir)->url = NULL;
2646 (*repo_dir)->age = NULL;
2647 (*repo_dir)->path = NULL;
2652 static const struct got_error *
2653 gotweb_get_repo_description(char **description, struct server *srv, char *dir)
2655 const struct got_error *error = NULL;
2657 char *d_file = NULL;
2661 *description = NULL;
2662 if (srv->show_repo_description == 0)
2665 if (asprintf(&d_file, "%s/description", dir) == -1)
2666 return got_error_from_errno("asprintf");
2668 f = fopen(d_file, "r");
2670 if (errno == ENOENT || errno == EACCES)
2672 error = got_error_from_errno2("fopen", d_file);
2676 if (fseek(f, 0, SEEK_END) == -1) {
2677 error = got_ferror(f, GOT_ERR_IO);
2682 error = got_ferror(f, GOT_ERR_IO);
2689 if (fseek(f, 0, SEEK_SET) == -1) {
2690 error = got_ferror(f, GOT_ERR_IO);
2693 *description = calloc(len + 1, sizeof(**description));
2694 if (*description == NULL) {
2695 error = got_error_from_errno("calloc");
2699 n = fread(*description, 1, len, f);
2700 if (n == 0 && ferror(f))
2701 error = got_ferror(f, GOT_ERR_IO);
2703 if (f != NULL && fclose(f) == EOF && error == NULL)
2704 error = got_error_from_errno("fclose");
2709 static const struct got_error *
2710 gotweb_get_clone_url(char **url, struct server *srv, char *dir)
2712 const struct got_error *error = NULL;
2714 char *d_file = NULL;
2720 if (srv->show_repo_cloneurl == 0)
2723 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2724 return got_error_from_errno("asprintf");
2726 f = fopen(d_file, "r");
2728 if (errno != ENOENT && errno != EACCES)
2729 error = got_error_from_errno2("fopen", d_file);
2733 if (fseek(f, 0, SEEK_END) == -1) {
2734 error = got_ferror(f, GOT_ERR_IO);
2739 error = got_ferror(f, GOT_ERR_IO);
2745 if (fseek(f, 0, SEEK_SET) == -1) {
2746 error = got_ferror(f, GOT_ERR_IO);
2750 *url = calloc(len + 1, sizeof(**url));
2752 error = got_error_from_errno("calloc");
2756 n = fread(*url, 1, len, f);
2757 if (n == 0 && ferror(f))
2758 error = got_ferror(f, GOT_ERR_IO);
2760 if (f != NULL && fclose(f) == EOF && error == NULL)
2761 error = got_error_from_errno("fclose");
2766 const struct got_error *
2767 gotweb_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2771 const char *years = "years ago", *months = "months ago";
2772 const char *weeks = "weeks ago", *days = "days ago";
2773 const char *hours = "hours ago", *minutes = "minutes ago";
2774 const char *seconds = "seconds ago", *now = "right now";
2782 diff_time = time(NULL) - committer_time;
2783 if (diff_time > 60 * 60 * 24 * 365 * 2) {
2784 if (asprintf(repo_age, "%lld %s",
2785 (diff_time / 60 / 60 / 24 / 365), years) == -1)
2786 return got_error_from_errno("asprintf");
2787 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
2788 if (asprintf(repo_age, "%lld %s",
2789 (diff_time / 60 / 60 / 24 / (365 / 12)),
2791 return got_error_from_errno("asprintf");
2792 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
2793 if (asprintf(repo_age, "%lld %s",
2794 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
2795 return got_error_from_errno("asprintf");
2796 } else if (diff_time > 60 * 60 * 24 * 2) {
2797 if (asprintf(repo_age, "%lld %s",
2798 (diff_time / 60 / 60 / 24), days) == -1)
2799 return got_error_from_errno("asprintf");
2800 } else if (diff_time > 60 * 60 * 2) {
2801 if (asprintf(repo_age, "%lld %s",
2802 (diff_time / 60 / 60), hours) == -1)
2803 return got_error_from_errno("asprintf");
2804 } else if (diff_time > 60 * 2) {
2805 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
2807 return got_error_from_errno("asprintf");
2808 } else if (diff_time > 2) {
2809 if (asprintf(repo_age, "%lld %s", diff_time,
2811 return got_error_from_errno("asprintf");
2813 if (asprintf(repo_age, "%s", now) == -1)
2814 return got_error_from_errno("asprintf");
2818 if (gmtime_r(&committer_time, &tm) == NULL)
2819 return got_error_from_errno("gmtime_r");
2821 s = asctime_r(&tm, datebuf);
2823 return got_error_from_errno("asctime_r");
2825 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2826 return got_error_from_errno("asprintf");