portable: add back sys/queue.h
[got-portable.git] / gotwebd / got_operations.c
blob5bc830293ba12e23a7db3496d2dab9e0ab9805c0
1 /*
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/queue.h>
19 #include <sys/queue.h>
20 #include <sys/socket.h>
21 #include <sys/stat.h>
23 #include <event.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <unistd.h>
29 #include "got_error.h"
30 #include "got_object.h"
31 #include "got_reference.h"
32 #include "got_repository.h"
33 #include "got_path.h"
34 #include "got_cancel.h"
35 #include "got_diff.h"
36 #include "got_commit_graph.h"
37 #include "got_blame.h"
38 #include "got_privsep.h"
40 #include "got_compat.h"
42 #include "proc.h"
43 #include "gotwebd.h"
45 static const struct got_error *got_init_repo_commit(struct repo_commit **);
46 static const struct got_error *got_init_repo_tag(struct repo_tag **);
47 static const struct got_error *got_get_repo_commit(struct request *,
48 struct repo_commit *, struct got_commit_object *, struct got_reflist_head *,
49 struct got_object_id *);
50 static const struct got_error *got_gotweb_dupfd(int *, int *);
51 static const struct got_error *got_gotweb_openfile(FILE **, int *, int *);
52 static const struct got_error *got_gotweb_flushfile(FILE *, int);
53 static const struct got_error *got_gotweb_blame_cb(void *, int, int,
54 struct got_commit_object *,struct got_object_id *);
56 static int
57 isbinary(const uint8_t *buf, size_t n)
59 size_t i;
61 for (i = 0; i < n; i++)
62 if (buf[i] == 0)
63 return 1;
64 return 0;
68 static const struct got_error *
69 got_gotweb_flushfile(FILE *f, int fd)
71 if (fseek(f, 0, SEEK_SET) == -1)
72 return got_error_from_errno("fseek");
74 if (ftruncate(fd, 0) == -1)
75 return got_error_from_errno("ftruncate");
77 if (fsync(fd) == -1)
78 return got_error_from_errno("fsync");
80 if (f && fclose(f) == EOF)
81 return got_error_from_errno("fclose");
83 if (fd != -1 && close(fd) != -1)
84 return got_error_from_errno("close");
86 return NULL;
89 static const struct got_error *
90 got_gotweb_openfile(FILE **f, int *priv_fd, int *fd)
92 const struct got_error *error = NULL;
94 *fd = dup(*priv_fd);
96 if (*fd < 0)
97 return NULL;
99 *f = fdopen(*fd, "w+");
100 if (*f == NULL) {
101 close(*fd);
102 error = got_error(GOT_ERR_PRIVSEP_NO_FD);
105 return error;
108 static const struct got_error *
109 got_gotweb_dupfd(int *priv_fd, int *fd)
111 const struct got_error *error = NULL;
113 *fd = dup(*priv_fd);
115 if (*fd < 0)
116 return NULL;
118 return error;
121 const struct got_error *
122 got_get_repo_owner(char **owner, struct request *c, char *dir)
124 const struct got_error *error = NULL;
125 struct server *srv = c->srv;
126 struct transport *t = c->t;
127 struct got_repository *repo = t->repo;
128 const char *gitconfig_owner;
130 *owner = NULL;
132 if (srv->show_repo_owner == 0)
133 return NULL;
135 gitconfig_owner = got_repo_get_gitconfig_owner(repo);
136 if (gitconfig_owner) {
137 *owner = strdup(gitconfig_owner);
138 if (*owner == NULL)
139 return got_error_from_errno("strdup");
141 return error;
144 const struct got_error *
145 got_get_repo_age(char **repo_age, struct request *c, char *dir,
146 const char *refname, int ref_tm)
148 const struct got_error *error = NULL;
149 struct server *srv = c->srv;
150 struct transport *t = c->t;
151 struct got_repository *repo = t->repo;
152 struct got_commit_object *commit = NULL;
153 struct got_reflist_head refs;
154 struct got_reflist_entry *re;
155 time_t committer_time = 0, cmp_time = 0;
157 *repo_age = NULL;
158 TAILQ_INIT(&refs);
160 if (srv->show_repo_age == 0)
161 return NULL;
163 error = got_ref_list(&refs, repo, "refs/heads",
164 got_ref_cmp_by_name, NULL);
165 if (error)
166 goto done;
169 * Find the youngest branch tip in the repository, or the age of
170 * the a specific branch tip if a name was provided by the caller.
172 TAILQ_FOREACH(re, &refs, entry) {
173 struct got_object_id *id = NULL;
175 if (refname && strcmp(got_ref_get_name(re->ref), refname) != 0)
176 continue;
178 error = got_ref_resolve(&id, repo, re->ref);
179 if (error)
180 goto done;
182 error = got_object_open_as_commit(&commit, repo, id);
183 free(id);
184 if (error)
185 goto done;
187 committer_time =
188 got_object_commit_get_committer_time(commit);
189 got_object_commit_close(commit);
190 if (cmp_time < committer_time)
191 cmp_time = committer_time;
193 if (refname)
194 break;
197 if (cmp_time != 0) {
198 committer_time = cmp_time;
199 error = gotweb_get_time_str(repo_age, committer_time, ref_tm);
201 done:
202 got_ref_list_free(&refs);
203 return error;
206 static const struct got_error *
207 got_get_repo_commit(struct request *c, struct repo_commit *repo_commit,
208 struct got_commit_object *commit, struct got_reflist_head *refs,
209 struct got_object_id *id)
211 const struct got_error *error = NULL;
212 struct got_reflist_entry *re;
213 struct got_object_id *id2 = NULL;
214 struct got_object_qid *parent_id;
215 struct transport *t = c->t;
216 struct querystring *qs = c->t->qs;
217 char *commit_msg = NULL, *commit_msg0;
219 TAILQ_FOREACH(re, refs, entry) {
220 char *s;
221 const char *name;
222 struct got_tag_object *tag = NULL;
223 struct got_object_id *ref_id;
224 int cmp;
226 if (got_ref_is_symbolic(re->ref))
227 continue;
229 name = got_ref_get_name(re->ref);
230 if (strncmp(name, "refs/", 5) == 0)
231 name += 5;
232 if (strncmp(name, "got/", 4) == 0)
233 continue;
234 if (strncmp(name, "heads/", 6) == 0)
235 name += 6;
236 if (strncmp(name, "remotes/", 8) == 0) {
237 name += 8;
238 s = strstr(name, "/" GOT_REF_HEAD);
239 if (s != NULL && s[strlen(s)] == '\0')
240 continue;
242 error = got_ref_resolve(&ref_id, t->repo, re->ref);
243 if (error)
244 return error;
245 if (strncmp(name, "tags/", 5) == 0) {
246 error = got_object_open_as_tag(&tag, t->repo, ref_id);
247 if (error) {
248 if (error->code != GOT_ERR_OBJ_TYPE) {
249 free(ref_id);
250 continue;
253 * Ref points at something other
254 * than a tag.
256 error = NULL;
257 tag = NULL;
260 cmp = got_object_id_cmp(tag ?
261 got_object_tag_get_object_id(tag) : ref_id, id);
262 free(ref_id);
263 if (tag)
264 got_object_tag_close(tag);
265 if (cmp != 0)
266 continue;
267 s = repo_commit->refs_str;
268 if (asprintf(&repo_commit->refs_str, "%s%s%s", s ? s : "",
269 s ? ", " : "", name) == -1) {
270 error = got_error_from_errno("asprintf");
271 free(s);
272 repo_commit->refs_str = NULL;
273 return error;
275 free(s);
278 error = got_object_id_str(&repo_commit->commit_id, id);
279 if (error)
280 return error;
282 error = got_object_id_str(&repo_commit->tree_id,
283 got_object_commit_get_tree_id(commit));
284 if (error)
285 return error;
287 if (qs->action == DIFF) {
288 parent_id = STAILQ_FIRST(
289 got_object_commit_get_parent_ids(commit));
290 if (parent_id != NULL) {
291 id2 = got_object_id_dup(&parent_id->id);
292 error = got_object_id_str(&repo_commit->parent_id, id2);
293 if (error)
294 return error;
295 free(id2);
296 } else {
297 repo_commit->parent_id = strdup("/dev/null");
298 if (repo_commit->parent_id == NULL) {
299 error = got_error_from_errno("strdup");
300 return error;
305 repo_commit->committer_time =
306 got_object_commit_get_committer_time(commit);
308 repo_commit->author =
309 strdup(got_object_commit_get_author(commit));
310 if (repo_commit->author == NULL) {
311 error = got_error_from_errno("strdup");
312 return error;
314 repo_commit->committer =
315 strdup(got_object_commit_get_committer(commit));
316 if (repo_commit->committer == NULL) {
317 error = got_error_from_errno("strdup");
318 return error;
320 error = got_object_commit_get_logmsg(&commit_msg0, commit);
321 if (error)
322 return error;
324 commit_msg = commit_msg0;
325 while (*commit_msg == '\n')
326 commit_msg++;
328 repo_commit->commit_msg = strdup(commit_msg);
329 if (repo_commit->commit_msg == NULL)
330 error = got_error_from_errno("strdup");
331 free(commit_msg0);
332 return error;
335 const struct got_error *
336 got_get_repo_commits(struct request *c, int limit)
338 const struct got_error *error = NULL;
339 struct got_object_id *id = NULL;
340 struct got_commit_graph *graph = NULL;
341 struct got_commit_object *commit = NULL;
342 struct got_reflist_head refs;
343 struct got_reference *ref;
344 struct repo_commit *repo_commit = NULL;
345 struct server *srv = c->srv;
346 struct transport *t = c->t;
347 struct got_repository *repo = t->repo;
348 struct querystring *qs = t->qs;
349 struct repo_dir *repo_dir = t->repo_dir;
350 char *in_repo_path = NULL, *repo_path = NULL, *file_path = NULL;
351 int chk_next = 0, chk_multi = 0, commit_found = 0;
352 int obj_type, limit_chk = 0;
354 TAILQ_INIT(&refs);
356 if (qs->file != NULL && strlen(qs->file) > 0)
357 if (asprintf(&file_path, "%s/%s", qs->folder ? qs->folder : "",
358 qs->file) == -1)
359 return got_error_from_errno("asprintf");
361 if (asprintf(&repo_path, "%s/%s", srv->repos_path,
362 repo_dir->name) == -1)
363 return got_error_from_errno("asprintf");
365 error = got_init_repo_commit(&repo_commit);
366 if (error)
367 return error;
370 * XXX: jumping directly to a commit id via
371 * got_repo_match_object_id_prefix significantly improves performance,
372 * but does not allow us to create a PREVIOUS button, since commits can
373 * only be itereated forward. So, we have to match as we iterate from
374 * the headref.
376 if (qs->action == BRIEFS || qs->action == COMMITS ||
377 (qs->action == TREE && qs->commit == NULL)) {
378 error = got_ref_open(&ref, repo, qs->headref, 0);
379 if (error)
380 goto done;
382 error = got_ref_resolve(&id, repo, ref);
383 got_ref_close(ref);
384 if (error)
385 goto done;
386 } else if (qs->commit != NULL) {
387 error = got_ref_open(&ref, repo, qs->commit, 0);
388 if (error == NULL) {
389 error = got_ref_resolve(&id, repo, ref);
390 if (error)
391 goto done;
392 error = got_object_get_type(&obj_type, repo, id);
393 got_ref_close(ref);
394 if (error)
395 goto done;
396 if (obj_type == GOT_OBJ_TYPE_TAG) {
397 struct got_tag_object *tag;
398 error = got_object_open_as_tag(&tag, repo, id);
399 if (error)
400 goto done;
401 if (got_object_tag_get_object_type(tag) !=
402 GOT_OBJ_TYPE_COMMIT) {
403 got_object_tag_close(tag);
404 error = got_error(GOT_ERR_OBJ_TYPE);
405 goto done;
407 free(id);
408 id = got_object_id_dup(
409 got_object_tag_get_object_id(tag));
410 if (id == NULL)
411 error = got_error_from_errno(
412 "got_object_id_dup");
413 got_object_tag_close(tag);
414 if (error)
415 goto done;
416 } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
417 error = got_error(GOT_ERR_OBJ_TYPE);
418 goto done;
421 error = got_repo_match_object_id_prefix(&id, qs->commit,
422 GOT_OBJ_TYPE_COMMIT, repo);
423 if (error)
424 goto done;
427 error = got_repo_map_path(&in_repo_path, repo, repo_path);
428 if (error)
429 goto done;
431 error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
432 if (error)
433 goto done;
435 if (qs->file != NULL && strlen(qs->file) > 0) {
436 error = got_commit_graph_open(&graph, file_path, 0);
437 if (error)
438 goto done;
439 } else {
440 error = got_commit_graph_open(&graph, in_repo_path, 0);
441 if (error)
442 goto done;
445 error = got_commit_graph_iter_start(graph, id, repo, NULL, NULL);
446 if (error)
447 goto done;
449 for (;;) {
450 if (limit_chk == ((limit * qs->page) - (limit - 1)) &&
451 commit_found == 0 && repo_commit->commit_id != NULL) {
452 t->prev_id = strdup(repo_commit->commit_id);
453 if (t->prev_id == NULL) {
454 error = got_error_from_errno("strdup");
455 goto done;
459 error = got_commit_graph_iter_next(&id, graph, repo, NULL,
460 NULL);
461 if (error) {
462 if (error->code == GOT_ERR_ITER_COMPLETED)
463 error = NULL;
464 goto done;
466 if (id == NULL)
467 goto done;
469 error = got_object_open_as_commit(&commit, repo, id);
470 if (error)
471 goto done;
473 error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name,
474 NULL);
475 if (error)
476 goto done;
478 error = got_get_repo_commit(c, repo_commit, commit,
479 &refs, id);
480 if (error)
481 goto done;
483 if (qs->commit != NULL && commit_found == 0 && limit != 1) {
484 if (strcmp(qs->commit, repo_commit->commit_id) == 0)
485 commit_found = 1;
486 else if (qs->file != NULL && strlen(qs->file) > 0 &&
487 qs->page == 0)
488 commit_found = 1;
489 else {
490 limit_chk++;
491 free(id);
492 id = NULL;
493 continue;
497 struct repo_commit *new_repo_commit = NULL;
498 error = got_init_repo_commit(&new_repo_commit);
499 if (error)
500 goto done;
502 TAILQ_INSERT_TAIL(&t->repo_commits, new_repo_commit, entry);
504 error = got_get_repo_commit(c, new_repo_commit, commit,
505 &refs, id);
506 if (error)
507 goto done;
509 free(id);
510 id = NULL;
512 if (limit == 1 && chk_multi == 0 &&
513 srv->max_commits_display != 1)
514 commit_found = 1;
515 else {
516 chk_multi = 1;
519 * check for one more commit before breaking,
520 * so we know whether to navigate through briefs
521 * commits and summary
523 if (chk_next && (qs->action == BRIEFS ||
524 qs->action == COMMITS || qs->action == SUMMARY)) {
525 t->next_id = strdup(new_repo_commit->commit_id);
526 if (t->next_id == NULL) {
527 error = got_error_from_errno("strdup");
528 goto done;
530 if (commit) {
531 got_object_commit_close(commit);
532 commit = NULL;
534 if (t->next_id == NULL) {
535 error = got_error_from_errno("strdup");
536 goto done;
538 TAILQ_REMOVE(&t->repo_commits, new_repo_commit,
539 entry);
540 gotweb_free_repo_commit(new_repo_commit);
541 goto done;
544 got_ref_list_free(&refs);
545 if (error || (limit && --limit == 0)) {
546 if (commit_found || (qs->file != NULL &&
547 strlen(qs->file) > 0))
548 if (chk_multi == 0)
549 break;
550 chk_next = 1;
552 if (commit) {
553 got_object_commit_close(commit);
554 commit = NULL;
557 done:
558 gotweb_free_repo_commit(repo_commit);
559 if (commit)
560 got_object_commit_close(commit);
561 if (graph)
562 got_commit_graph_close(graph);
563 got_ref_list_free(&refs);
564 free(file_path);
565 free(repo_path);
566 free(id);
567 return error;
570 const struct got_error *
571 got_get_repo_tags(struct request *c, int limit)
573 const struct got_error *error = NULL;
574 struct got_object_id *id = NULL;
575 struct got_commit_object *commit = NULL;
576 struct got_reflist_head refs;
577 struct got_reference *ref;
578 struct got_reflist_entry *re;
579 struct server *srv = c->srv;
580 struct transport *t = c->t;
581 struct got_repository *repo = t->repo;
582 struct querystring *qs = t->qs;
583 struct repo_dir *repo_dir = t->repo_dir;
584 struct got_tag_object *tag = NULL;
585 struct repo_tag *rt = NULL, *trt = NULL;
586 char *in_repo_path = NULL, *repo_path = NULL, *id_str = NULL;
587 char *commit_msg = NULL, *commit_msg0 = NULL;
588 int chk_next = 0, chk_multi = 1, commit_found = 0, c_cnt = 0;
590 TAILQ_INIT(&refs);
592 if (asprintf(&repo_path, "%s/%s", srv->repos_path,
593 repo_dir->name) == -1)
594 return got_error_from_errno("asprintf");
596 if (error)
597 return error;
599 if (qs->commit == NULL && qs->action == TAGS) {
600 error = got_ref_open(&ref, repo, qs->headref, 0);
601 if (error)
602 goto err;
603 error = got_ref_resolve(&id, repo, ref);
604 got_ref_close(ref);
605 if (error)
606 goto err;
607 } else if (qs->commit == NULL && qs->action == TAG) {
608 error = got_error_msg(GOT_ERR_EOF, "commit id missing");
609 goto err;
610 } else {
611 error = got_repo_match_object_id_prefix(&id, qs->commit,
612 GOT_OBJ_TYPE_COMMIT, repo);
613 if (error)
614 goto err;
617 if (qs->action != SUMMARY && qs->action != TAGS) {
618 error = got_object_open_as_commit(&commit, repo, id);
619 if (error)
620 goto err;
621 error = got_object_commit_get_logmsg(&commit_msg0, commit);
622 if (error)
623 goto err;
624 if (commit) {
625 got_object_commit_close(commit);
626 commit = NULL;
630 error = got_repo_map_path(&in_repo_path, repo, repo_path);
631 if (error)
632 goto err;
634 error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags,
635 repo);
636 if (error)
637 goto err;
639 if (limit == 1)
640 chk_multi = 0;
643 * XXX: again, see previous message about caching
646 TAILQ_FOREACH(re, &refs, entry) {
647 struct repo_tag *new_repo_tag = NULL;
648 error = got_init_repo_tag(&new_repo_tag);
649 if (error)
650 goto err;
652 TAILQ_INSERT_TAIL(&t->repo_tags, new_repo_tag, entry);
654 new_repo_tag->tag_name = strdup(got_ref_get_name(re->ref));
655 if (new_repo_tag->tag_name == NULL) {
656 error = got_error_from_errno("strdup");
657 goto err;
660 error = got_ref_resolve(&id, repo, re->ref);
661 if (error)
662 goto done;
664 error = got_object_open_as_tag(&tag, repo, id);
665 if (error) {
666 if (error->code != GOT_ERR_OBJ_TYPE) {
667 free(id);
668 id = NULL;
669 goto done;
671 /* "lightweight" tag */
672 error = got_object_open_as_commit(&commit, repo, id);
673 if (error) {
674 free(id);
675 id = NULL;
676 goto done;
678 new_repo_tag->tagger =
679 strdup(got_object_commit_get_committer(commit));
680 if (new_repo_tag->tagger == NULL) {
681 error = got_error_from_errno("strdup");
682 goto err;
684 new_repo_tag->tagger_time =
685 got_object_commit_get_committer_time(commit);
686 error = got_object_id_str(&id_str, id);
687 if (error)
688 goto err;
689 free(id);
690 id = NULL;
691 } else {
692 free(id);
693 id = NULL;
694 new_repo_tag->tagger =
695 strdup(got_object_tag_get_tagger(tag));
696 if (new_repo_tag->tagger == NULL) {
697 error = got_error_from_errno("strdup");
698 goto err;
700 new_repo_tag->tagger_time =
701 got_object_tag_get_tagger_time(tag);
702 error = got_object_id_str(&id_str,
703 got_object_tag_get_object_id(tag));
704 if (error)
705 goto err;
708 new_repo_tag->commit_id = strdup(id_str);
709 if (new_repo_tag->commit_id == NULL)
710 goto err;
712 if (commit_found == 0 && qs->commit != NULL &&
713 strncmp(id_str, qs->commit, strlen(id_str)) != 0)
714 continue;
715 else
716 commit_found = 1;
718 t->tag_count++;
721 * check for one more commit before breaking,
722 * so we know whether to navigate through briefs
723 * commits and summary
725 if (chk_next) {
726 t->next_id = strdup(new_repo_tag->commit_id);
727 if (t->next_id == NULL) {
728 error = got_error_from_errno("strdup");
729 goto err;
731 if (commit) {
732 got_object_commit_close(commit);
733 commit = NULL;
735 if (t->next_id == NULL) {
736 error = got_error_from_errno("strdup");
737 goto err;
739 TAILQ_REMOVE(&t->repo_tags, new_repo_tag, entry);
740 gotweb_free_repo_tag(new_repo_tag);
741 goto done;
744 if (commit) {
745 error = got_object_commit_get_logmsg(&new_repo_tag->
746 tag_commit, commit);
747 if (error)
748 goto done;
749 got_object_commit_close(commit);
750 commit = NULL;
751 } else {
752 new_repo_tag->tag_commit =
753 strdup(got_object_tag_get_message(tag));
754 if (new_repo_tag->tag_commit == NULL) {
755 error = got_error_from_errno("strdup");
756 goto done;
760 while (*new_repo_tag->tag_commit == '\n')
761 new_repo_tag->tag_commit++;
763 if (qs->action != SUMMARY && qs->action != TAGS) {
764 commit_msg = commit_msg0;
765 while (*commit_msg == '\n')
766 commit_msg++;
768 new_repo_tag->commit_msg = strdup(commit_msg);
769 if (new_repo_tag->commit_msg == NULL) {
770 error = got_error_from_errno("strdup");
771 free(commit_msg0);
772 goto err;
774 free(commit_msg0);
777 if (limit && --limit == 0) {
778 if (chk_multi == 0)
779 break;
780 chk_next = 1;
782 free(id);
783 id = NULL;
786 done:
788 * we have tailq populated, so find previous commit id
789 * for navigation through briefs and commits
791 if (t->tag_count == 0) {
792 TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
793 TAILQ_REMOVE(&t->repo_tags, rt, entry);
794 gotweb_free_repo_tag(rt);
797 if (t->tag_count > 0 && t->prev_id == NULL && qs->commit != NULL) {
798 commit_found = 0;
799 TAILQ_FOREACH_REVERSE(rt, &t->repo_tags, repo_tags_head,
800 entry) {
801 if (commit_found == 0 && rt->commit_id != NULL &&
802 strcmp(qs->commit, rt->commit_id) != 0) {
803 continue;
804 } else
805 commit_found = 1;
806 if (c_cnt == srv->max_commits_display ||
807 rt == TAILQ_FIRST(&t->repo_tags)) {
808 t->prev_id = strdup(rt->commit_id);
809 if (t->prev_id == NULL)
810 error = got_error_from_errno("strdup");
811 break;
813 c_cnt++;
816 err:
817 if (commit)
818 got_object_commit_close(commit);
819 got_ref_list_free(&refs);
820 free(repo_path);
821 free(id);
822 return error;
825 const struct got_error *
826 got_output_repo_tree(struct request *c)
828 const struct got_error *error = NULL;
829 struct transport *t = c->t;
830 struct got_commit_object *commit = NULL;
831 struct got_repository *repo = t->repo;
832 struct querystring *qs = t->qs;
833 struct repo_commit *rc = NULL;
834 struct got_object_id *tree_id = NULL, *commit_id = NULL;
835 struct got_reflist_head refs;
836 struct got_tree_object *tree = NULL;
837 struct repo_dir *repo_dir = t->repo_dir;
838 char *id_str = NULL;
839 char *path = NULL, *in_repo_path = NULL, *build_folder = NULL;
840 char *modestr = NULL, *name = NULL, *class = NULL;
841 int nentries, i, class_flip = 0;
843 TAILQ_INIT(&refs);
845 rc = TAILQ_FIRST(&t->repo_commits);
847 if (qs->folder != NULL) {
848 path = strdup(qs->folder);
849 if (path == NULL) {
850 error = got_error_from_errno("strdup");
851 goto done;
853 } else {
854 error = got_repo_map_path(&in_repo_path, repo, repo_dir->path);
855 if (error)
856 goto done;
857 free(path);
858 path = in_repo_path;
861 error = got_repo_match_object_id(&commit_id, NULL, rc->commit_id,
862 GOT_OBJ_TYPE_COMMIT, &refs, repo);
863 if (error)
864 goto done;
866 error = got_object_open_as_commit(&commit, repo, commit_id);
867 if (error)
868 goto done;
870 error = got_object_id_by_path(&tree_id, repo, commit, path);
871 if (error)
872 goto done;
874 error = got_object_open_as_tree(&tree, repo, tree_id);
875 if (error)
876 goto done;
878 nentries = got_object_tree_get_nentries(tree);
880 for (i = 0; i < nentries; i++) {
881 struct got_tree_entry *te;
882 mode_t mode;
884 te = got_object_tree_get_entry(tree, i);
886 error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
887 if (error)
888 goto done;
890 modestr = strdup("");
891 if (modestr == NULL) {
892 error = got_error_from_errno("strdup");
893 goto done;
895 mode = got_tree_entry_get_mode(te);
896 if (got_object_tree_entry_is_submodule(te)) {
897 free(modestr);
898 modestr = strdup("$");
899 if (modestr == NULL) {
900 error = got_error_from_errno("strdup");
901 goto done;
903 } else if (S_ISLNK(mode)) {
904 free(modestr);
905 modestr = strdup("@");
906 if (modestr == NULL) {
907 error = got_error_from_errno("strdup");
908 goto done;
910 } else if (S_ISDIR(mode)) {
911 free(modestr);
912 modestr = strdup("/");
913 if (modestr == NULL) {
914 error = got_error_from_errno("strdup");
915 goto done;
917 } else if (mode & S_IXUSR) {
918 free(modestr);
919 modestr = strdup("*");
920 if (modestr == NULL) {
921 error = got_error_from_errno("strdup");
922 goto done;
926 if (class_flip == 0) {
927 class = strdup("back_lightgray");
928 if (class == NULL) {
929 error = got_error_from_errno("strdup");
930 goto done;
932 class_flip = 1;
933 } else {
934 class = strdup("back_white");
935 if (class == NULL) {
936 error = got_error_from_errno("strdup");
937 goto done;
939 class_flip = 0;
942 name = strdup(got_tree_entry_get_name(te));
943 if (name == NULL) {
944 error = got_error_from_errno("strdup");
945 goto done;
947 if (S_ISDIR(mode)) {
948 if (asprintf(&build_folder, "%s/%s",
949 qs->folder ? qs->folder : "",
950 got_tree_entry_get_name(te)) == -1) {
951 error = got_error_from_errno("asprintf");
952 goto done;
955 if (fcgi_gen_response(c,
956 "<div id='tree_wrapper'>\n") == -1)
957 goto done;
959 if (fcgi_gen_response(c, "<div id='tree_line' "
960 "class='") == -1)
961 goto done;
962 if (fcgi_gen_response(c, class) == -1)
963 goto done;
964 if (fcgi_gen_response(c, "'>") == -1)
965 goto done;
967 if (fcgi_gen_response(c, "<a class='diff_directory' "
968 "href='?index_page=") == -1)
969 goto done;
970 if (fcgi_gen_response(c, qs->index_page_str) == -1)
971 goto done;
972 if (fcgi_gen_response(c, "&path=") == -1)
973 goto done;
974 if (fcgi_gen_response(c, qs->path) == -1)
975 goto done;
976 if (fcgi_gen_response(c, "&action=tree") == -1)
977 goto done;
978 if (fcgi_gen_response(c, "&commit=") == -1)
979 goto done;
980 if (fcgi_gen_response(c, rc->commit_id) == -1)
981 goto done;
982 if (fcgi_gen_response(c, "&folder=") == -1)
983 goto done;
984 if (fcgi_gen_response(c, build_folder) == -1)
985 goto done;
986 if (fcgi_gen_response(c, "'>") == -1)
987 goto done;
988 if (fcgi_gen_response(c, name) == -1)
989 goto done;
990 if (fcgi_gen_response(c, modestr) == -1)
991 goto done;
992 if (fcgi_gen_response(c, "</a>") == -1)
993 goto done;
995 if (fcgi_gen_response(c, "</div>\n") == -1)
996 goto done;
998 if (fcgi_gen_response(c, "<div id='tree_line_blank' "
999 "class='") == -1)
1000 goto done;
1001 if (fcgi_gen_response(c, class) == -1)
1002 goto done;
1003 if (fcgi_gen_response(c, "'>") == -1)
1004 goto done;
1005 if (fcgi_gen_response(c, "&nbsp;") == -1)
1006 goto done;
1007 if (fcgi_gen_response(c, "</div>\n") == -1)
1008 goto done;
1010 if (fcgi_gen_response(c, "</div>\n") == -1)
1011 goto done;
1013 } else {
1014 free(name);
1015 name = strdup(got_tree_entry_get_name(te));
1016 if (name == NULL) {
1017 error = got_error_from_errno("strdup");
1018 goto done;
1021 if (fcgi_gen_response(c,
1022 "<div id='tree_wrapper'>\n") == -1)
1023 goto done;
1024 if (fcgi_gen_response(c, "<div id='tree_line' "
1025 "class='") == -1)
1026 goto done;
1027 if (fcgi_gen_response(c, class) == -1)
1028 goto done;
1029 if (fcgi_gen_response(c, "'>") == -1)
1030 goto done;
1032 if (fcgi_gen_response(c,
1033 "<a href='?index_page=") == -1)
1034 goto done;
1036 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1037 goto done;
1039 if (fcgi_gen_response(c, "&path=") == -1)
1040 goto done;
1041 if (fcgi_gen_response(c, qs->path) == -1)
1042 goto done;
1044 if (fcgi_gen_response(c, "&action=blob") == -1)
1045 goto done;
1047 if (fcgi_gen_response(c, "&commit=") == -1)
1048 goto done;
1049 if (fcgi_gen_response(c, rc->commit_id) == -1)
1050 goto done;
1052 if (fcgi_gen_response(c, "&folder=") == -1)
1053 goto done;
1054 if (fcgi_gen_response(c, qs->folder) == -1)
1055 goto done;
1057 if (fcgi_gen_response(c, "&file=") == -1)
1058 goto done;
1059 if (fcgi_gen_response(c, name) == -1)
1060 goto done;
1062 if (fcgi_gen_response(c, "'>") == -1)
1063 goto done;
1064 if (fcgi_gen_response(c, name) == -1)
1065 goto done;
1066 if (fcgi_gen_response(c, modestr) == -1)
1067 goto done;
1069 if (fcgi_gen_response(c, "</a>") == -1)
1070 goto done;
1072 if (fcgi_gen_response(c, "</div>\n") == -1)
1073 goto done;
1075 if (fcgi_gen_response(c, "<div id='tree_line_blank' "
1076 "class='") == -1)
1077 goto done;
1078 if (fcgi_gen_response(c, class) == -1)
1079 goto done;
1080 if (fcgi_gen_response(c, "'>") == -1)
1081 goto done;
1083 if (fcgi_gen_response(c,
1084 "<a href='?index_page=") == -1)
1085 goto done;
1087 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1088 goto done;
1090 if (fcgi_gen_response(c, "&path=") == -1)
1091 goto done;
1092 if (fcgi_gen_response(c, qs->path) == -1)
1093 goto done;
1095 if (fcgi_gen_response(c, "&action=commits") == -1)
1096 goto done;
1098 if (fcgi_gen_response(c, "&commit=") == -1)
1099 goto done;
1100 if (fcgi_gen_response(c, rc->commit_id) == -1)
1101 goto done;
1103 if (fcgi_gen_response(c, "&folder=") == -1)
1104 goto done;
1105 if (fcgi_gen_response(c, qs->folder) == -1)
1106 goto done;
1108 if (fcgi_gen_response(c, "&file=") == -1)
1109 goto done;
1110 if (fcgi_gen_response(c, name) == -1)
1111 goto done;
1113 if (fcgi_gen_response(c, "'>") == -1)
1114 goto done;
1116 if (fcgi_gen_response(c, "commits") == -1)
1117 goto done;
1118 if (fcgi_gen_response(c, "</a>\n") == -1)
1119 goto done;
1121 if (fcgi_gen_response(c, " | \n") == -1)
1122 goto done;
1124 if (fcgi_gen_response(c,
1125 "<a href='?index_page=") == -1)
1126 goto done;
1128 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1129 goto done;
1131 if (fcgi_gen_response(c, "&path=") == -1)
1132 goto done;
1133 if (fcgi_gen_response(c, qs->path) == -1)
1134 goto done;
1136 if (fcgi_gen_response(c, "&action=blame") == -1)
1137 goto done;
1139 if (fcgi_gen_response(c, "&commit=") == -1)
1140 goto done;
1141 if (fcgi_gen_response(c, rc->commit_id) == -1)
1142 goto done;
1144 if (fcgi_gen_response(c, "&folder=") == -1)
1145 goto done;
1146 if (fcgi_gen_response(c, qs->folder) == -1)
1147 goto done;
1149 if (fcgi_gen_response(c, "&file=") == -1)
1150 goto done;
1151 if (fcgi_gen_response(c, name) == -1)
1152 goto done;
1154 if (fcgi_gen_response(c, "'>") == -1)
1155 goto done;
1157 if (fcgi_gen_response(c, "blame") == -1)
1158 goto done;
1159 if (fcgi_gen_response(c, "</a>\n") == -1)
1160 goto done;
1162 if (fcgi_gen_response(c, "</div>\n") == -1)
1163 goto done;
1164 if (fcgi_gen_response(c, "</div>\n") == -1)
1165 goto done;
1167 free(id_str);
1168 id_str = NULL;
1169 free(build_folder);
1170 build_folder = NULL;
1171 free(name);
1172 name = NULL;
1173 free(modestr);
1174 modestr = NULL;
1175 free(class);
1176 class = NULL;
1178 done:
1179 free(id_str);
1180 free(build_folder);
1181 free(modestr);
1182 free(path);
1183 free(name);
1184 free(class);
1185 got_ref_list_free(&refs);
1186 if (commit)
1187 got_object_commit_close(commit);
1188 free(commit_id);
1189 free(tree_id);
1190 return error;
1193 const struct got_error *
1194 got_output_file_blob(struct request *c)
1196 const struct got_error *error = NULL;
1197 struct transport *t = c->t;
1198 struct got_repository *repo = t->repo;
1199 struct querystring *qs = c->t->qs;
1200 struct got_commit_object *commit = NULL;
1201 struct got_object_id *commit_id = NULL;
1202 struct got_reflist_head refs;
1203 struct got_blob_object *blob = NULL;
1204 char *path = NULL, *in_repo_path = NULL;
1205 int obj_type, set_mime = 0, type = 0, fd = -1;
1206 char *buf_output = NULL;
1207 size_t len, hdrlen;
1208 const uint8_t *buf;
1210 TAILQ_INIT(&refs);
1212 if (asprintf(&path, "%s%s%s", qs->folder ? qs->folder : "",
1213 qs->folder ? "/" : "", qs->file) == -1) {
1214 error = got_error_from_errno("asprintf");
1215 goto done;
1218 error = got_repo_map_path(&in_repo_path, repo, path);
1219 if (error)
1220 goto done;
1222 error = got_repo_match_object_id(&commit_id, NULL, qs->commit,
1223 GOT_OBJ_TYPE_COMMIT, &refs, repo);
1224 if (error)
1225 goto done;
1227 error = got_object_open_as_commit(&commit, repo, commit_id);
1228 if (error)
1229 goto done;
1231 error = got_object_id_by_path(&commit_id, repo, commit, in_repo_path);
1232 if (error)
1233 goto done;
1235 if (commit_id == NULL) {
1236 error = got_error(GOT_ERR_NO_OBJ);
1237 goto done;
1240 error = got_object_get_type(&obj_type, repo, commit_id);
1241 if (error)
1242 goto done;
1244 if (obj_type != GOT_OBJ_TYPE_BLOB) {
1245 error = got_error(GOT_ERR_OBJ_TYPE);
1246 goto done;
1249 error = got_gotweb_dupfd(&c->priv_fd[BLOB_FD_1], &fd);
1250 if (error)
1251 goto done;
1253 error = got_object_open_as_blob(&blob, repo, commit_id, BUF, fd);
1254 if (error)
1255 goto done;
1256 hdrlen = got_object_blob_get_hdrlen(blob);
1257 do {
1258 error = got_object_blob_read_block(&len, blob);
1259 if (error)
1260 goto done;
1261 buf = got_object_blob_get_read_buf(blob);
1264 * Skip blob object header first time around,
1265 * which also contains a zero byte.
1267 buf += hdrlen;
1268 if (set_mime == 0) {
1269 if (isbinary(buf, len - hdrlen)) {
1270 error = gotweb_render_content_type_file(c,
1271 "application/octet-stream",
1272 qs->file);
1273 if (error) {
1274 log_warnx("%s: %s", __func__,
1275 error->msg);
1276 goto done;
1278 type = 0;
1279 } else {
1280 error = gotweb_render_content_type(c,
1281 "text/text");
1282 if (error) {
1283 log_warnx("%s: %s", __func__,
1284 error->msg);
1285 goto done;
1287 type = 1;
1290 set_mime = 1;
1291 if (type) {
1292 buf_output = calloc(len - hdrlen + 1,
1293 sizeof(*buf_output));
1294 if (buf_output == NULL) {
1295 error = got_error_from_errno("calloc");
1296 goto done;
1298 memcpy(buf_output, buf, len - hdrlen);
1299 fcgi_gen_response(c, buf_output);
1300 free(buf_output);
1301 buf_output = NULL;
1302 } else
1303 fcgi_gen_binary_response(c, buf, len - hdrlen);
1305 hdrlen = 0;
1306 } while (len != 0);
1307 done:
1308 if (commit)
1309 got_object_commit_close(commit);
1310 if (fd != -1 && close(fd) == -1 && error == NULL)
1311 error = got_error_from_errno("close");
1312 if (blob)
1313 got_object_blob_close(blob);
1314 free(buf_output);
1315 free(in_repo_path);
1316 free(commit_id);
1317 free(path);
1318 return error;
1321 struct blame_line {
1322 int annotated;
1323 char *id_str;
1324 char *committer;
1325 char datebuf[11]; /* YYYY-MM-DD + NUL */
1328 struct blame_cb_args {
1329 struct blame_line *lines;
1330 int nlines;
1331 int nlines_prec;
1332 int lineno_cur;
1333 off_t *line_offsets;
1334 FILE *f;
1335 struct got_repository *repo;
1336 struct request *c;
1339 static const struct got_error *
1340 got_gotweb_blame_cb(void *arg, int nlines, int lineno,
1341 struct got_commit_object *commit, struct got_object_id *id)
1343 const struct got_error *err = NULL;
1344 struct blame_cb_args *a = arg;
1345 struct blame_line *bline;
1346 struct request *c = a->c;
1347 struct transport *t = c->t;
1348 struct querystring *qs = t->qs;
1349 struct repo_dir *repo_dir = t->repo_dir;
1350 char *line = NULL, *eline = NULL;
1351 size_t linesize = 0;
1352 off_t offset;
1353 struct tm tm;
1354 time_t committer_time;
1356 if (nlines != a->nlines ||
1357 (lineno != -1 && lineno < 1) || lineno > a->nlines)
1358 return got_error(GOT_ERR_RANGE);
1360 if (lineno == -1)
1361 return NULL; /* no change in this commit */
1363 /* Annotate this line. */
1364 bline = &a->lines[lineno - 1];
1365 if (bline->annotated)
1366 return NULL;
1367 err = got_object_id_str(&bline->id_str, id);
1368 if (err)
1369 return err;
1371 bline->committer = strdup(got_object_commit_get_committer(commit));
1372 if (bline->committer == NULL) {
1373 err = got_error_from_errno("strdup");
1374 goto done;
1377 committer_time = got_object_commit_get_committer_time(commit);
1378 if (gmtime_r(&committer_time, &tm) == NULL)
1379 return got_error_from_errno("gmtime_r");
1380 if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d",
1381 &tm) == 0) {
1382 err = got_error(GOT_ERR_NO_SPACE);
1383 goto done;
1385 bline->annotated = 1;
1387 /* Print lines annotated so far. */
1388 bline = &a->lines[a->lineno_cur - 1];
1389 if (!bline->annotated)
1390 goto done;
1392 offset = a->line_offsets[a->lineno_cur - 1];
1393 if (fseeko(a->f, offset, SEEK_SET) == -1) {
1394 err = got_error_from_errno("fseeko");
1395 goto done;
1398 while (bline->annotated) {
1399 int out_buff_size = 100;
1400 char *smallerthan, *at, *nl, *committer;
1401 char out_buff[out_buff_size];
1402 size_t len;
1404 if (getline(&line, &linesize, a->f) == -1) {
1405 if (ferror(a->f))
1406 err = got_error_from_errno("getline");
1407 break;
1410 committer = bline->committer;
1411 smallerthan = strchr(committer, '<');
1412 if (smallerthan && smallerthan[1] != '\0')
1413 committer = smallerthan + 1;
1414 at = strchr(committer, '@');
1415 if (at)
1416 *at = '\0';
1417 len = strlen(committer);
1418 if (len >= 9)
1419 committer[8] = '\0';
1421 nl = strchr(line, '\n');
1422 if (nl)
1423 *nl = '\0';
1425 if (fcgi_gen_response(c, "<div id='blame_wrapper'>") == -1)
1426 goto done;
1427 if (fcgi_gen_response(c, "<div id='blame_number'>") == -1)
1428 goto done;
1429 if (snprintf(out_buff, strlen(out_buff), "%.*d", a->nlines_prec,
1430 a->lineno_cur) < 0)
1431 goto done;
1432 if (fcgi_gen_response(c, out_buff) == -1)
1433 goto done;
1434 if (fcgi_gen_response(c, "</div>") == -1)
1435 goto done;
1437 if (fcgi_gen_response(c, "<div id='blame_hash'>") == -1)
1438 goto done;
1440 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1441 goto done;
1442 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1443 goto done;
1444 if (fcgi_gen_response(c, "&path=") == -1)
1445 goto done;
1446 if (fcgi_gen_response(c, repo_dir->name) == -1)
1447 goto done;
1448 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1449 goto done;
1450 if (fcgi_gen_response(c, bline->id_str) == -1)
1451 goto done;
1452 if (fcgi_gen_response(c, "'>") == -1)
1453 goto done;
1454 if (snprintf(out_buff, 10, "%.8s", bline->id_str) < 0)
1455 goto done;
1456 if (fcgi_gen_response(c, out_buff) == -1)
1457 goto done;
1458 if (fcgi_gen_response(c, "</a></div>") == -1)
1459 goto done;
1461 if (fcgi_gen_response(c, "<div id='blame_date'>") == -1)
1462 goto done;
1463 if (fcgi_gen_response(c, bline->datebuf) == -1)
1464 goto done;
1465 if (fcgi_gen_response(c, "</div>") == -1)
1466 goto done;
1468 if (fcgi_gen_response(c, "<div id='blame_author'>") == -1)
1469 goto done;
1470 if (fcgi_gen_response(c, committer) == -1)
1471 goto done;
1472 if (fcgi_gen_response(c, "</div>") == -1)
1473 goto done;
1475 if (fcgi_gen_response(c, "<div id='blame_code'>") == -1)
1476 goto done;
1477 err = gotweb_escape_html(&eline, line);
1478 if (err)
1479 goto done;
1480 if (fcgi_gen_response(c, eline) == -1)
1481 goto done;
1482 if (fcgi_gen_response(c, "</div>") == -1)
1483 goto done;
1485 if (fcgi_gen_response(c, "</div>") == -1)
1486 goto done;
1487 a->lineno_cur++;
1488 bline = &a->lines[a->lineno_cur - 1];
1490 done:
1491 free(line);
1492 free(eline);
1493 return err;
1496 const struct got_error *
1497 got_output_file_blame(struct request *c)
1499 const struct got_error *error = NULL;
1500 struct transport *t = c->t;
1501 struct got_repository *repo = t->repo;
1502 struct querystring *qs = c->t->qs;
1503 struct got_object_id *obj_id = NULL, *commit_id = NULL;
1504 struct got_commit_object *commit = NULL;
1505 struct got_reflist_head refs;
1506 struct got_blob_object *blob = NULL;
1507 char *path = NULL, *in_repo_path = NULL;
1508 struct blame_cb_args bca;
1509 int i, obj_type, fd1 = -1, fd2 = -1, fd3 = -1, fd4 = -1, fd5 = -1;
1510 int fd6 = -1;
1511 off_t filesize;
1512 FILE *f1 = NULL, *f2 = NULL;
1514 TAILQ_INIT(&refs);
1515 bca.f = NULL;
1516 bca.lines = NULL;
1518 if (asprintf(&path, "%s%s%s", qs->folder ? qs->folder : "",
1519 qs->folder ? "/" : "", qs->file) == -1) {
1520 error = got_error_from_errno("asprintf");
1521 goto done;
1524 error = got_repo_map_path(&in_repo_path, repo, path);
1525 if (error)
1526 goto done;
1528 error = got_repo_match_object_id(&commit_id, NULL, qs->commit,
1529 GOT_OBJ_TYPE_COMMIT, &refs, repo);
1530 if (error)
1531 goto done;
1533 error = got_object_open_as_commit(&commit, repo, commit_id);
1534 if (error)
1535 goto done;
1537 error = got_object_id_by_path(&obj_id, repo, commit, in_repo_path);
1538 if (error)
1539 goto done;
1541 if (commit_id == NULL) {
1542 error = got_error(GOT_ERR_NO_OBJ);
1543 goto done;
1546 error = got_object_get_type(&obj_type, repo, obj_id);
1547 if (error)
1548 goto done;
1550 if (obj_type != GOT_OBJ_TYPE_BLOB) {
1551 error = got_error(GOT_ERR_OBJ_TYPE);
1552 goto done;
1555 error = got_gotweb_openfile(&bca.f, &c->priv_fd[BLAME_FD_1], &fd1);
1556 if (error)
1557 goto done;
1559 error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_2], &fd2);
1560 if (error)
1561 goto done;
1563 error = got_object_open_as_blob(&blob, repo, obj_id, BUF, fd2);
1564 if (error)
1565 goto done;
1567 error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
1568 &bca.line_offsets, bca.f, blob);
1569 if (error || bca.nlines == 0)
1570 goto done;
1572 /* Don't include \n at EOF in the blame line count. */
1573 if (bca.line_offsets[bca.nlines - 1] == filesize)
1574 bca.nlines--;
1576 bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
1577 if (bca.lines == NULL) {
1578 error = got_error_from_errno("calloc");
1579 goto done;
1581 bca.lineno_cur = 1;
1582 bca.nlines_prec = 0;
1583 i = bca.nlines;
1584 while (i > 0) {
1585 i /= 10;
1586 bca.nlines_prec++;
1588 bca.repo = repo;
1589 bca.c = c;
1591 error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_3], &fd3);
1592 if (error)
1593 goto done;
1595 error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_4], &fd4);
1596 if (error)
1597 goto done;
1599 error = got_gotweb_openfile(&f1, &c->priv_fd[BLAME_FD_5], &fd5);
1600 if (error)
1601 goto done;
1603 error = got_gotweb_openfile(&f2, &c->priv_fd[BLAME_FD_6], &fd6);
1604 if (error)
1605 goto done;
1607 error = got_blame(in_repo_path, commit_id, repo,
1608 GOT_DIFF_ALGORITHM_MYERS, got_gotweb_blame_cb, &bca, NULL, NULL,
1609 fd3, fd4, f1, f2);
1611 if (blob) {
1612 free(bca.line_offsets);
1613 for (i = 0; i < bca.nlines; i++) {
1614 struct blame_line *bline = &bca.lines[i];
1615 free(bline->id_str);
1616 free(bline->committer);
1619 done:
1620 free(bca.lines);
1621 if (fd2 != -1 && close(fd2) == -1 && error == NULL)
1622 error = got_error_from_errno("close");
1623 if (fd3 != -1 && close(fd3) == -1 && error == NULL)
1624 error = got_error_from_errno("close");
1625 if (fd4 != -1 && close(fd4) == -1 && error == NULL)
1626 error = got_error_from_errno("close");
1627 if (bca.f) {
1628 const struct got_error *bca_err =
1629 got_gotweb_flushfile(bca.f, fd1);
1630 if (error == NULL)
1631 error = bca_err;
1633 if (f1) {
1634 const struct got_error *f1_err =
1635 got_gotweb_flushfile(f1, fd5);
1636 if (error == NULL)
1637 error = f1_err;
1639 if (f2) {
1640 const struct got_error *f2_err =
1641 got_gotweb_flushfile(f2, fd6);
1642 if (error == NULL)
1643 error = f2_err;
1645 if (commit)
1646 got_object_commit_close(commit);
1647 if (blob)
1648 got_object_blob_close(blob);
1649 free(in_repo_path);
1650 free(commit_id);
1651 free(path);
1652 return error;
1655 const struct got_error *
1656 got_output_repo_diff(struct request *c)
1658 const struct got_error *error = NULL;
1659 struct transport *t = c->t;
1660 struct got_repository *repo = t->repo;
1661 struct repo_commit *rc = NULL;
1662 struct got_object_id *id1 = NULL, *id2 = NULL;
1663 struct got_reflist_head refs;
1664 FILE *f1 = NULL, *f2 = NULL, *f3 = NULL;
1665 char *label1 = NULL, *label2 = NULL, *line = NULL;
1666 char *newline, *eline = NULL, *color = NULL;
1667 int obj_type, fd1, fd2, fd3, fd4 = -1, fd5 = -1;
1668 size_t linesize = 0;
1669 ssize_t linelen;
1670 int wrlen = 0;
1672 TAILQ_INIT(&refs);
1674 error = got_gotweb_openfile(&f1, &c->priv_fd[DIFF_FD_1], &fd1);
1675 if (error)
1676 return error;
1678 error = got_gotweb_openfile(&f2, &c->priv_fd[DIFF_FD_2], &fd2);
1679 if (error)
1680 return error;
1682 error = got_gotweb_openfile(&f3, &c->priv_fd[DIFF_FD_3], &fd3);
1683 if (error)
1684 return error;
1686 rc = TAILQ_FIRST(&t->repo_commits);
1688 if (rc->parent_id != NULL &&
1689 strncmp(rc->parent_id, "/dev/null", 9) != 0) {
1690 error = got_repo_match_object_id(&id1, &label1,
1691 rc->parent_id, GOT_OBJ_TYPE_ANY,
1692 &refs, repo);
1693 if (error)
1694 goto done;
1697 error = got_repo_match_object_id(&id2, &label2, rc->commit_id,
1698 GOT_OBJ_TYPE_ANY, &refs, repo);
1699 if (error)
1700 goto done;
1702 error = got_object_get_type(&obj_type, repo, id2);
1703 if (error)
1704 goto done;
1706 error = got_gotweb_dupfd(&c->priv_fd[DIFF_FD_4], &fd4);
1707 if (error)
1708 goto done;
1710 error = got_gotweb_dupfd(&c->priv_fd[DIFF_FD_5], &fd5);
1711 if (error)
1712 goto done;
1714 switch (obj_type) {
1715 case GOT_OBJ_TYPE_BLOB:
1716 error = got_diff_objects_as_blobs(NULL, NULL, f1, f2, fd4, fd5,
1717 id1, id2, NULL, NULL, GOT_DIFF_ALGORITHM_MYERS, 3, 0, 0,
1718 repo, f3);
1719 break;
1720 case GOT_OBJ_TYPE_TREE:
1721 error = got_diff_objects_as_trees(NULL, NULL, f1, f2, fd4, fd5,
1722 id1, id2, NULL, "", "", GOT_DIFF_ALGORITHM_MYERS, 3, 0, 0,
1723 repo, f3);
1724 break;
1725 case GOT_OBJ_TYPE_COMMIT:
1726 error = got_diff_objects_as_commits(NULL, NULL, f1, f2, fd4,
1727 fd5, id1, id2, NULL, GOT_DIFF_ALGORITHM_MYERS, 3, 0, 0,
1728 repo, f3);
1729 break;
1730 default:
1731 error = got_error(GOT_ERR_OBJ_TYPE);
1733 if (error)
1734 goto done;
1736 if (fseek(f1, 0, SEEK_SET) == -1) {
1737 error = got_ferror(f1, GOT_ERR_IO);
1738 goto done;
1741 if (fseek(f2, 0, SEEK_SET) == -1) {
1742 error = got_ferror(f2, GOT_ERR_IO);
1743 goto done;
1746 if (fseek(f3, 0, SEEK_SET) == -1) {
1747 error = got_ferror(f3, GOT_ERR_IO);
1748 goto done;
1751 while ((linelen = getline(&line, &linesize, f3)) != -1) {
1752 if (strncmp(line, "-", 1) == 0) {
1753 color = strdup("diff_minus");
1754 if (color == NULL) {
1755 error = got_error_from_errno("strdup");
1756 goto done;
1758 } else if (strncmp(line, "+", 1) == 0) {
1759 color = strdup("diff_plus");
1760 if (color == NULL) {
1761 error = got_error_from_errno("strdup");
1762 goto done;
1764 } else if (strncmp(line, "@@", 2) == 0) {
1765 color = strdup("diff_chunk_header");
1766 if (color == NULL) {
1767 error = got_error_from_errno("strdup");
1768 goto done;
1770 } else if (strncmp(line, "@@", 2) == 0) {
1771 color = strdup("diff_chunk_header");
1772 if (color == NULL) {
1773 error = got_error_from_errno("strdup");
1774 goto done;
1776 } else if (strncmp(line, "commit +", 8) == 0) {
1777 color = strdup("diff_meta");
1778 if (color == NULL) {
1779 error = got_error_from_errno("strdup");
1780 goto done;
1782 } else if (strncmp(line, "commit -", 8) == 0) {
1783 color = strdup("diff_meta");
1784 if (color == NULL) {
1785 error = got_error_from_errno("strdup");
1786 goto done;
1788 } else if (strncmp(line, "blob +", 6) == 0) {
1789 color = strdup("diff_meta");
1790 if (color == NULL) {
1791 error = got_error_from_errno("strdup");
1792 goto done;
1794 } else if (strncmp(line, "blob -", 6) == 0) {
1795 color = strdup("diff_meta");
1796 if (color == NULL) {
1797 error = got_error_from_errno("strdup");
1798 goto done;
1800 } else if (strncmp(line, "file +", 6) == 0) {
1801 color = strdup("diff_meta");
1802 if (color == NULL) {
1803 error = got_error_from_errno("strdup");
1804 goto done;
1806 } else if (strncmp(line, "file -", 6) == 0) {
1807 color = strdup("diff_meta");
1808 if (color == NULL) {
1809 error = got_error_from_errno("strdup");
1810 goto done;
1812 } else if (strncmp(line, "from:", 5) == 0) {
1813 color = strdup("diff_author");
1814 if (color == NULL) {
1815 error = got_error_from_errno("strdup");
1816 goto done;
1818 } else if (strncmp(line, "via:", 4) == 0) {
1819 color = strdup("diff_author");
1820 if (color == NULL) {
1821 error = got_error_from_errno("strdup");
1822 goto done;
1824 } else if (strncmp(line, "date:", 5) == 0) {
1825 color = strdup("diff_date");
1826 if (color == NULL) {
1827 error = got_error_from_errno("strdup");
1828 goto done;
1831 if (fcgi_gen_response(c, "<div id='diff_line' class='") == -1)
1832 goto done;
1833 if (fcgi_gen_response(c, color ? color : "") == -1)
1834 goto done;
1835 if (fcgi_gen_response(c, "'>") == -1)
1836 goto done;
1837 newline = strchr(line, '\n');
1838 if (newline)
1839 *newline = '\0';
1841 error = gotweb_escape_html(&eline, line);
1842 if (error)
1843 goto done;
1844 if (fcgi_gen_response(c, eline) == -1)
1845 goto done;
1846 free(eline);
1847 eline = NULL;
1849 if (fcgi_gen_response(c, "</div>\n") == -1)
1850 goto done;
1851 if (linelen > 0)
1852 wrlen = wrlen + linelen;
1853 free(color);
1854 color = NULL;
1856 if (linelen == -1 && ferror(f3))
1857 error = got_error_from_errno("getline");
1858 done:
1859 free(color);
1860 if (fd4 != -1 && close(fd4) == -1 && error == NULL)
1861 error = got_error_from_errno("close");
1862 if (fd5 != -1 && close(fd5) == -1 && error == NULL)
1863 error = got_error_from_errno("close");
1864 if (f1) {
1865 const struct got_error *f1_err =
1866 got_gotweb_flushfile(f1, fd1);
1867 if (error == NULL)
1868 error = f1_err;
1870 if (f2) {
1871 const struct got_error *f2_err =
1872 got_gotweb_flushfile(f2, fd2);
1873 if (error == NULL)
1874 error = f2_err;
1876 if (f3) {
1877 const struct got_error *f3_err =
1878 got_gotweb_flushfile(f3, fd3);
1879 if (error == NULL)
1880 error = f3_err;
1882 got_ref_list_free(&refs);
1883 free(line);
1884 free(eline);
1885 free(label1);
1886 free(label2);
1887 free(id1);
1888 free(id2);
1889 return error;
1892 static const struct got_error *
1893 got_init_repo_commit(struct repo_commit **rc)
1895 const struct got_error *error = NULL;
1897 *rc = calloc(1, sizeof(**rc));
1898 if (*rc == NULL)
1899 return got_error_from_errno2("%s: calloc", __func__);
1901 (*rc)->path = NULL;
1902 (*rc)->refs_str = NULL;
1903 (*rc)->commit_id = NULL;
1904 (*rc)->committer = NULL;
1905 (*rc)->author = NULL;
1906 (*rc)->parent_id = NULL;
1907 (*rc)->tree_id = NULL;
1908 (*rc)->commit_msg = NULL;
1910 return error;
1913 static const struct got_error *
1914 got_init_repo_tag(struct repo_tag **rt)
1916 const struct got_error *error = NULL;
1918 *rt = calloc(1, sizeof(**rt));
1919 if (*rt == NULL)
1920 return got_error_from_errno2("%s: calloc", __func__);
1922 (*rt)->commit_id = NULL;
1923 (*rt)->tag_name = NULL;
1924 (*rt)->tag_commit = NULL;
1925 (*rt)->commit_msg = NULL;
1926 (*rt)->tagger = NULL;
1928 return error;