2 * Copyright (c) 2020-2022 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2018, 2019 Stefan Sperling <stsp@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <sys/socket.h>
27 #include "got_error.h"
28 #include "got_object.h"
29 #include "got_reference.h"
30 #include "got_repository.h"
32 #include "got_cancel.h"
34 #include "got_commit_graph.h"
35 #include "got_blame.h"
36 #include "got_privsep.h"
38 #include "got_compat.h"
43 static const struct got_error *got_init_repo_commit(struct repo_commit **);
44 static const struct got_error *got_init_repo_tag(struct repo_tag **);
45 static const struct got_error *got_get_repo_commit(struct request *,
46 struct repo_commit *, struct got_commit_object *, struct got_reflist_head *,
47 struct got_object_id *);
48 static const struct got_error *got_gotweb_dupfd(int *, int *);
49 static const struct got_error *got_gotweb_openfile(FILE **, int *, int *);
50 static const struct got_error *got_gotweb_flushfile(FILE *, int);
51 static const struct got_error *got_gotweb_blame_cb(void *, int, int,
52 struct got_commit_object *,struct got_object_id *);
55 isbinary(const uint8_t *buf, size_t n)
59 for (i = 0; i < n; i++)
66 static const struct got_error *
67 got_gotweb_flushfile(FILE *f, int fd)
69 if (fseek(f, 0, SEEK_SET) == -1)
70 return got_error_from_errno("fseek");
72 if (ftruncate(fd, 0) == -1)
73 return got_error_from_errno("ftruncate");
76 return got_error_from_errno("fsync");
78 if (f && fclose(f) == EOF)
79 return got_error_from_errno("fclose");
81 if (fd != -1 && close(fd) != -1)
82 return got_error_from_errno("close");
87 static const struct got_error *
88 got_gotweb_openfile(FILE **f, int *priv_fd, int *fd)
90 const struct got_error *error = NULL;
97 *f = fdopen(*fd, "w+");
100 error = got_error(GOT_ERR_PRIVSEP_NO_FD);
106 static const struct got_error *
107 got_gotweb_dupfd(int *priv_fd, int *fd)
109 const struct got_error *error = NULL;
119 const struct got_error *
120 got_get_repo_owner(char **owner, struct request *c, char *dir)
122 const struct got_error *error = NULL;
123 struct server *srv = c->srv;
124 struct transport *t = c->t;
125 struct got_repository *repo = t->repo;
126 const char *gitconfig_owner;
130 if (srv->show_repo_owner == 0)
133 gitconfig_owner = got_repo_get_gitconfig_owner(repo);
134 if (gitconfig_owner) {
135 *owner = strdup(gitconfig_owner);
137 return got_error_from_errno("strdup");
142 const struct got_error *
143 got_get_repo_age(char **repo_age, struct request *c, char *dir,
144 const char *refname, int ref_tm)
146 const struct got_error *error = NULL;
147 struct server *srv = c->srv;
148 struct transport *t = c->t;
149 struct got_repository *repo = t->repo;
150 struct got_commit_object *commit = NULL;
151 struct got_reflist_head refs;
152 struct got_reflist_entry *re;
153 time_t committer_time = 0, cmp_time = 0;
158 if (srv->show_repo_age == 0)
161 error = got_ref_list(&refs, repo, "refs/heads",
162 got_ref_cmp_by_name, NULL);
167 * Find the youngest branch tip in the repository, or the age of
168 * the a specific branch tip if a name was provided by the caller.
170 TAILQ_FOREACH(re, &refs, entry) {
171 struct got_object_id *id = NULL;
173 if (refname && strcmp(got_ref_get_name(re->ref), refname) != 0)
176 error = got_ref_resolve(&id, repo, re->ref);
180 error = got_object_open_as_commit(&commit, repo, id);
186 got_object_commit_get_committer_time(commit);
187 got_object_commit_close(commit);
188 if (cmp_time < committer_time)
189 cmp_time = committer_time;
196 committer_time = cmp_time;
197 error = gotweb_get_time_str(repo_age, committer_time, ref_tm);
200 got_ref_list_free(&refs);
204 static const struct got_error *
205 got_get_repo_commit(struct request *c, struct repo_commit *repo_commit,
206 struct got_commit_object *commit, struct got_reflist_head *refs,
207 struct got_object_id *id)
209 const struct got_error *error = NULL;
210 struct got_reflist_entry *re;
211 struct got_object_id *id2 = NULL;
212 struct got_object_qid *parent_id;
213 struct transport *t = c->t;
214 struct querystring *qs = c->t->qs;
215 char *commit_msg = NULL, *commit_msg0;
217 TAILQ_FOREACH(re, refs, entry) {
220 struct got_tag_object *tag = NULL;
221 struct got_object_id *ref_id;
224 if (got_ref_is_symbolic(re->ref))
227 name = got_ref_get_name(re->ref);
228 if (strncmp(name, "refs/", 5) == 0)
230 if (strncmp(name, "got/", 4) == 0)
232 if (strncmp(name, "heads/", 6) == 0)
234 if (strncmp(name, "remotes/", 8) == 0) {
236 s = strstr(name, "/" GOT_REF_HEAD);
237 if (s != NULL && s[strlen(s)] == '\0')
240 error = got_ref_resolve(&ref_id, t->repo, re->ref);
243 if (strncmp(name, "tags/", 5) == 0) {
244 error = got_object_open_as_tag(&tag, t->repo, ref_id);
246 if (error->code != GOT_ERR_OBJ_TYPE) {
251 * Ref points at something other
258 cmp = got_object_id_cmp(tag ?
259 got_object_tag_get_object_id(tag) : ref_id, id);
262 got_object_tag_close(tag);
265 s = repo_commit->refs_str;
266 if (asprintf(&repo_commit->refs_str, "%s%s%s", s ? s : "",
267 s ? ", " : "", name) == -1) {
268 error = got_error_from_errno("asprintf");
270 repo_commit->refs_str = NULL;
276 error = got_object_id_str(&repo_commit->commit_id, id);
280 error = got_object_id_str(&repo_commit->tree_id,
281 got_object_commit_get_tree_id(commit));
285 if (qs->action == DIFF) {
286 parent_id = STAILQ_FIRST(
287 got_object_commit_get_parent_ids(commit));
288 if (parent_id != NULL) {
289 id2 = got_object_id_dup(&parent_id->id);
290 error = got_object_id_str(&repo_commit->parent_id, id2);
295 repo_commit->parent_id = strdup("/dev/null");
296 if (repo_commit->parent_id == NULL) {
297 error = got_error_from_errno("strdup");
303 repo_commit->committer_time =
304 got_object_commit_get_committer_time(commit);
306 repo_commit->author =
307 strdup(got_object_commit_get_author(commit));
308 if (repo_commit->author == NULL) {
309 error = got_error_from_errno("strdup");
312 repo_commit->committer =
313 strdup(got_object_commit_get_committer(commit));
314 if (repo_commit->committer == NULL) {
315 error = got_error_from_errno("strdup");
318 error = got_object_commit_get_logmsg(&commit_msg0, commit);
322 commit_msg = commit_msg0;
323 while (*commit_msg == '\n')
326 repo_commit->commit_msg = strdup(commit_msg);
327 if (repo_commit->commit_msg == NULL)
328 error = got_error_from_errno("strdup");
333 const struct got_error *
334 got_get_repo_commits(struct request *c, int limit)
336 const struct got_error *error = NULL;
337 struct got_object_id *id = NULL;
338 struct got_commit_graph *graph = NULL;
339 struct got_commit_object *commit = NULL;
340 struct got_reflist_head refs;
341 struct got_reference *ref;
342 struct repo_commit *repo_commit = NULL;
343 struct server *srv = c->srv;
344 struct transport *t = c->t;
345 struct got_repository *repo = t->repo;
346 struct querystring *qs = t->qs;
347 struct repo_dir *repo_dir = t->repo_dir;
348 char *in_repo_path = NULL, *repo_path = NULL, *file_path = NULL;
349 int chk_next = 0, chk_multi = 0, commit_found = 0;
350 int obj_type, limit_chk = 0;
354 if (qs->file != NULL && strlen(qs->file) > 0)
355 if (asprintf(&file_path, "%s/%s", qs->folder ? qs->folder : "",
357 return got_error_from_errno("asprintf");
359 if (asprintf(&repo_path, "%s/%s", srv->repos_path,
360 repo_dir->name) == -1)
361 return got_error_from_errno("asprintf");
363 error = got_init_repo_commit(&repo_commit);
368 * XXX: jumping directly to a commit id via
369 * got_repo_match_object_id_prefix significantly improves performance,
370 * but does not allow us to create a PREVIOUS button, since commits can
371 * only be itereated forward. So, we have to match as we iterate from
374 if (qs->action == BRIEFS || qs->action == COMMITS ||
375 (qs->action == TREE && qs->commit == NULL)) {
376 error = got_ref_open(&ref, repo, qs->headref, 0);
380 error = got_ref_resolve(&id, repo, ref);
384 } else if (qs->commit != NULL) {
385 error = got_ref_open(&ref, repo, qs->commit, 0);
387 error = got_ref_resolve(&id, repo, ref);
390 error = got_object_get_type(&obj_type, repo, id);
394 if (obj_type == GOT_OBJ_TYPE_TAG) {
395 struct got_tag_object *tag;
396 error = got_object_open_as_tag(&tag, repo, id);
399 if (got_object_tag_get_object_type(tag) !=
400 GOT_OBJ_TYPE_COMMIT) {
401 got_object_tag_close(tag);
402 error = got_error(GOT_ERR_OBJ_TYPE);
406 id = got_object_id_dup(
407 got_object_tag_get_object_id(tag));
409 error = got_error_from_errno(
410 "got_object_id_dup");
411 got_object_tag_close(tag);
414 } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
415 error = got_error(GOT_ERR_OBJ_TYPE);
419 error = got_repo_match_object_id_prefix(&id, qs->commit,
420 GOT_OBJ_TYPE_COMMIT, repo);
425 error = got_repo_map_path(&in_repo_path, repo, repo_path);
429 error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
433 if (qs->file != NULL && strlen(qs->file) > 0) {
434 error = got_commit_graph_open(&graph, file_path, 0);
438 error = got_commit_graph_open(&graph, in_repo_path, 0);
443 error = got_commit_graph_iter_start(graph, id, repo, NULL, NULL);
448 if (limit_chk == ((limit * qs->page) - (limit - 1)) &&
449 commit_found == 0 && repo_commit->commit_id != NULL) {
450 t->prev_id = strdup(repo_commit->commit_id);
451 if (t->prev_id == NULL) {
452 error = got_error_from_errno("strdup");
457 error = got_commit_graph_iter_next(&id, graph, repo, NULL,
460 if (error->code == GOT_ERR_ITER_COMPLETED)
467 error = got_object_open_as_commit(&commit, repo, id);
471 error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name,
476 error = got_get_repo_commit(c, repo_commit, commit,
481 if (qs->commit != NULL && commit_found == 0 && limit != 1) {
482 if (strcmp(qs->commit, repo_commit->commit_id) == 0)
484 else if (qs->file != NULL && strlen(qs->file) > 0 &&
495 struct repo_commit *new_repo_commit = NULL;
496 error = got_init_repo_commit(&new_repo_commit);
500 TAILQ_INSERT_TAIL(&t->repo_commits, new_repo_commit, entry);
502 error = got_get_repo_commit(c, new_repo_commit, commit,
510 if (limit == 1 && chk_multi == 0 &&
511 srv->max_commits_display != 1)
517 * check for one more commit before breaking,
518 * so we know whether to navigate through briefs
519 * commits and summary
521 if (chk_next && (qs->action == BRIEFS ||
522 qs->action == COMMITS || qs->action == SUMMARY)) {
523 t->next_id = strdup(new_repo_commit->commit_id);
524 if (t->next_id == NULL) {
525 error = got_error_from_errno("strdup");
529 got_object_commit_close(commit);
532 if (t->next_id == NULL) {
533 error = got_error_from_errno("strdup");
536 TAILQ_REMOVE(&t->repo_commits, new_repo_commit,
538 gotweb_free_repo_commit(new_repo_commit);
542 got_ref_list_free(&refs);
543 if (error || (limit && --limit == 0)) {
544 if (commit_found || (qs->file != NULL &&
545 strlen(qs->file) > 0))
551 got_object_commit_close(commit);
556 gotweb_free_repo_commit(repo_commit);
558 got_object_commit_close(commit);
560 got_commit_graph_close(graph);
561 got_ref_list_free(&refs);
568 const struct got_error *
569 got_get_repo_tags(struct request *c, int limit)
571 const struct got_error *error = NULL;
572 struct got_object_id *id = NULL;
573 struct got_commit_object *commit = NULL;
574 struct got_reflist_head refs;
575 struct got_reference *ref;
576 struct got_reflist_entry *re;
577 struct server *srv = c->srv;
578 struct transport *t = c->t;
579 struct got_repository *repo = t->repo;
580 struct querystring *qs = t->qs;
581 struct repo_dir *repo_dir = t->repo_dir;
582 struct got_tag_object *tag = NULL;
583 struct repo_tag *rt = NULL, *trt = NULL;
584 char *in_repo_path = NULL, *repo_path = NULL, *id_str = NULL;
585 char *commit_msg = NULL, *commit_msg0 = NULL;
586 int chk_next = 0, chk_multi = 1, commit_found = 0, c_cnt = 0;
590 if (asprintf(&repo_path, "%s/%s", srv->repos_path,
591 repo_dir->name) == -1)
592 return got_error_from_errno("asprintf");
597 if (qs->commit == NULL && qs->action == TAGS) {
598 error = got_ref_open(&ref, repo, qs->headref, 0);
601 error = got_ref_resolve(&id, repo, ref);
605 } else if (qs->commit == NULL && qs->action == TAG) {
606 error = got_error_msg(GOT_ERR_EOF, "commit id missing");
609 error = got_repo_match_object_id_prefix(&id, qs->commit,
610 GOT_OBJ_TYPE_COMMIT, repo);
615 if (qs->action != SUMMARY && qs->action != TAGS) {
616 error = got_object_open_as_commit(&commit, repo, id);
619 error = got_object_commit_get_logmsg(&commit_msg0, commit);
623 got_object_commit_close(commit);
628 error = got_repo_map_path(&in_repo_path, repo, repo_path);
632 error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags,
641 * XXX: again, see previous message about caching
644 TAILQ_FOREACH(re, &refs, entry) {
645 struct repo_tag *new_repo_tag = NULL;
646 error = got_init_repo_tag(&new_repo_tag);
650 TAILQ_INSERT_TAIL(&t->repo_tags, new_repo_tag, entry);
652 new_repo_tag->tag_name = strdup(got_ref_get_name(re->ref));
653 if (new_repo_tag->tag_name == NULL) {
654 error = got_error_from_errno("strdup");
658 error = got_ref_resolve(&id, repo, re->ref);
662 error = got_object_open_as_tag(&tag, repo, id);
664 if (error->code != GOT_ERR_OBJ_TYPE) {
669 /* "lightweight" tag */
670 error = got_object_open_as_commit(&commit, repo, id);
676 new_repo_tag->tagger =
677 strdup(got_object_commit_get_committer(commit));
678 if (new_repo_tag->tagger == NULL) {
679 error = got_error_from_errno("strdup");
682 new_repo_tag->tagger_time =
683 got_object_commit_get_committer_time(commit);
684 error = got_object_id_str(&id_str, id);
692 new_repo_tag->tagger =
693 strdup(got_object_tag_get_tagger(tag));
694 if (new_repo_tag->tagger == NULL) {
695 error = got_error_from_errno("strdup");
698 new_repo_tag->tagger_time =
699 got_object_tag_get_tagger_time(tag);
700 error = got_object_id_str(&id_str,
701 got_object_tag_get_object_id(tag));
706 new_repo_tag->commit_id = strdup(id_str);
707 if (new_repo_tag->commit_id == NULL)
710 if (commit_found == 0 && qs->commit != NULL &&
711 strncmp(id_str, qs->commit, strlen(id_str)) != 0)
719 * check for one more commit before breaking,
720 * so we know whether to navigate through briefs
721 * commits and summary
724 t->next_id = strdup(new_repo_tag->commit_id);
725 if (t->next_id == NULL) {
726 error = got_error_from_errno("strdup");
730 got_object_commit_close(commit);
733 if (t->next_id == NULL) {
734 error = got_error_from_errno("strdup");
737 TAILQ_REMOVE(&t->repo_tags, new_repo_tag, entry);
738 gotweb_free_repo_tag(new_repo_tag);
743 error = got_object_commit_get_logmsg(&new_repo_tag->
747 got_object_commit_close(commit);
750 new_repo_tag->tag_commit =
751 strdup(got_object_tag_get_message(tag));
752 if (new_repo_tag->tag_commit == NULL) {
753 error = got_error_from_errno("strdup");
758 while (*new_repo_tag->tag_commit == '\n')
759 new_repo_tag->tag_commit++;
761 if (qs->action != SUMMARY && qs->action != TAGS) {
762 commit_msg = commit_msg0;
763 while (*commit_msg == '\n')
766 new_repo_tag->commit_msg = strdup(commit_msg);
767 if (new_repo_tag->commit_msg == NULL) {
768 error = got_error_from_errno("strdup");
775 if (limit && --limit == 0) {
786 * we have tailq populated, so find previous commit id
787 * for navigation through briefs and commits
789 if (t->tag_count == 0) {
790 TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
791 TAILQ_REMOVE(&t->repo_tags, rt, entry);
792 gotweb_free_repo_tag(rt);
795 if (t->tag_count > 0 && t->prev_id == NULL && qs->commit != NULL) {
797 TAILQ_FOREACH_REVERSE(rt, &t->repo_tags, repo_tags_head,
799 if (commit_found == 0 && rt->commit_id != NULL &&
800 strcmp(qs->commit, rt->commit_id) != 0) {
804 if (c_cnt == srv->max_commits_display ||
805 rt == TAILQ_FIRST(&t->repo_tags)) {
806 t->prev_id = strdup(rt->commit_id);
807 if (t->prev_id == NULL)
808 error = got_error_from_errno("strdup");
816 got_object_commit_close(commit);
817 got_ref_list_free(&refs);
823 const struct got_error *
824 got_output_repo_tree(struct request *c)
826 const struct got_error *error = NULL;
827 struct transport *t = c->t;
828 struct got_commit_object *commit = NULL;
829 struct got_repository *repo = t->repo;
830 struct querystring *qs = t->qs;
831 struct repo_commit *rc = NULL;
832 struct got_object_id *tree_id = NULL, *commit_id = NULL;
833 struct got_reflist_head refs;
834 struct got_tree_object *tree = NULL;
835 struct repo_dir *repo_dir = t->repo_dir;
837 char *path = NULL, *in_repo_path = NULL, *build_folder = NULL;
838 char *modestr = NULL, *name = NULL, *class = NULL;
839 int nentries, i, class_flip = 0;
843 rc = TAILQ_FIRST(&t->repo_commits);
845 if (qs->folder != NULL) {
846 path = strdup(qs->folder);
848 error = got_error_from_errno("strdup");
852 error = got_repo_map_path(&in_repo_path, repo, repo_dir->path);
859 error = got_repo_match_object_id(&commit_id, NULL, rc->commit_id,
860 GOT_OBJ_TYPE_COMMIT, &refs, repo);
864 error = got_object_open_as_commit(&commit, repo, commit_id);
868 error = got_object_id_by_path(&tree_id, repo, commit, path);
872 error = got_object_open_as_tree(&tree, repo, tree_id);
876 nentries = got_object_tree_get_nentries(tree);
878 for (i = 0; i < nentries; i++) {
879 struct got_tree_entry *te;
882 te = got_object_tree_get_entry(tree, i);
884 error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
888 modestr = strdup("");
889 if (modestr == NULL) {
890 error = got_error_from_errno("strdup");
893 mode = got_tree_entry_get_mode(te);
894 if (got_object_tree_entry_is_submodule(te)) {
896 modestr = strdup("$");
897 if (modestr == NULL) {
898 error = got_error_from_errno("strdup");
901 } else if (S_ISLNK(mode)) {
903 modestr = strdup("@");
904 if (modestr == NULL) {
905 error = got_error_from_errno("strdup");
908 } else if (S_ISDIR(mode)) {
910 modestr = strdup("/");
911 if (modestr == NULL) {
912 error = got_error_from_errno("strdup");
915 } else if (mode & S_IXUSR) {
917 modestr = strdup("*");
918 if (modestr == NULL) {
919 error = got_error_from_errno("strdup");
924 if (class_flip == 0) {
925 class = strdup("back_lightgray");
927 error = got_error_from_errno("strdup");
932 class = strdup("back_white");
934 error = got_error_from_errno("strdup");
940 name = strdup(got_tree_entry_get_name(te));
942 error = got_error_from_errno("strdup");
946 if (asprintf(&build_folder, "%s/%s",
947 qs->folder ? qs->folder : "",
948 got_tree_entry_get_name(te)) == -1) {
949 error = got_error_from_errno("asprintf");
953 if (fcgi_gen_response(c,
954 "<div id='tree_wrapper'>\n") == -1)
957 if (fcgi_gen_response(c, "<div id='tree_line' "
960 if (fcgi_gen_response(c, class) == -1)
962 if (fcgi_gen_response(c, "'>") == -1)
965 if (fcgi_gen_response(c, "<a class='diff_directory' "
966 "href='?index_page=") == -1)
968 if (fcgi_gen_response(c, qs->index_page_str) == -1)
970 if (fcgi_gen_response(c, "&path=") == -1)
972 if (fcgi_gen_response(c, qs->path) == -1)
974 if (fcgi_gen_response(c, "&action=tree") == -1)
976 if (fcgi_gen_response(c, "&commit=") == -1)
978 if (fcgi_gen_response(c, rc->commit_id) == -1)
980 if (fcgi_gen_response(c, "&folder=") == -1)
982 if (fcgi_gen_response(c, build_folder) == -1)
984 if (fcgi_gen_response(c, "'>") == -1)
986 if (fcgi_gen_response(c, name) == -1)
988 if (fcgi_gen_response(c, modestr) == -1)
990 if (fcgi_gen_response(c, "</a>") == -1)
993 if (fcgi_gen_response(c, "</div>\n") == -1)
996 if (fcgi_gen_response(c, "<div id='tree_line_blank' "
999 if (fcgi_gen_response(c, class) == -1)
1001 if (fcgi_gen_response(c, "'>") == -1)
1003 if (fcgi_gen_response(c, " ") == -1)
1005 if (fcgi_gen_response(c, "</div>\n") == -1)
1008 if (fcgi_gen_response(c, "</div>\n") == -1)
1013 name = strdup(got_tree_entry_get_name(te));
1015 error = got_error_from_errno("strdup");
1019 if (fcgi_gen_response(c,
1020 "<div id='tree_wrapper'>\n") == -1)
1022 if (fcgi_gen_response(c, "<div id='tree_line' "
1025 if (fcgi_gen_response(c, class) == -1)
1027 if (fcgi_gen_response(c, "'>") == -1)
1030 if (fcgi_gen_response(c,
1031 "<a href='?index_page=") == -1)
1034 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1037 if (fcgi_gen_response(c, "&path=") == -1)
1039 if (fcgi_gen_response(c, qs->path) == -1)
1042 if (fcgi_gen_response(c, "&action=blob") == -1)
1045 if (fcgi_gen_response(c, "&commit=") == -1)
1047 if (fcgi_gen_response(c, rc->commit_id) == -1)
1050 if (fcgi_gen_response(c, "&folder=") == -1)
1052 if (fcgi_gen_response(c, qs->folder) == -1)
1055 if (fcgi_gen_response(c, "&file=") == -1)
1057 if (fcgi_gen_response(c, name) == -1)
1060 if (fcgi_gen_response(c, "'>") == -1)
1062 if (fcgi_gen_response(c, name) == -1)
1064 if (fcgi_gen_response(c, modestr) == -1)
1067 if (fcgi_gen_response(c, "</a>") == -1)
1070 if (fcgi_gen_response(c, "</div>\n") == -1)
1073 if (fcgi_gen_response(c, "<div id='tree_line_blank' "
1076 if (fcgi_gen_response(c, class) == -1)
1078 if (fcgi_gen_response(c, "'>") == -1)
1081 if (fcgi_gen_response(c,
1082 "<a href='?index_page=") == -1)
1085 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1088 if (fcgi_gen_response(c, "&path=") == -1)
1090 if (fcgi_gen_response(c, qs->path) == -1)
1093 if (fcgi_gen_response(c, "&action=commits") == -1)
1096 if (fcgi_gen_response(c, "&commit=") == -1)
1098 if (fcgi_gen_response(c, rc->commit_id) == -1)
1101 if (fcgi_gen_response(c, "&folder=") == -1)
1103 if (fcgi_gen_response(c, qs->folder) == -1)
1106 if (fcgi_gen_response(c, "&file=") == -1)
1108 if (fcgi_gen_response(c, name) == -1)
1111 if (fcgi_gen_response(c, "'>") == -1)
1114 if (fcgi_gen_response(c, "commits") == -1)
1116 if (fcgi_gen_response(c, "</a>\n") == -1)
1119 if (fcgi_gen_response(c, " | \n") == -1)
1122 if (fcgi_gen_response(c,
1123 "<a href='?index_page=") == -1)
1126 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1129 if (fcgi_gen_response(c, "&path=") == -1)
1131 if (fcgi_gen_response(c, qs->path) == -1)
1134 if (fcgi_gen_response(c, "&action=blame") == -1)
1137 if (fcgi_gen_response(c, "&commit=") == -1)
1139 if (fcgi_gen_response(c, rc->commit_id) == -1)
1142 if (fcgi_gen_response(c, "&folder=") == -1)
1144 if (fcgi_gen_response(c, qs->folder) == -1)
1147 if (fcgi_gen_response(c, "&file=") == -1)
1149 if (fcgi_gen_response(c, name) == -1)
1152 if (fcgi_gen_response(c, "'>") == -1)
1155 if (fcgi_gen_response(c, "blame") == -1)
1157 if (fcgi_gen_response(c, "</a>\n") == -1)
1160 if (fcgi_gen_response(c, "</div>\n") == -1)
1162 if (fcgi_gen_response(c, "</div>\n") == -1)
1168 build_folder = NULL;
1183 got_ref_list_free(&refs);
1185 got_object_commit_close(commit);
1191 const struct got_error *
1192 got_output_file_blob(struct request *c)
1194 const struct got_error *error = NULL;
1195 struct transport *t = c->t;
1196 struct got_repository *repo = t->repo;
1197 struct querystring *qs = c->t->qs;
1198 struct got_commit_object *commit = NULL;
1199 struct got_object_id *commit_id = NULL;
1200 struct got_reflist_head refs;
1201 struct got_blob_object *blob = NULL;
1202 char *path = NULL, *in_repo_path = NULL;
1203 int obj_type, set_mime = 0, type = 0, fd = -1;
1204 char *buf_output = NULL;
1210 if (asprintf(&path, "%s%s%s", qs->folder ? qs->folder : "",
1211 qs->folder ? "/" : "", qs->file) == -1) {
1212 error = got_error_from_errno("asprintf");
1216 error = got_repo_map_path(&in_repo_path, repo, path);
1220 error = got_repo_match_object_id(&commit_id, NULL, qs->commit,
1221 GOT_OBJ_TYPE_COMMIT, &refs, repo);
1225 error = got_object_open_as_commit(&commit, repo, commit_id);
1229 error = got_object_id_by_path(&commit_id, repo, commit, in_repo_path);
1233 if (commit_id == NULL) {
1234 error = got_error(GOT_ERR_NO_OBJ);
1238 error = got_object_get_type(&obj_type, repo, commit_id);
1242 if (obj_type != GOT_OBJ_TYPE_BLOB) {
1243 error = got_error(GOT_ERR_OBJ_TYPE);
1247 error = got_gotweb_dupfd(&c->priv_fd[BLOB_FD_1], &fd);
1251 error = got_object_open_as_blob(&blob, repo, commit_id, BUF, fd);
1254 hdrlen = got_object_blob_get_hdrlen(blob);
1256 error = got_object_blob_read_block(&len, blob);
1259 buf = got_object_blob_get_read_buf(blob);
1262 * Skip blob object header first time around,
1263 * which also contains a zero byte.
1266 if (set_mime == 0) {
1267 if (isbinary(buf, len - hdrlen)) {
1268 error = gotweb_render_content_type_file(c,
1269 "application/octet-stream",
1272 log_warnx("%s: %s", __func__,
1278 error = gotweb_render_content_type(c,
1281 log_warnx("%s: %s", __func__,
1290 buf_output = calloc(len - hdrlen + 1,
1291 sizeof(*buf_output));
1292 if (buf_output == NULL) {
1293 error = got_error_from_errno("calloc");
1296 memcpy(buf_output, buf, len - hdrlen);
1297 fcgi_gen_response(c, buf_output);
1301 fcgi_gen_binary_response(c, buf, len - hdrlen);
1307 got_object_commit_close(commit);
1308 if (fd != -1 && close(fd) == -1 && error == NULL)
1309 error = got_error_from_errno("close");
1311 got_object_blob_close(blob);
1323 char datebuf[11]; /* YYYY-MM-DD + NUL */
1326 struct blame_cb_args {
1327 struct blame_line *lines;
1331 off_t *line_offsets;
1333 struct got_repository *repo;
1337 static const struct got_error *
1338 got_gotweb_blame_cb(void *arg, int nlines, int lineno,
1339 struct got_commit_object *commit, struct got_object_id *id)
1341 const struct got_error *err = NULL;
1342 struct blame_cb_args *a = arg;
1343 struct blame_line *bline;
1344 struct request *c = a->c;
1345 struct transport *t = c->t;
1346 struct querystring *qs = t->qs;
1347 struct repo_dir *repo_dir = t->repo_dir;
1348 char *line = NULL, *eline = NULL;
1349 size_t linesize = 0;
1352 time_t committer_time;
1354 if (nlines != a->nlines ||
1355 (lineno != -1 && lineno < 1) || lineno > a->nlines)
1356 return got_error(GOT_ERR_RANGE);
1359 return NULL; /* no change in this commit */
1361 /* Annotate this line. */
1362 bline = &a->lines[lineno - 1];
1363 if (bline->annotated)
1365 err = got_object_id_str(&bline->id_str, id);
1369 bline->committer = strdup(got_object_commit_get_committer(commit));
1370 if (bline->committer == NULL) {
1371 err = got_error_from_errno("strdup");
1375 committer_time = got_object_commit_get_committer_time(commit);
1376 if (gmtime_r(&committer_time, &tm) == NULL)
1377 return got_error_from_errno("gmtime_r");
1378 if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d",
1380 err = got_error(GOT_ERR_NO_SPACE);
1383 bline->annotated = 1;
1385 /* Print lines annotated so far. */
1386 bline = &a->lines[a->lineno_cur - 1];
1387 if (!bline->annotated)
1390 offset = a->line_offsets[a->lineno_cur - 1];
1391 if (fseeko(a->f, offset, SEEK_SET) == -1) {
1392 err = got_error_from_errno("fseeko");
1396 while (bline->annotated) {
1397 int out_buff_size = 100;
1398 char *smallerthan, *at, *nl, *committer;
1399 char out_buff[out_buff_size];
1402 if (getline(&line, &linesize, a->f) == -1) {
1404 err = got_error_from_errno("getline");
1408 committer = bline->committer;
1409 smallerthan = strchr(committer, '<');
1410 if (smallerthan && smallerthan[1] != '\0')
1411 committer = smallerthan + 1;
1412 at = strchr(committer, '@');
1415 len = strlen(committer);
1417 committer[8] = '\0';
1419 nl = strchr(line, '\n');
1423 if (fcgi_gen_response(c, "<div id='blame_wrapper'>") == -1)
1425 if (fcgi_gen_response(c, "<div id='blame_number'>") == -1)
1427 if (snprintf(out_buff, strlen(out_buff), "%.*d", a->nlines_prec,
1430 if (fcgi_gen_response(c, out_buff) == -1)
1432 if (fcgi_gen_response(c, "</div>") == -1)
1435 if (fcgi_gen_response(c, "<div id='blame_hash'>") == -1)
1438 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1440 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1442 if (fcgi_gen_response(c, "&path=") == -1)
1444 if (fcgi_gen_response(c, repo_dir->name) == -1)
1446 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1448 if (fcgi_gen_response(c, bline->id_str) == -1)
1450 if (fcgi_gen_response(c, "'>") == -1)
1452 if (snprintf(out_buff, 10, "%.8s", bline->id_str) < 0)
1454 if (fcgi_gen_response(c, out_buff) == -1)
1456 if (fcgi_gen_response(c, "</a></div>") == -1)
1459 if (fcgi_gen_response(c, "<div id='blame_date'>") == -1)
1461 if (fcgi_gen_response(c, bline->datebuf) == -1)
1463 if (fcgi_gen_response(c, "</div>") == -1)
1466 if (fcgi_gen_response(c, "<div id='blame_author'>") == -1)
1468 if (fcgi_gen_response(c, committer) == -1)
1470 if (fcgi_gen_response(c, "</div>") == -1)
1473 if (fcgi_gen_response(c, "<div id='blame_code'>") == -1)
1475 err = gotweb_escape_html(&eline, line);
1478 if (fcgi_gen_response(c, eline) == -1)
1480 if (fcgi_gen_response(c, "</div>") == -1)
1483 if (fcgi_gen_response(c, "</div>") == -1)
1486 bline = &a->lines[a->lineno_cur - 1];
1494 const struct got_error *
1495 got_output_file_blame(struct request *c)
1497 const struct got_error *error = NULL;
1498 struct transport *t = c->t;
1499 struct got_repository *repo = t->repo;
1500 struct querystring *qs = c->t->qs;
1501 struct got_object_id *obj_id = NULL, *commit_id = NULL;
1502 struct got_commit_object *commit = NULL;
1503 struct got_reflist_head refs;
1504 struct got_blob_object *blob = NULL;
1505 char *path = NULL, *in_repo_path = NULL;
1506 struct blame_cb_args bca;
1507 int i, obj_type, fd1 = -1, fd2 = -1, fd3 = -1, fd4 = -1, fd5 = -1;
1510 FILE *f1 = NULL, *f2 = NULL;
1516 if (asprintf(&path, "%s%s%s", qs->folder ? qs->folder : "",
1517 qs->folder ? "/" : "", qs->file) == -1) {
1518 error = got_error_from_errno("asprintf");
1522 error = got_repo_map_path(&in_repo_path, repo, path);
1526 error = got_repo_match_object_id(&commit_id, NULL, qs->commit,
1527 GOT_OBJ_TYPE_COMMIT, &refs, repo);
1531 error = got_object_open_as_commit(&commit, repo, commit_id);
1535 error = got_object_id_by_path(&obj_id, repo, commit, in_repo_path);
1539 if (commit_id == NULL) {
1540 error = got_error(GOT_ERR_NO_OBJ);
1544 error = got_object_get_type(&obj_type, repo, obj_id);
1548 if (obj_type != GOT_OBJ_TYPE_BLOB) {
1549 error = got_error(GOT_ERR_OBJ_TYPE);
1553 error = got_gotweb_openfile(&bca.f, &c->priv_fd[BLAME_FD_1], &fd1);
1557 error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_2], &fd2);
1561 error = got_object_open_as_blob(&blob, repo, obj_id, BUF, fd2);
1565 error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
1566 &bca.line_offsets, bca.f, blob);
1567 if (error || bca.nlines == 0)
1570 /* Don't include \n at EOF in the blame line count. */
1571 if (bca.line_offsets[bca.nlines - 1] == filesize)
1574 bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
1575 if (bca.lines == NULL) {
1576 error = got_error_from_errno("calloc");
1580 bca.nlines_prec = 0;
1589 error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_3], &fd3);
1593 error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_4], &fd4);
1597 error = got_gotweb_openfile(&f1, &c->priv_fd[BLAME_FD_5], &fd5);
1601 error = got_gotweb_openfile(&f2, &c->priv_fd[BLAME_FD_6], &fd6);
1605 error = got_blame(in_repo_path, commit_id, repo,
1606 GOT_DIFF_ALGORITHM_MYERS, got_gotweb_blame_cb, &bca, NULL, NULL,
1610 free(bca.line_offsets);
1611 for (i = 0; i < bca.nlines; i++) {
1612 struct blame_line *bline = &bca.lines[i];
1613 free(bline->id_str);
1614 free(bline->committer);
1619 if (fd2 != -1 && close(fd2) == -1 && error == NULL)
1620 error = got_error_from_errno("close");
1621 if (fd3 != -1 && close(fd3) == -1 && error == NULL)
1622 error = got_error_from_errno("close");
1623 if (fd4 != -1 && close(fd4) == -1 && error == NULL)
1624 error = got_error_from_errno("close");
1626 const struct got_error *bca_err =
1627 got_gotweb_flushfile(bca.f, fd1);
1632 const struct got_error *f1_err =
1633 got_gotweb_flushfile(f1, fd5);
1638 const struct got_error *f2_err =
1639 got_gotweb_flushfile(f2, fd6);
1644 got_object_commit_close(commit);
1646 got_object_blob_close(blob);
1653 const struct got_error *
1654 got_output_repo_diff(struct request *c)
1656 const struct got_error *error = NULL;
1657 struct transport *t = c->t;
1658 struct got_repository *repo = t->repo;
1659 struct repo_commit *rc = NULL;
1660 struct got_object_id *id1 = NULL, *id2 = NULL;
1661 struct got_reflist_head refs;
1662 FILE *f1 = NULL, *f2 = NULL, *f3 = NULL;
1663 char *label1 = NULL, *label2 = NULL, *line = NULL;
1664 char *newline, *eline = NULL, *color = NULL;
1665 int obj_type, fd1, fd2, fd3, fd4 = -1, fd5 = -1;
1666 size_t linesize = 0;
1672 error = got_gotweb_openfile(&f1, &c->priv_fd[DIFF_FD_1], &fd1);
1676 error = got_gotweb_openfile(&f2, &c->priv_fd[DIFF_FD_2], &fd2);
1680 error = got_gotweb_openfile(&f3, &c->priv_fd[DIFF_FD_3], &fd3);
1684 rc = TAILQ_FIRST(&t->repo_commits);
1686 if (rc->parent_id != NULL &&
1687 strncmp(rc->parent_id, "/dev/null", 9) != 0) {
1688 error = got_repo_match_object_id(&id1, &label1,
1689 rc->parent_id, GOT_OBJ_TYPE_ANY,
1695 error = got_repo_match_object_id(&id2, &label2, rc->commit_id,
1696 GOT_OBJ_TYPE_ANY, &refs, repo);
1700 error = got_object_get_type(&obj_type, repo, id2);
1704 error = got_gotweb_dupfd(&c->priv_fd[DIFF_FD_4], &fd4);
1708 error = got_gotweb_dupfd(&c->priv_fd[DIFF_FD_5], &fd5);
1713 case GOT_OBJ_TYPE_BLOB:
1714 error = got_diff_objects_as_blobs(NULL, NULL, f1, f2, fd4, fd5,
1715 id1, id2, NULL, NULL, GOT_DIFF_ALGORITHM_MYERS, 3, 0, 0,
1718 case GOT_OBJ_TYPE_TREE:
1719 error = got_diff_objects_as_trees(NULL, NULL, f1, f2, fd4, fd5,
1720 id1, id2, NULL, "", "", GOT_DIFF_ALGORITHM_MYERS, 3, 0, 0,
1723 case GOT_OBJ_TYPE_COMMIT:
1724 error = got_diff_objects_as_commits(NULL, NULL, f1, f2, fd4,
1725 fd5, id1, id2, NULL, GOT_DIFF_ALGORITHM_MYERS, 3, 0, 0,
1729 error = got_error(GOT_ERR_OBJ_TYPE);
1734 if (fseek(f1, 0, SEEK_SET) == -1) {
1735 error = got_ferror(f1, GOT_ERR_IO);
1739 if (fseek(f2, 0, SEEK_SET) == -1) {
1740 error = got_ferror(f2, GOT_ERR_IO);
1744 if (fseek(f3, 0, SEEK_SET) == -1) {
1745 error = got_ferror(f3, GOT_ERR_IO);
1749 while ((linelen = getline(&line, &linesize, f3)) != -1) {
1750 if (strncmp(line, "-", 1) == 0) {
1751 color = strdup("diff_minus");
1752 if (color == NULL) {
1753 error = got_error_from_errno("strdup");
1756 } else if (strncmp(line, "+", 1) == 0) {
1757 color = strdup("diff_plus");
1758 if (color == NULL) {
1759 error = got_error_from_errno("strdup");
1762 } else if (strncmp(line, "@@", 2) == 0) {
1763 color = strdup("diff_chunk_header");
1764 if (color == NULL) {
1765 error = got_error_from_errno("strdup");
1768 } else if (strncmp(line, "@@", 2) == 0) {
1769 color = strdup("diff_chunk_header");
1770 if (color == NULL) {
1771 error = got_error_from_errno("strdup");
1774 } else if (strncmp(line, "commit +", 8) == 0) {
1775 color = strdup("diff_meta");
1776 if (color == NULL) {
1777 error = got_error_from_errno("strdup");
1780 } else if (strncmp(line, "commit -", 8) == 0) {
1781 color = strdup("diff_meta");
1782 if (color == NULL) {
1783 error = got_error_from_errno("strdup");
1786 } else if (strncmp(line, "blob +", 6) == 0) {
1787 color = strdup("diff_meta");
1788 if (color == NULL) {
1789 error = got_error_from_errno("strdup");
1792 } else if (strncmp(line, "blob -", 6) == 0) {
1793 color = strdup("diff_meta");
1794 if (color == NULL) {
1795 error = got_error_from_errno("strdup");
1798 } else if (strncmp(line, "file +", 6) == 0) {
1799 color = strdup("diff_meta");
1800 if (color == NULL) {
1801 error = got_error_from_errno("strdup");
1804 } else if (strncmp(line, "file -", 6) == 0) {
1805 color = strdup("diff_meta");
1806 if (color == NULL) {
1807 error = got_error_from_errno("strdup");
1810 } else if (strncmp(line, "from:", 5) == 0) {
1811 color = strdup("diff_author");
1812 if (color == NULL) {
1813 error = got_error_from_errno("strdup");
1816 } else if (strncmp(line, "via:", 4) == 0) {
1817 color = strdup("diff_author");
1818 if (color == NULL) {
1819 error = got_error_from_errno("strdup");
1822 } else if (strncmp(line, "date:", 5) == 0) {
1823 color = strdup("diff_date");
1824 if (color == NULL) {
1825 error = got_error_from_errno("strdup");
1829 if (fcgi_gen_response(c, "<div id='diff_line' class='") == -1)
1831 if (fcgi_gen_response(c, color ? color : "") == -1)
1833 if (fcgi_gen_response(c, "'>") == -1)
1835 newline = strchr(line, '\n');
1839 error = gotweb_escape_html(&eline, line);
1842 if (fcgi_gen_response(c, eline) == -1)
1847 if (fcgi_gen_response(c, "</div>\n") == -1)
1850 wrlen = wrlen + linelen;
1854 if (linelen == -1 && ferror(f3))
1855 error = got_error_from_errno("getline");
1858 if (fd4 != -1 && close(fd4) == -1 && error == NULL)
1859 error = got_error_from_errno("close");
1860 if (fd5 != -1 && close(fd5) == -1 && error == NULL)
1861 error = got_error_from_errno("close");
1863 const struct got_error *f1_err =
1864 got_gotweb_flushfile(f1, fd1);
1869 const struct got_error *f2_err =
1870 got_gotweb_flushfile(f2, fd2);
1875 const struct got_error *f3_err =
1876 got_gotweb_flushfile(f3, fd3);
1880 got_ref_list_free(&refs);
1890 static const struct got_error *
1891 got_init_repo_commit(struct repo_commit **rc)
1893 const struct got_error *error = NULL;
1895 *rc = calloc(1, sizeof(**rc));
1897 return got_error_from_errno2("%s: calloc", __func__);
1900 (*rc)->refs_str = NULL;
1901 (*rc)->commit_id = NULL;
1902 (*rc)->committer = NULL;
1903 (*rc)->author = NULL;
1904 (*rc)->parent_id = NULL;
1905 (*rc)->tree_id = NULL;
1906 (*rc)->commit_msg = NULL;
1911 static const struct got_error *
1912 got_init_repo_tag(struct repo_tag **rt)
1914 const struct got_error *error = NULL;
1916 *rt = calloc(1, sizeof(**rt));
1918 return got_error_from_errno2("%s: calloc", __func__);
1920 (*rt)->commit_id = NULL;
1921 (*rt)->tag_name = NULL;
1922 (*rt)->tag_commit = NULL;
1923 (*rt)->commit_msg = NULL;
1924 (*rt)->tagger = NULL;