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>
34 #include "got_error.h"
35 #include "got_object.h"
36 #include "got_reference.h"
37 #include "got_repository.h"
39 #include "got_cancel.h"
40 #include "got_worktree.h"
42 #include "got_commit_graph.h"
43 #include "got_blame.h"
44 #include "got_privsep.h"
49 #include "got_compat.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 = got_output_file_blob(c);
182 log_warnx("%s: %s", __func__, error->msg);
188 error = gotweb_render_content_type(c, "text/html");
190 log_warnx("%s: %s", __func__, error->msg);
196 error = gotweb_render_header(c);
198 log_warnx("%s: %s", __func__, error->msg);
209 error = gotweb_render_blame(c);
211 log_warnx("%s: %s", __func__, error->msg);
216 error = gotweb_render_briefs(c);
218 log_warnx("%s: %s", __func__, error->msg);
223 error = gotweb_render_commits(c);
225 log_warnx("%s: %s", __func__, error->msg);
230 error = gotweb_render_diff(c);
232 log_warnx("%s: %s", __func__, error->msg);
237 error = gotweb_render_index(c);
239 log_warnx("%s: %s", __func__, error->msg);
244 error = gotweb_render_summary(c);
246 log_warnx("%s: %s", __func__, error->msg);
251 error = gotweb_render_tag(c);
253 log_warnx("%s: %s", __func__, error->msg);
258 error = gotweb_render_tags(c);
260 log_warnx("%s: %s", __func__, error->msg);
265 error = gotweb_render_tree(c);
267 log_warnx("%s: %s", __func__, error->msg);
273 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
275 if (fcgi_gen_response(c, "Error: Bad Querystring\n") == -1)
277 if (fcgi_gen_response(c, "</div>\n") == -1)
284 if (html && fcgi_gen_response(c, "<div id='err_content'>") == -1)
286 if (fcgi_gen_response(c, err) == -1)
289 if (fcgi_gen_response(c, (uint8_t *)error->msg) == -1)
292 if (fcgi_gen_response(c, "see daemon logs for details") == -1)
295 if (html && fcgi_gen_response(c, "</div>\n") == -1)
298 if (c->t->repo != NULL && qs->action != INDEX)
299 got_repo_close(c->t->repo);
300 if (html && srv != NULL)
301 gotweb_render_footer(c);
305 gotweb_get_server(uint8_t *server_name, uint8_t *document_root,
308 struct server *srv = NULL;
310 /* check against document_root first */
311 if (strlen(server_name) > 0)
312 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
313 if (strcmp(srv->name, server_name) == 0)
316 /* check against document_root second */
317 if (strlen(document_root) > 0)
318 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
319 if (strcmp(srv->name, document_root) == 0)
322 /* check against subdomain third */
323 if (strlen(subdomain) > 0)
324 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
325 if (strcmp(srv->name, subdomain) == 0)
328 /* if those fail, send first server */
329 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
336 const struct got_error *
337 gotweb_init_transport(struct transport **t)
339 const struct got_error *error = NULL;
341 *t = calloc(1, sizeof(**t));
343 return got_error_from_errno2("%s: calloc", __func__);
345 TAILQ_INIT(&(*t)->repo_commits);
346 TAILQ_INIT(&(*t)->repo_tags);
349 (*t)->repo_dir = NULL;
351 (*t)->next_id = NULL;
352 (*t)->prev_id = NULL;
359 static const struct got_error *
360 gotweb_init_querystring(struct querystring **qs)
362 const struct got_error *error = NULL;
364 *qs = calloc(1, sizeof(**qs));
366 return got_error_from_errno2("%s: calloc", __func__);
368 (*qs)->action = INDEX;
369 (*qs)->commit = NULL;
371 (*qs)->folder = NULL;
372 (*qs)->headref = strdup("HEAD");
373 if ((*qs)->headref == NULL) {
374 return got_error_from_errno2("%s: strdup", __func__);
376 (*qs)->index_page = 0;
377 (*qs)->index_page_str = NULL;
383 static const struct got_error *
384 gotweb_parse_querystring(struct querystring **qs, char *qst)
386 const struct got_error *error = NULL;
387 char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL;
388 char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL;
395 return got_error_from_errno2("%s: strdup", __func__);
400 while (tok1_pair != NULL) {
401 strsep(&tok1_end, "&");
403 tok2 = strdup(tok1_pair);
406 return got_error_from_errno2("%s: strdup", __func__);
412 while (tok2_pair != NULL) {
413 strsep(&tok2_end, "=");
415 error = gotweb_assign_querystring(qs, tok2_pair,
420 tok2_pair = tok2_end;
423 tok1_pair = tok1_end;
433 static const struct got_error *
434 gotweb_assign_querystring(struct querystring **qs, char *key, char *value)
436 const struct got_error *error = NULL;
440 for (el_cnt = 0; el_cnt < QSELEM__MAX; el_cnt++) {
441 if (strcmp(key, querystring_keys[el_cnt].name) != 0)
444 switch (querystring_keys[el_cnt].element) {
446 for (a_cnt = 0; a_cnt < ACTIONS__MAX; a_cnt++) {
447 if (strcmp(value, action_keys[a_cnt].name) != 0)
449 else if (strcmp(value,
450 action_keys[a_cnt].name) == 0){
452 action_keys[a_cnt].action;
460 (*qs)->commit = strdup(value);
461 if ((*qs)->commit == NULL) {
462 error = got_error_from_errno2("%s: strdup",
468 (*qs)->file = strdup(value);
469 if ((*qs)->file == NULL) {
470 error = got_error_from_errno2("%s: strdup",
476 (*qs)->folder = strdup(value);
477 if ((*qs)->folder == NULL) {
478 error = got_error_from_errno2("%s: strdup",
484 (*qs)->headref = strdup(value);
485 if ((*qs)->headref == NULL) {
486 error = got_error_from_errno2("%s: strdup",
492 if (strlen(value) == 0)
494 (*qs)->index_page_str = strdup(value);
495 if ((*qs)->index_page_str == NULL) {
496 error = got_error_from_errno2("%s: strdup",
500 (*qs)->index_page = strtonum(value, INT64_MIN,
503 error = got_error_from_errno3("%s: strtonum %s",
507 if ((*qs)->index_page < 0) {
508 (*qs)->index_page = 0;
509 sprintf((*qs)->index_page_str, "%d", 0);
513 (*qs)->path = strdup(value);
514 if ((*qs)->path == NULL) {
515 error = got_error_from_errno2("%s: strdup",
521 if (strlen(value) == 0)
523 (*qs)->page_str = strdup(value);
524 if ((*qs)->page_str == NULL) {
525 error = got_error_from_errno2("%s: strdup",
529 (*qs)->page = strtonum(value, INT64_MIN,
532 error = got_error_from_errno3("%s: strtonum %s",
536 if ((*qs)->page < 0) {
538 sprintf((*qs)->page_str, "%d", 0);
550 gotweb_free_repo_tag(struct repo_tag *rt)
553 free(rt->commit_msg);
561 gotweb_free_repo_commit(struct repo_commit *rc)
571 free(rc->commit_msg);
577 gotweb_free_querystring(struct querystring *qs)
584 free(qs->index_page_str);
592 gotweb_free_repo_dir(struct repo_dir *repo_dir)
594 if (repo_dir != NULL) {
595 free(repo_dir->name);
596 free(repo_dir->owner);
597 free(repo_dir->description);
600 free(repo_dir->path);
606 gotweb_free_transport(struct transport *t)
608 struct repo_commit *rc = NULL, *trc = NULL;
609 struct repo_tag *rt = NULL, *trt = NULL;
611 TAILQ_FOREACH_SAFE(rc, &t->repo_commits, entry, trc) {
612 TAILQ_REMOVE(&t->repo_commits, rc, entry);
613 gotweb_free_repo_commit(rc);
615 TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
616 TAILQ_REMOVE(&t->repo_tags, rt, entry);
617 gotweb_free_repo_tag(rt);
619 gotweb_free_repo_dir(t->repo_dir);
620 gotweb_free_querystring(t->qs);
628 const struct got_error *
629 gotweb_render_content_type(struct request *c, const uint8_t *type)
631 const struct got_error *error = NULL;
634 if (asprintf(&h, "Content-type: %s\r\n\r\n", type) == -1) {
635 error = got_error_from_errno2("%s: asprintf", __func__);
639 fcgi_gen_response(c, h);
646 const struct got_error *
647 gotweb_render_content_type_file(struct request *c, const uint8_t *type,
650 const struct got_error *error = NULL;
653 if (asprintf(&h, "Content-type: %s\r\n"
654 "Content-disposition: attachment; filename=%s\r\n\r\n",
656 error = got_error_from_errno2("%s: asprintf", __func__);
660 fcgi_gen_response(c, h);
667 static const struct got_error *
668 gotweb_render_header(struct request *c)
670 const struct got_error *error = NULL;
671 struct server *srv = c->srv;
672 struct querystring *qs = c->t->qs;
673 char *title = NULL, *droot = NULL, *css = NULL, *gotlink = NULL;
674 char *gotimg = NULL, *sitelink = NULL, *summlink = NULL;
676 if (strlen(c->document_root) > 0) {
677 if (asprintf(&droot, "/%s/", c->document_root) == -1) {
678 error = got_error_from_errno2("%s: asprintf", __func__);
682 if (asprintf(&droot, "/") == -1) {
683 error = got_error_from_errno2("%s: asprintf", __func__);
688 if (asprintf(&title, "<title>%s</title>\n", srv->site_name) == -1) {
689 error = got_error_from_errno2("%s: asprintf", __func__);
693 "<link rel='stylesheet' type='text/css' href='%s%s'/>\n",
694 droot, srv->custom_css) == -1) {
695 error = got_error_from_errno2("%s: asprintf", __func__);
698 if (asprintf(&gotlink, "<a href='%s' target='_sotd'>",
699 srv->logo_url) == -1) {
700 error = got_error_from_errno2("%s: asprintf", __func__);
703 if (asprintf(&gotimg, "<img src='%s%s' alt='logo' id='logo'/></a>",
704 droot, srv->logo) == -1) {
705 error = got_error_from_errno2("%s: asprintf", __func__);
708 if (asprintf(&sitelink, "<a href='/%s?index_page=%d' "
709 "alt='sitelink'>%s</a>", c->document_root, qs->index_page,
710 srv->site_link) == -1) {
711 error = got_error_from_errno2("%s: asprintf", __func__);
714 if (asprintf(&summlink, "<a href='/%s?index_page=%d&path=%s"
715 "&action=summary' alt='summlink'>%s</a>", c->document_root,
716 qs->index_page, qs->path, qs->path) == -1) {
717 error = got_error_from_errno2("%s: asprintf", __func__);
721 if (fcgi_gen_response(c, "<!DOCTYPE html>\n<head>\n") == -1)
723 if (fcgi_gen_response(c, title) == -1)
725 if (fcgi_gen_response(c, "<meta name='viewport' "
726 "content='initial-scale=.75, user-scalable=yes'/>\n") == -1)
728 if (fcgi_gen_response(c, "<meta charset='utf-8'/>\n") == -1)
730 if (fcgi_gen_response(c, "<meta name='msapplication-TileColor' "
731 "content='#da532c'/>\n") == -1)
733 if (fcgi_gen_response(c,
734 "<meta name='theme-color' content='#ffffff'/>\n") == -1)
736 if (fcgi_gen_response(c, "<link rel='apple-touch-icon' sizes='180x180' "
737 "href='/apple-touch-icon.png'/>\n") == -1)
739 if (fcgi_gen_response(c,
740 "<link rel='icon' type='image/png' sizes='32x32' "
741 "href='/favicon-32x32.png'/>\n") == -1)
743 if (fcgi_gen_response(c, "<link rel='icon' type='image/png' "
744 "sizes='16x16' href='/favicon-16x16.png'/>\n") == -1)
746 if (fcgi_gen_response(c, "<link rel='manifest' "
747 "href='/site.webmanifest'/>\n") == -1)
749 if (fcgi_gen_response(c, "<link rel='mask-icon' "
750 "href='/safari-pinned-tab.svg'/>\n") == -1)
752 if (fcgi_gen_response(c, css) == -1)
754 if (fcgi_gen_response(c, "</head>\n<body>\n<div id='gw_body'>\n") == -1)
756 if (fcgi_gen_response(c,
757 "<div id='header'>\n<div id='got_link'>") == -1)
759 if (fcgi_gen_response(c, gotlink) == -1)
761 if (fcgi_gen_response(c, gotimg) == -1)
763 if (fcgi_gen_response(c, "</div>\n</div>\n") == -1)
765 if (fcgi_gen_response(c,
766 "<div id='site_path'>\n<div id='site_link'>") == -1)
768 if (fcgi_gen_response(c, sitelink) == -1)
771 if (qs->path != NULL) {
772 if (fcgi_gen_response(c, " / ") == -1)
774 if (fcgi_gen_response(c, summlink) == -1)
777 if (qs->action != INDEX) {
778 if (fcgi_gen_response(c, " / ") == -1)
782 if (fcgi_gen_response(c, "blame") == -1)
786 if (fcgi_gen_response(c, "briefs") == -1)
790 if (fcgi_gen_response(c, "commits") == -1)
794 if (fcgi_gen_response(c, "diff") == -1)
798 if (fcgi_gen_response(c, "summary") == -1)
802 if (fcgi_gen_response(c, "tag") == -1)
806 if (fcgi_gen_response(c, "tags") == -1)
810 if (fcgi_gen_response(c, "tree") == -1)
819 fcgi_gen_response(c, "</div>\n</div>\n<div id='content'>\n");
832 static const struct got_error *
833 gotweb_render_footer(struct request *c)
835 const struct got_error *error = NULL;
836 struct server *srv = c->srv;
837 char *siteowner = NULL;
839 if (fcgi_gen_response(c, "<div id='site_owner_wrapper'>\n") == -1)
841 if (fcgi_gen_response(c, "<div id='site_owner'>") == -1)
843 if (srv->show_site_owner) {
844 error = gotweb_escape_html(&siteowner, srv->site_owner);
847 if (fcgi_gen_response(c, siteowner) == -1)
850 if (fcgi_gen_response(c, " ") == -1)
852 fcgi_gen_response(c, "</div>\n</div>\n</div>\n</body>\n</html>");
859 static const struct got_error *
860 gotweb_render_navs(struct request *c)
862 const struct got_error *error = NULL;
863 struct transport *t = c->t;
864 struct querystring *qs = t->qs;
865 struct server *srv = c->srv;
866 char *nhref = NULL, *phref = NULL;
869 if (fcgi_gen_response(c, "<div id='np_wrapper'>\n") == -1)
871 if (fcgi_gen_response(c, "<div id='nav_prev'>") == -1)
876 if (qs->index_page > 0) {
877 if (asprintf(&phref, "index_page=%d",
878 qs->index_page - 1) == -1) {
879 error = got_error_from_errno2("%s: asprintf",
887 if (t->prev_id && qs->commit != NULL &&
888 strcmp(qs->commit, t->prev_id) != 0) {
889 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
890 "&action=briefs&commit=%s&headref=%s",
891 qs->index_page, qs->path, qs->page - 1, t->prev_id,
892 qs->headref) == -1) {
893 error = got_error_from_errno2("%s: asprintf",
901 if (t->prev_id && qs->commit != NULL &&
902 strcmp(qs->commit, t->prev_id) != 0) {
903 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
904 "&action=commits&commit=%s&headref=%s&folder=%s"
906 qs->index_page, qs->path, qs->page - 1, t->prev_id,
907 qs->headref, qs->folder ? qs->folder : "",
908 qs->file ? qs->file : "") == -1) {
909 error = got_error_from_errno2("%s: asprintf",
917 if (t->prev_id && qs->commit != NULL &&
918 strcmp(qs->commit, t->prev_id) != 0) {
919 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
920 "&action=tags&commit=%s&headref=%s",
921 qs->index_page, qs->path, qs->page - 1, t->prev_id,
922 qs->headref) == -1) {
923 error = got_error_from_errno2("%s: asprintf",
936 if (fcgi_gen_response(c, "<a href='?") == -1)
938 if (fcgi_gen_response(c, phref) == -1)
940 if (fcgi_gen_response(c, "'>Previous</a>") == -1)
943 if (fcgi_gen_response(c, "</div>\n") == -1)
945 if (fcgi_gen_response(c, "<div id='nav_next'>") == -1)
952 if (t->next_disp == srv->max_repos_display &&
953 t->repos_total != (qs->index_page + 1) *
954 srv->max_repos_display) {
955 if (asprintf(&nhref, "index_page=%d",
956 qs->index_page + 1) == -1) {
957 error = got_error_from_errno2("%s: asprintf",
966 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
967 "&action=briefs&commit=%s&headref=%s",
968 qs->index_page, qs->path, qs->page + 1, t->next_id,
969 qs->headref) == -1) {
970 error = got_error_from_errno2("%s: asprintf",
979 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
980 "&action=commits&commit=%s&headref=%s&folder=%s"
982 qs->index_page, qs->path, qs->page + 1, t->next_id,
983 qs->headref, qs->folder ? qs->folder : "",
984 qs->file ? qs->file : "") == -1) {
985 error = got_error_from_errno2("%s: asprintf",
994 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
995 "&action=tags&commit=%s&headref=%s",
996 qs->index_page, qs->path, qs->page + 1, t->next_id,
997 qs->headref) == -1) {
998 error = got_error_from_errno2("%s: asprintf",
1010 if (fcgi_gen_response(c, "<a href='?") == -1)
1012 if (fcgi_gen_response(c, nhref) == -1)
1014 if (fcgi_gen_response(c, "'>Next</a>") == -1)
1017 fcgi_gen_response(c, "</div>\n");
1028 static const struct got_error *
1029 gotweb_render_index(struct request *c)
1031 const struct got_error *error = NULL;
1032 struct server *srv = c->srv;
1033 struct transport *t = c->t;
1034 struct querystring *qs = t->qs;
1035 struct repo_dir *repo_dir = NULL;
1037 struct dirent **sd_dent;
1038 char *c_path = NULL;
1040 unsigned int d_cnt, d_i, d_disp = 0;
1042 d = opendir(srv->repos_path);
1044 error = got_error_from_errno2("opendir", srv->repos_path);
1048 d_cnt = scandir(srv->repos_path, &sd_dent, NULL, alphasort);
1050 error = got_error_from_errno2("scandir", srv->repos_path);
1054 /* get total count of repos */
1055 for (d_i = 0; d_i < d_cnt; d_i++) {
1056 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1057 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1060 if (asprintf(&c_path, "%s/%s", srv->repos_path,
1061 sd_dent[d_i]->d_name) == -1) {
1062 error = got_error_from_errno("asprintf");
1066 if (lstat(c_path, &st) == 0 && S_ISDIR(st.st_mode) &&
1067 !got_path_dir_is_empty(c_path))
1073 if (fcgi_gen_response(c, "<div id='index_header'>\n") == -1)
1075 if (fcgi_gen_response(c,
1076 "<div id='index_header_project'>Project</div>\n") == -1)
1078 if (srv->show_repo_description)
1079 if (fcgi_gen_response(c, "<div id='index_header_description'>"
1080 "Description</div>\n") == -1)
1082 if (srv->show_repo_owner)
1083 if (fcgi_gen_response(c, "<div id='index_header_owner'>"
1084 "Owner</div>\n") == -1)
1086 if (srv->show_repo_age)
1087 if (fcgi_gen_response(c, "<div id='index_header_age'>"
1088 "Last Change</div>\n") == -1)
1090 if (fcgi_gen_response(c, "</div>\n") == -1)
1093 for (d_i = 0; d_i < d_cnt; d_i++) {
1094 if (srv->max_repos > 0 && (d_i - 2) == srv->max_repos)
1095 break; /* account for parent and self */
1097 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1098 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1101 if (qs->index_page > 0 && (qs->index_page *
1102 srv->max_repos_display) > t->prev_disp) {
1107 error = gotweb_init_repo_dir(&repo_dir, sd_dent[d_i]->d_name);
1111 error = gotweb_load_got_path(c, repo_dir);
1112 if (error && error->code == GOT_ERR_NOT_GIT_REPO) {
1116 else if (error && error->code != GOT_ERR_LONELY_PACKIDX)
1119 if (lstat(repo_dir->path, &st) == 0 &&
1120 S_ISDIR(st.st_mode) &&
1121 !got_path_dir_is_empty(repo_dir->path))
1124 gotweb_free_repo_dir(repo_dir);
1131 if (fcgi_gen_response(c, "<div id='index_wrapper'>\n") == -1)
1133 if (fcgi_gen_response(c, "<div id='index_project'>") == -1)
1136 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1138 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1140 if (fcgi_gen_response(c, "&path=") == -1)
1142 if (fcgi_gen_response(c, repo_dir->name) == -1)
1144 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1146 if (fcgi_gen_response(c, repo_dir->name) == -1)
1148 if (fcgi_gen_response(c, "</a>") == -1)
1151 if (fcgi_gen_response(c, "</div>\n") == -1)
1154 if (srv->show_repo_description) {
1155 if (fcgi_gen_response(c,
1156 "<div id='index_project_description'>\n") == -1)
1158 if (fcgi_gen_response(c, repo_dir->description) == -1)
1160 if (fcgi_gen_response(c, "</div>\n") == -1)
1164 if (srv->show_repo_owner) {
1165 if (fcgi_gen_response(c,
1166 "<div id='index_project_owner'>") == -1)
1168 if (fcgi_gen_response(c, repo_dir->owner) == -1)
1170 if (fcgi_gen_response(c, "</div>\n") == -1)
1174 if (srv->show_repo_age) {
1175 if (fcgi_gen_response(c,
1176 "<div id='index_project_age'>") == -1)
1178 if (fcgi_gen_response(c, repo_dir->age) == -1)
1180 if (fcgi_gen_response(c, "</div>\n") == -1)
1184 if (fcgi_gen_response(c, "<div id='navs_wrapper'>") == -1)
1186 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1189 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1191 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1193 if (fcgi_gen_response(c, "&path=") == -1)
1195 if (fcgi_gen_response(c, repo_dir->name) == -1)
1197 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1199 if (fcgi_gen_response(c, "summary") == -1)
1201 if (fcgi_gen_response(c, "</a> | ") == -1)
1204 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1206 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1208 if (fcgi_gen_response(c, "&path=") == -1)
1210 if (fcgi_gen_response(c, repo_dir->name) == -1)
1212 if (fcgi_gen_response(c, "&action=briefs'>") == -1)
1214 if (fcgi_gen_response(c, "commit briefs") == -1)
1216 if (fcgi_gen_response(c, "</a> | ") == -1)
1219 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1221 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1223 if (fcgi_gen_response(c, "&path=") == -1)
1225 if (fcgi_gen_response(c, repo_dir->name) == -1)
1227 if (fcgi_gen_response(c, "&action=commits'>") == -1)
1229 if (fcgi_gen_response(c, "commits") == -1)
1231 if (fcgi_gen_response(c, "</a> | ") == -1)
1234 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1236 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1238 if (fcgi_gen_response(c, "&path=") == -1)
1240 if (fcgi_gen_response(c, repo_dir->name) == -1)
1242 if (fcgi_gen_response(c, "&action=tags'>") == -1)
1244 if (fcgi_gen_response(c, "tags") == -1)
1246 if (fcgi_gen_response(c, "</a> | ") == -1)
1249 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1251 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1253 if (fcgi_gen_response(c, "&path=") == -1)
1255 if (fcgi_gen_response(c, repo_dir->name) == -1)
1257 if (fcgi_gen_response(c, "&action=tree'>") == -1)
1259 if (fcgi_gen_response(c, "tree") == -1)
1261 if (fcgi_gen_response(c, "</a>") == -1)
1264 if (fcgi_gen_response(c, "</div>") == -1)
1266 if (fcgi_gen_response(c,
1267 "<div id='dotted_line'></div>\n") == -1)
1269 if (fcgi_gen_response(c, "</div>\n") == -1)
1271 if (fcgi_gen_response(c, "</div>\n") == -1)
1274 gotweb_free_repo_dir(repo_dir);
1276 error = got_repo_close(t->repo);
1280 if (d_disp == srv->max_repos_display)
1283 if (srv->max_repos_display == 0)
1285 if (srv->max_repos > 0 && srv->max_repos < srv->max_repos_display)
1287 if (t->repos_total <= srv->max_repos ||
1288 t->repos_total <= srv->max_repos_display)
1291 error = gotweb_render_navs(c);
1295 fcgi_gen_response(c, "</div>\n");
1297 if (d != NULL && closedir(d) == EOF && error == NULL)
1298 error = got_error_from_errno("closedir");
1302 static const struct got_error *
1303 gotweb_render_blame(struct request *c)
1305 const struct got_error *error = NULL;
1306 struct transport *t = c->t;
1307 struct repo_commit *rc = NULL;
1310 error = got_get_repo_commits(c, 1);
1314 rc = TAILQ_FIRST(&t->repo_commits);
1316 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1320 if (fcgi_gen_response(c, "<div id='blame_title_wrapper'>\n") == -1)
1322 if (fcgi_gen_response(c, "<div id='blame_title'>Blame</div>\n") == -1)
1324 if (fcgi_gen_response(c, "</div>\n") == -1)
1327 if (fcgi_gen_response(c, "<div id='blame_content'>\n") == -1)
1330 if (fcgi_gen_response(c, "<div id='blame_header_wrapper'>\n") == -1)
1332 if (fcgi_gen_response(c, "<div id='blame_header'>\n") == -1)
1335 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1338 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1340 if (fcgi_gen_response(c, age ? age : "") == -1)
1342 if (fcgi_gen_response(c, "</div>\n") == -1)
1345 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1348 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1350 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1352 if (fcgi_gen_response(c, "</div>\n") == -1)
1355 if (fcgi_gen_response(c, "</div>\n") == -1)
1357 if (fcgi_gen_response(c, "</div>\n") == -1)
1360 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1362 if (fcgi_gen_response(c, "<div id='blame'>\n") == -1)
1365 error = got_output_file_blame(c);
1369 fcgi_gen_response(c, "</div>\n");
1371 fcgi_gen_response(c, "</div>\n");
1375 static const struct got_error *
1376 gotweb_render_briefs(struct request *c)
1378 const struct got_error *error = NULL;
1379 struct repo_commit *rc = NULL;
1380 struct server *srv = c->srv;
1381 struct transport *t = c->t;
1382 struct querystring *qs = t->qs;
1383 struct repo_dir *repo_dir = t->repo_dir;
1384 char *smallerthan, *newline;
1387 if (fcgi_gen_response(c, "<div id='briefs_title_wrapper'>\n") == -1)
1389 if (fcgi_gen_response(c,
1390 "<div id='briefs_title'>Commit Briefs</div>\n") == -1)
1392 if (fcgi_gen_response(c, "</div>\n") == -1)
1395 if (fcgi_gen_response(c, "<div id='briefs_content'>\n") == -1)
1398 if (qs->action == SUMMARY) {
1399 qs->action = BRIEFS;
1400 error = got_get_repo_commits(c, D_MAXSLCOMMDISP);
1402 error = got_get_repo_commits(c, srv->max_commits_display);
1406 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1407 error = gotweb_get_time_str(&age, rc->committer_time, TM_DIFF);
1410 if (fcgi_gen_response(c, "<div id='briefs_age'>") == -1)
1412 if (fcgi_gen_response(c, age ? age : "") == -1)
1414 if (fcgi_gen_response(c, "</div>\n") == -1)
1417 if (fcgi_gen_response(c, "<div id='briefs_author'>") == -1)
1419 smallerthan = strchr(rc->author, '<');
1421 *smallerthan = '\0';
1422 if (fcgi_gen_response(c, rc->author) == -1)
1424 if (fcgi_gen_response(c, "</div>\n") == -1)
1427 if (fcgi_gen_response(c, "<div id='briefs_log'>") == -1)
1429 newline = strchr(rc->commit_msg, '\n');
1433 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1435 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1437 if (fcgi_gen_response(c, "&path=") == -1)
1439 if (fcgi_gen_response(c, repo_dir->name) == -1)
1441 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1443 if (fcgi_gen_response(c, rc->commit_id) == -1)
1445 if (fcgi_gen_response(c, "&headref=") == -1)
1447 if (fcgi_gen_response(c, qs->headref) == -1)
1449 if (fcgi_gen_response(c, "'>") == -1)
1451 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1453 if (fcgi_gen_response(c, "</a>") == -1)
1456 if (fcgi_gen_response(c,
1457 " <span id='refs_str'>(") == -1)
1459 if (fcgi_gen_response(c, rc->refs_str) == -1)
1461 if (fcgi_gen_response(c, ")</span>") == -1)
1464 if (fcgi_gen_response(c, "</div>\n") == -1)
1467 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1469 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1471 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1473 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1475 if (fcgi_gen_response(c, "&path=") == -1)
1477 if (fcgi_gen_response(c, repo_dir->name) == -1)
1479 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1481 if (fcgi_gen_response(c, rc->commit_id) == -1)
1483 if (fcgi_gen_response(c, "&headref=") == -1)
1485 if (fcgi_gen_response(c, qs->headref) == -1)
1487 if (fcgi_gen_response(c, "'>") == -1)
1489 if (fcgi_gen_response(c, "diff") == -1)
1491 if (fcgi_gen_response(c, "</a>") == -1)
1494 if (fcgi_gen_response(c, " | ") == -1)
1497 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1499 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1501 if (fcgi_gen_response(c, "&path=") == -1)
1503 if (fcgi_gen_response(c, repo_dir->name) == -1)
1505 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1507 if (fcgi_gen_response(c, rc->commit_id) == -1)
1509 if (fcgi_gen_response(c, "&headref=") == -1)
1511 if (fcgi_gen_response(c, qs->headref) == -1)
1513 if (fcgi_gen_response(c, "'>") == -1)
1515 if (fcgi_gen_response(c, "tree") == -1)
1517 if (fcgi_gen_response(c, "</a>") == -1)
1519 if (fcgi_gen_response(c, "</div>\n") == -1)
1521 if (fcgi_gen_response(c, "</div>\n") == -1)
1523 if (fcgi_gen_response(c,
1524 "<div id='dotted_line'></div>\n") == -1)
1531 if (t->next_id || t->prev_id) {
1532 error = gotweb_render_navs(c);
1536 fcgi_gen_response(c, "</div>\n");
1542 static const struct got_error *
1543 gotweb_render_commits(struct request *c)
1545 const struct got_error *error = NULL;
1546 struct repo_commit *rc = NULL;
1547 struct server *srv = c->srv;
1548 struct transport *t = c->t;
1549 struct querystring *qs = t->qs;
1550 struct repo_dir *repo_dir = t->repo_dir;
1551 char *age = NULL, *author = NULL;
1552 /* int commit_found = 0; */
1554 if (fcgi_gen_response(c, "<div id='commits_title_wrapper'>\n") == -1)
1556 if (fcgi_gen_response(c,
1557 "<div id='commits_title'>Commits</div>\n") == -1)
1559 if (fcgi_gen_response(c, "</div>\n") == -1)
1562 if (fcgi_gen_response(c, "<div id='commits_content'>\n") == -1)
1565 error = got_get_repo_commits(c, srv->max_commits_display);
1569 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1570 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1573 error = gotweb_escape_html(&author, rc->author);
1577 if (fcgi_gen_response(c,
1578 "<div id='commits_header_wrapper'>\n") == -1)
1580 if (fcgi_gen_response(c, "<div id='commits_header'>\n") == -1)
1584 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
1587 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
1589 if (fcgi_gen_response(c, rc->commit_id) == -1)
1591 if (fcgi_gen_response(c, "</div>\n") == -1)
1594 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
1597 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
1599 if (fcgi_gen_response(c, author ? author : "") == -1)
1601 if (fcgi_gen_response(c, "</div>\n") == -1)
1604 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1607 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1609 if (fcgi_gen_response(c, age ? age : "") == -1)
1611 if (fcgi_gen_response(c, "</div>\n") == -1)
1614 if (fcgi_gen_response(c, "</div>\n") == -1)
1616 if (fcgi_gen_response(c, "</div>\n") == -1)
1619 if (fcgi_gen_response(c,
1620 "<div id='dotted_line'></div>\n") == -1)
1622 if (fcgi_gen_response(c, "<div id='commit'>\n") == -1)
1625 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1628 if (fcgi_gen_response(c, "</div>\n") == -1)
1630 if (fcgi_gen_response(c, "</div>\n") == -1)
1633 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1635 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1637 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1639 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1641 if (fcgi_gen_response(c, "&path=") == -1)
1643 if (fcgi_gen_response(c, repo_dir->name) == -1)
1645 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1647 if (fcgi_gen_response(c, rc->commit_id) == -1)
1649 if (fcgi_gen_response(c, "'>") == -1)
1651 if (fcgi_gen_response(c, "diff") == -1)
1653 if (fcgi_gen_response(c, "</a>") == -1)
1656 if (fcgi_gen_response(c, " | ") == -1)
1659 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1661 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1663 if (fcgi_gen_response(c, "&path=") == -1)
1665 if (fcgi_gen_response(c, repo_dir->name) == -1)
1667 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1669 if (fcgi_gen_response(c, rc->commit_id) == -1)
1671 if (fcgi_gen_response(c, "'>") == -1)
1673 if (fcgi_gen_response(c, "tree") == -1)
1675 if (fcgi_gen_response(c, "</a>") == -1)
1677 if (fcgi_gen_response(c, "</div>\n") == -1)
1679 if (fcgi_gen_response(c, "</div>\n") == -1)
1681 if (fcgi_gen_response(c,
1682 "<div id='dotted_line'></div>\n") == -1)
1690 if (t->next_id || t->prev_id) {
1691 error = gotweb_render_navs(c);
1695 if (fcgi_gen_response(c, "</div>\n") == -1)
1697 fcgi_gen_response(c, "</div>\n");
1703 static const struct got_error *
1704 gotweb_render_branches(struct request *c)
1706 const struct got_error *error = NULL;
1707 struct got_reflist_head refs;
1708 struct got_reflist_entry *re;
1709 struct transport *t = c->t;
1710 struct querystring *qs = t->qs;
1711 struct got_repository *repo = t->repo;
1716 error = got_ref_list(&refs, repo, "refs/heads",
1717 got_ref_cmp_by_name, NULL);
1721 if (fcgi_gen_response(c, "<div id='branches_title_wrapper'>\n") == -1)
1723 if (fcgi_gen_response(c,
1724 "<div id='branches_title'>Branches</div>\n") == -1)
1726 if (fcgi_gen_response(c, "</div>\n") == -1)
1729 if (fcgi_gen_response(c, "<div id='branches_content'>\n") == -1)
1732 TAILQ_FOREACH(re, &refs, entry) {
1733 char *refname = NULL;
1735 if (got_ref_is_symbolic(re->ref))
1738 refname = strdup(got_ref_get_name(re->ref));
1739 if (refname == NULL) {
1740 error = got_error_from_errno("strdup");
1743 if (strncmp(refname, "refs/heads/", 11) != 0)
1746 error = got_get_repo_age(&age, c, qs->path, refname,
1751 if (strncmp(refname, "refs/heads/", 11) == 0)
1754 if (fcgi_gen_response(c, "<div id='branches_wrapper'>") == -1)
1757 if (fcgi_gen_response(c, "<div id='branches_age'>") == -1)
1759 if (fcgi_gen_response(c, age ? age : "") == -1)
1761 if (fcgi_gen_response(c, "</div>\n") == -1)
1764 if (fcgi_gen_response(c, "<div id='branches_space'>") == -1)
1766 if (fcgi_gen_response(c, " ") == -1)
1768 if (fcgi_gen_response(c, "</div>\n") == -1)
1771 if (fcgi_gen_response(c, "<div id='branch'>") == -1)
1773 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1775 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1777 if (fcgi_gen_response(c, "&path=") == -1)
1779 if (fcgi_gen_response(c, qs->path) == -1)
1781 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1783 if (fcgi_gen_response(c, refname) == -1)
1785 if (fcgi_gen_response(c, "'>") == -1)
1787 if (fcgi_gen_response(c, refname) == -1)
1789 if (fcgi_gen_response(c, "</a>") == -1)
1791 if (fcgi_gen_response(c, "</div>\n") == -1)
1794 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1796 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1799 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1801 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1803 if (fcgi_gen_response(c, "&path=") == -1)
1805 if (fcgi_gen_response(c, qs->path) == -1)
1807 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1809 if (fcgi_gen_response(c, refname) == -1)
1811 if (fcgi_gen_response(c, "'>") == -1)
1813 if (fcgi_gen_response(c, "summary") == -1)
1815 if (fcgi_gen_response(c, "</a>") == -1)
1818 if (fcgi_gen_response(c, " | ") == -1)
1821 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1823 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1825 if (fcgi_gen_response(c, "&path=") == -1)
1827 if (fcgi_gen_response(c, qs->path) == -1)
1829 if (fcgi_gen_response(c, "&action=briefs&headref=") == -1)
1831 if (fcgi_gen_response(c, refname) == -1)
1833 if (fcgi_gen_response(c, "'>") == -1)
1835 if (fcgi_gen_response(c, "commit briefs") == -1)
1837 if (fcgi_gen_response(c, "</a>") == -1)
1840 if (fcgi_gen_response(c, " | ") == -1)
1843 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1845 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1847 if (fcgi_gen_response(c, "&path=") == -1)
1849 if (fcgi_gen_response(c, qs->path) == -1)
1851 if (fcgi_gen_response(c, "&action=commits&headref=") == -1)
1853 if (fcgi_gen_response(c, refname) == -1)
1855 if (fcgi_gen_response(c, "'>") == -1)
1857 if (fcgi_gen_response(c, "commits") == -1)
1859 if (fcgi_gen_response(c, "</a>") == -1)
1862 if (fcgi_gen_response(c, "</div>\n") == -1)
1864 if (fcgi_gen_response(c, "</div>\n") == -1)
1867 if (fcgi_gen_response(c,
1868 "<div id='dotted_line'></div>\n") == -1)
1875 fcgi_gen_response(c, "</div>\n");
1880 static const struct got_error *
1881 gotweb_render_tree(struct request *c)
1883 const struct got_error *error = NULL;
1884 struct transport *t = c->t;
1885 struct repo_commit *rc = NULL;
1888 error = got_get_repo_commits(c, 1);
1892 rc = TAILQ_FIRST(&t->repo_commits);
1894 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1898 if (fcgi_gen_response(c, "<div id='tree_title_wrapper'>\n") == -1)
1900 if (fcgi_gen_response(c, "<div id='tree_title'>Tree</div>\n") == -1)
1902 if (fcgi_gen_response(c, "</div>\n") == -1)
1905 if (fcgi_gen_response(c, "<div id='tree_content'>\n") == -1)
1908 if (fcgi_gen_response(c, "<div id='tree_header_wrapper'>\n") == -1)
1910 if (fcgi_gen_response(c, "<div id='tree_header'>\n") == -1)
1913 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
1916 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
1918 if (fcgi_gen_response(c, rc->tree_id) == -1)
1920 if (fcgi_gen_response(c, "</div>\n") == -1)
1923 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1926 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1928 if (fcgi_gen_response(c, age ? age : "") == -1)
1930 if (fcgi_gen_response(c, "</div>\n") == -1)
1933 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1936 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1938 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1940 if (fcgi_gen_response(c, "</div>\n") == -1)
1943 if (fcgi_gen_response(c, "</div>\n") == -1)
1945 if (fcgi_gen_response(c, "</div>\n") == -1)
1948 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1950 if (fcgi_gen_response(c, "<div id='tree'>\n") == -1)
1953 error = got_output_repo_tree(c);
1957 fcgi_gen_response(c, "</div>\n");
1958 fcgi_gen_response(c, "</div>\n");
1963 static const struct got_error *
1964 gotweb_render_diff(struct request *c)
1966 const struct got_error *error = NULL;
1967 struct transport *t = c->t;
1968 struct repo_commit *rc = NULL;
1969 char *age = NULL, *author = NULL;
1971 error = got_get_repo_commits(c, 1);
1975 rc = TAILQ_FIRST(&t->repo_commits);
1977 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1980 error = gotweb_escape_html(&author, rc->author);
1984 if (fcgi_gen_response(c, "<div id='diff_title_wrapper'>\n") == -1)
1986 if (fcgi_gen_response(c,
1987 "<div id='diff_title'>Commit Diff</div>\n") == -1)
1989 if (fcgi_gen_response(c, "</div>\n") == -1)
1992 if (fcgi_gen_response(c, "<div id='diff_content'>\n") == -1)
1994 if (fcgi_gen_response(c, "<div id='diff_header_wrapper'>\n") == -1)
1996 if (fcgi_gen_response(c, "<div id='diff_header'>\n") == -1)
1999 if (fcgi_gen_response(c, "<div id='header_diff_title'>Diff:"
2002 if (fcgi_gen_response(c, "<div id='header_diff'>") == -1)
2004 if (fcgi_gen_response(c, rc->parent_id) == -1)
2006 if (fcgi_gen_response(c, "<br />") == -1)
2008 if (fcgi_gen_response(c, rc->commit_id) == -1)
2010 if (fcgi_gen_response(c, "</div>\n") == -1)
2013 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2016 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2018 if (fcgi_gen_response(c, rc->commit_id) == -1)
2020 if (fcgi_gen_response(c, "</div>\n") == -1)
2023 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
2026 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
2028 if (fcgi_gen_response(c, rc->tree_id) == -1)
2030 if (fcgi_gen_response(c, "</div>\n") == -1)
2033 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
2036 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2038 if (fcgi_gen_response(c, author ? author : "") == -1)
2040 if (fcgi_gen_response(c, "</div>\n") == -1)
2043 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2046 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2048 if (fcgi_gen_response(c, age ? age : "") == -1)
2050 if (fcgi_gen_response(c, "</div>\n") == -1)
2053 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2056 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2058 if (fcgi_gen_response(c, rc->commit_msg) == -1)
2060 if (fcgi_gen_response(c, "</div>\n") == -1)
2062 if (fcgi_gen_response(c, "</div>\n") == -1)
2064 if (fcgi_gen_response(c, "</div>\n") == -1)
2067 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2069 if (fcgi_gen_response(c, "<div id='diff'>\n") == -1)
2072 error = got_output_repo_diff(c);
2076 fcgi_gen_response(c, "</div>\n");
2078 fcgi_gen_response(c, "</div>\n");
2084 static const struct got_error *
2085 gotweb_render_summary(struct request *c)
2087 const struct got_error *error = NULL;
2088 struct transport *t = c->t;
2089 struct server *srv = c->srv;
2091 if (fcgi_gen_response(c, "<div id='summary_wrapper'>\n") == -1)
2094 if (!srv->show_repo_description)
2097 if (fcgi_gen_response(c, "<div id='description_title'>"
2098 "Description:</div>\n") == -1)
2100 if (fcgi_gen_response(c, "<div id='description'>") == -1)
2102 if (fcgi_gen_response(c, t->repo_dir->description) == -1)
2104 if (fcgi_gen_response(c, "</div>\n") == -1)
2107 if (!srv->show_repo_owner)
2110 if (fcgi_gen_response(c, "<div id='repo_owner_title'>"
2111 "Owner:</div>\n") == -1)
2113 if (fcgi_gen_response(c, "<div id='repo_owner'>") == -1)
2115 if (fcgi_gen_response(c, t->repo_dir->owner) == -1)
2117 if (fcgi_gen_response(c, "</div>\n") == -1)
2120 if (!srv->show_repo_age)
2123 if (fcgi_gen_response(c, "<div id='last_change_title'>"
2124 "Last Change:</div>\n") == -1)
2126 if (fcgi_gen_response(c, "<div id='last_change'>") == -1)
2128 if (fcgi_gen_response(c, t->repo_dir->age) == -1)
2130 if (fcgi_gen_response(c, "</div>\n") == -1)
2133 if (!srv->show_repo_cloneurl)
2136 if (fcgi_gen_response(c, "<div id='cloneurl_title'>"
2137 "Clone URL:</div>\n") == -1)
2139 if (fcgi_gen_response(c, "<div id='cloneurl'>") == -1)
2141 if (fcgi_gen_response(c, t->repo_dir->url) == -1)
2143 if (fcgi_gen_response(c, "</div>\n") == -1)
2146 if (fcgi_gen_response(c, "</div>\n") == -1)
2148 if (fcgi_gen_response(c, "</div>\n") == -1)
2151 error = gotweb_render_briefs(c);
2153 log_warnx("%s: %s", __func__, error->msg);
2157 error = gotweb_render_tags(c);
2159 log_warnx("%s: %s", __func__, error->msg);
2163 error = gotweb_render_branches(c);
2165 log_warnx("%s: %s", __func__, error->msg);
2170 static const struct got_error *
2171 gotweb_render_tag(struct request *c)
2173 const struct got_error *error = NULL;
2174 struct repo_tag *rt = NULL;
2175 struct transport *t = c->t;
2176 char *age = NULL, *author = NULL;
2178 error = got_get_repo_tags(c, 1);
2182 if (t->tag_count == 0) {
2183 error = got_error_set_errno(GOT_ERR_BAD_OBJ_ID,
2188 rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
2190 error = gotweb_get_time_str(&age, rt->tagger_time, TM_LONG);
2193 error = gotweb_escape_html(&author, rt->tagger);
2197 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2199 if (fcgi_gen_response(c, "<div id='tags_title'>Tag</div>\n") == -1)
2201 if (fcgi_gen_response(c, "</div>\n") == -1)
2204 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2206 if (fcgi_gen_response(c, "<div id='tag_header_wrapper'>\n") == -1)
2208 if (fcgi_gen_response(c, "<div id='tag_header'>\n") == -1)
2211 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2214 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2216 if (fcgi_gen_response(c, rt->commit_id) == -1)
2219 if (strncmp(rt->tag_name, "refs/", 5) == 0)
2222 if (fcgi_gen_response(c, " <span id='refs_str'>(") == -1)
2224 if (fcgi_gen_response(c, rt->tag_name) == -1)
2226 if (fcgi_gen_response(c, ")</span>") == -1)
2229 if (fcgi_gen_response(c, "</div>\n") == -1)
2232 if (fcgi_gen_response(c, "<div id='header_author_title'>Tagger:"
2235 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2237 if (fcgi_gen_response(c, author ? author : "") == -1)
2239 if (fcgi_gen_response(c, "</div>\n") == -1)
2242 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2245 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2247 if (fcgi_gen_response(c, age ? age : "") == -1)
2249 if (fcgi_gen_response(c, "</div>\n") == -1)
2252 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2255 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2257 if (fcgi_gen_response(c, rt->commit_msg) == -1)
2259 if (fcgi_gen_response(c, "</div>\n") == -1)
2261 if (fcgi_gen_response(c, "</div>\n") == -1)
2264 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2266 if (fcgi_gen_response(c, "<div id='tag_commit'>\n") == -1)
2269 if (fcgi_gen_response(c, rt->tag_commit) == -1)
2272 if (fcgi_gen_response(c, "</div>\n") == -1)
2274 fcgi_gen_response(c, "</div>\n");
2281 static const struct got_error *
2282 gotweb_render_tags(struct request *c)
2284 const struct got_error *error = NULL;
2285 struct repo_tag *rt = NULL;
2286 struct server *srv = c->srv;
2287 struct transport *t = c->t;
2288 struct querystring *qs = t->qs;
2289 struct repo_dir *repo_dir = t->repo_dir;
2292 int commit_found = 0;
2294 if (qs->action == BRIEFS) {
2296 error = got_get_repo_tags(c, D_MAXSLCOMMDISP);
2298 error = got_get_repo_tags(c, srv->max_commits_display);
2302 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2304 if (fcgi_gen_response(c,
2305 "<div id='tags_title'>Tags</div>\n") == -1)
2307 if (fcgi_gen_response(c, "</div>\n") == -1)
2310 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2313 if (t->tag_count == 0) {
2314 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
2316 if (fcgi_gen_response(c,
2317 "This repository contains no tags\n") == -1)
2319 if (fcgi_gen_response(c, "</div>\n") == -1)
2321 if (fcgi_gen_response(c, "</div>\n") == -1)
2325 TAILQ_FOREACH(rt, &t->repo_tags, entry) {
2326 if (commit_found == 0 && qs->commit != NULL) {
2327 if (strcmp(qs->commit, rt->commit_id) != 0)
2332 error = gotweb_get_time_str(&age, rt->tagger_time, TM_DIFF);
2335 if (fcgi_gen_response(c, "<div id='tag_age'>") == -1)
2337 if (fcgi_gen_response(c, age ? age : "") == -1)
2339 if (fcgi_gen_response(c, "</div>\n") == -1)
2342 if (fcgi_gen_response(c, "<div id='tag'>") == -1)
2344 if (strncmp(rt->tag_name, "refs/tags/", 10) == 0)
2346 if (fcgi_gen_response(c, rt->tag_name) == -1)
2348 if (fcgi_gen_response(c, "</div>\n") == -1)
2351 if (fcgi_gen_response(c, "<div id='tags_log'>") == -1)
2353 if (rt->tag_commit != NULL) {
2354 newline = strchr(rt->tag_commit, '\n');
2359 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2361 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2363 if (fcgi_gen_response(c, "&path=") == -1)
2365 if (fcgi_gen_response(c, repo_dir->name) == -1)
2367 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2369 if (fcgi_gen_response(c, rt->commit_id) == -1)
2371 if (fcgi_gen_response(c, "'>") == -1)
2373 if (rt->tag_commit != NULL &&
2374 fcgi_gen_response(c, rt->tag_commit) == -1)
2376 if (fcgi_gen_response(c, "</a>") == -1)
2378 if (fcgi_gen_response(c, "</div>\n") == -1)
2381 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
2383 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
2386 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2388 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2390 if (fcgi_gen_response(c, "&path=") == -1)
2392 if (fcgi_gen_response(c, repo_dir->name) == -1)
2394 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2396 if (fcgi_gen_response(c, rt->commit_id) == -1)
2398 if (fcgi_gen_response(c, "'>") == -1)
2400 if (fcgi_gen_response(c, "tag") == -1)
2402 if (fcgi_gen_response(c, "</a>") == -1)
2405 if (fcgi_gen_response(c, " | ") == -1)
2408 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2410 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2412 if (fcgi_gen_response(c, "&path=") == -1)
2414 if (fcgi_gen_response(c, repo_dir->name) == -1)
2416 if (fcgi_gen_response(c, "&action=briefs&commit=") == -1)
2418 if (fcgi_gen_response(c, rt->commit_id) == -1)
2420 if (fcgi_gen_response(c, "'>") == -1)
2422 if (fcgi_gen_response(c, "commit briefs") == -1)
2424 if (fcgi_gen_response(c, "</a>") == -1)
2427 if (fcgi_gen_response(c, " | ") == -1)
2430 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2432 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2434 if (fcgi_gen_response(c, "&path=") == -1)
2436 if (fcgi_gen_response(c, repo_dir->name) == -1)
2438 if (fcgi_gen_response(c, "&action=commits&commit=") == -1)
2440 if (fcgi_gen_response(c, rt->commit_id) == -1)
2442 if (fcgi_gen_response(c, "'>") == -1)
2444 if (fcgi_gen_response(c, "commits") == -1)
2446 if (fcgi_gen_response(c, "</a>") == -1)
2449 if (fcgi_gen_response(c, "</div>\n") == -1)
2451 if (fcgi_gen_response(c, "</div>\n") == -1)
2453 if (fcgi_gen_response(c,
2454 "<div id='dotted_line'></div>\n") == -1)
2460 if (t->next_id || t->prev_id) {
2461 error = gotweb_render_navs(c);
2465 fcgi_gen_response(c, "</div>\n");
2471 const struct got_error *
2472 gotweb_escape_html(char **escaped_html, const char *orig_html)
2474 const struct got_error *error = NULL;
2475 struct escape_pair {
2486 size_t orig_len, len;
2489 orig_len = strlen(orig_html);
2491 for (i = 0; i < orig_len; i++) {
2492 for (j = 0; j < nitems(esc); j++) {
2493 if (orig_html[i] != esc[j].c)
2495 len += strlen(esc[j].s) - 1 /* escaped char */;
2499 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
2500 if (*escaped_html == NULL)
2501 return got_error_from_errno("calloc");
2504 for (i = 0; i < orig_len; i++) {
2506 for (j = 0; j < nitems(esc); j++) {
2507 if (orig_html[i] != esc[j].c)
2510 if (strlcat(*escaped_html, esc[j].s, len + 1)
2512 error = got_error(GOT_ERR_NO_SPACE);
2515 x += strlen(esc[j].s);
2520 (*escaped_html)[x] = orig_html[i];
2526 free(*escaped_html);
2527 *escaped_html = NULL;
2529 (*escaped_html)[x] = '\0';
2535 static const struct got_error *
2536 gotweb_load_got_path(struct request *c, struct repo_dir *repo_dir)
2538 const struct got_error *error = NULL;
2539 struct socket *sock = c->sock;
2540 struct server *srv = c->srv;
2541 struct transport *t = c->t;
2546 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2547 GOTWEB_GIT_DIR) == -1)
2548 return got_error_from_errno("asprintf");
2550 dt = opendir(dir_test);
2554 repo_dir->path = strdup(dir_test);
2555 if (repo_dir->path == NULL) {
2557 error = got_error_from_errno("strdup");
2564 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2565 GOTWEB_GOT_DIR) == -1) {
2567 error = got_error_from_errno("asprintf");
2571 dt = opendir(dir_test);
2576 error = got_error(GOT_ERR_NOT_GIT_REPO);
2580 if (asprintf(&dir_test, "%s/%s", srv->repos_path,
2581 repo_dir->name) == -1) {
2582 error = got_error_from_errno("asprintf");
2587 repo_dir->path = strdup(dir_test);
2588 if (repo_dir->path == NULL) {
2590 error = got_error_from_errno("strdup");
2594 dt = opendir(dir_test);
2596 error = got_error_path(repo_dir->name, GOT_ERR_NOT_GIT_REPO);
2601 error = got_repo_open(&t->repo, repo_dir->path, NULL, sock->pack_fds);
2604 error = gotweb_get_repo_description(&repo_dir->description, srv,
2608 error = got_get_repo_owner(&repo_dir->owner, c, repo_dir->path);
2611 error = got_get_repo_age(&repo_dir->age, c, repo_dir->path,
2615 error = gotweb_get_clone_url(&repo_dir->url, srv, repo_dir->path);
2619 if (dt != NULL && closedir(dt) == EOF && error == NULL)
2620 error = got_error_from_errno("closedir");
2624 static const struct got_error *
2625 gotweb_init_repo_dir(struct repo_dir **repo_dir, const char *dir)
2627 const struct got_error *error;
2629 *repo_dir = calloc(1, sizeof(**repo_dir));
2630 if (*repo_dir == NULL)
2631 return got_error_from_errno("calloc");
2633 if (asprintf(&(*repo_dir)->name, "%s", dir) == -1) {
2634 error = got_error_from_errno("asprintf");
2639 (*repo_dir)->owner = NULL;
2640 (*repo_dir)->description = NULL;
2641 (*repo_dir)->url = NULL;
2642 (*repo_dir)->age = NULL;
2643 (*repo_dir)->path = NULL;
2648 static const struct got_error *
2649 gotweb_get_repo_description(char **description, struct server *srv, char *dir)
2651 const struct got_error *error = NULL;
2653 char *d_file = NULL;
2657 *description = NULL;
2658 if (srv->show_repo_description == 0)
2661 if (asprintf(&d_file, "%s/description", dir) == -1)
2662 return got_error_from_errno("asprintf");
2664 f = fopen(d_file, "r");
2666 if (errno == ENOENT || errno == EACCES)
2668 error = got_error_from_errno2("fopen", d_file);
2672 if (fseek(f, 0, SEEK_END) == -1) {
2673 error = got_ferror(f, GOT_ERR_IO);
2678 error = got_ferror(f, GOT_ERR_IO);
2685 if (fseek(f, 0, SEEK_SET) == -1) {
2686 error = got_ferror(f, GOT_ERR_IO);
2689 *description = calloc(len + 1, sizeof(**description));
2690 if (*description == NULL) {
2691 error = got_error_from_errno("calloc");
2695 n = fread(*description, 1, len, f);
2696 if (n == 0 && ferror(f))
2697 error = got_ferror(f, GOT_ERR_IO);
2699 if (f != NULL && fclose(f) == EOF && error == NULL)
2700 error = got_error_from_errno("fclose");
2705 static const struct got_error *
2706 gotweb_get_clone_url(char **url, struct server *srv, char *dir)
2708 const struct got_error *error = NULL;
2710 char *d_file = NULL;
2716 if (srv->show_repo_cloneurl == 0)
2719 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2720 return got_error_from_errno("asprintf");
2722 f = fopen(d_file, "r");
2724 if (errno != ENOENT && errno != EACCES)
2725 error = got_error_from_errno2("fopen", d_file);
2729 if (fseek(f, 0, SEEK_END) == -1) {
2730 error = got_ferror(f, GOT_ERR_IO);
2735 error = got_ferror(f, GOT_ERR_IO);
2741 if (fseek(f, 0, SEEK_SET) == -1) {
2742 error = got_ferror(f, GOT_ERR_IO);
2746 *url = calloc(len + 1, sizeof(**url));
2748 error = got_error_from_errno("calloc");
2752 n = fread(*url, 1, len, f);
2753 if (n == 0 && ferror(f))
2754 error = got_ferror(f, GOT_ERR_IO);
2756 if (f != NULL && fclose(f) == EOF && error == NULL)
2757 error = got_error_from_errno("fclose");
2762 const struct got_error *
2763 gotweb_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2766 long long diff_time;
2767 const char *years = "years ago", *months = "months ago";
2768 const char *weeks = "weeks ago", *days = "days ago";
2769 const char *hours = "hours ago", *minutes = "minutes ago";
2770 const char *seconds = "seconds ago", *now = "right now";
2778 diff_time = time(NULL) - committer_time;
2779 if (diff_time > 60 * 60 * 24 * 365 * 2) {
2780 if (asprintf(repo_age, "%lld %s",
2781 (diff_time / 60 / 60 / 24 / 365), years) == -1)
2782 return got_error_from_errno("asprintf");
2783 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
2784 if (asprintf(repo_age, "%lld %s",
2785 (diff_time / 60 / 60 / 24 / (365 / 12)),
2787 return got_error_from_errno("asprintf");
2788 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
2789 if (asprintf(repo_age, "%lld %s",
2790 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
2791 return got_error_from_errno("asprintf");
2792 } else if (diff_time > 60 * 60 * 24 * 2) {
2793 if (asprintf(repo_age, "%lld %s",
2794 (diff_time / 60 / 60 / 24), days) == -1)
2795 return got_error_from_errno("asprintf");
2796 } else if (diff_time > 60 * 60 * 2) {
2797 if (asprintf(repo_age, "%lld %s",
2798 (diff_time / 60 / 60), hours) == -1)
2799 return got_error_from_errno("asprintf");
2800 } else if (diff_time > 60 * 2) {
2801 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
2803 return got_error_from_errno("asprintf");
2804 } else if (diff_time > 2) {
2805 if (asprintf(repo_age, "%lld %s", diff_time,
2807 return got_error_from_errno("asprintf");
2809 if (asprintf(repo_age, "%s", now) == -1)
2810 return got_error_from_errno("asprintf");
2814 if (gmtime_r(&committer_time, &tm) == NULL)
2815 return got_error_from_errno("gmtime_r");
2817 s = asctime_r(&tm, datebuf);
2819 return got_error_from_errno("asctime_r");
2821 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2822 return got_error_from_errno("asprintf");