Blob


1 /*
2 * Copyright (c) 2020-2022 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2018, 2019 Stefan Sperling <stsp@openbsd.org>
4 *
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.
8 *
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.
16 */
18 #include <sys/socket.h>
19 #include <sys/stat.h>
21 #include <event.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <unistd.h>
27 #include "got_error.h"
28 #include "got_object.h"
29 #include "got_reference.h"
30 #include "got_repository.h"
31 #include "got_path.h"
32 #include "got_cancel.h"
33 #include "got_diff.h"
34 #include "got_commit_graph.h"
35 #include "got_blame.h"
36 #include "got_privsep.h"
38 #include "got_compat.h"
40 #include "proc.h"
41 #include "gotwebd.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 *);
54 static int
55 isbinary(const uint8_t *buf, size_t n)
56 {
57 size_t i;
59 for (i = 0; i < n; i++)
60 if (buf[i] == 0)
61 return 1;
62 return 0;
63 }
66 static const struct got_error *
67 got_gotweb_flushfile(FILE *f, int fd)
68 {
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");
75 if (fsync(fd) == -1)
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");
84 return NULL;
85 }
87 static const struct got_error *
88 got_gotweb_openfile(FILE **f, int *priv_fd, int *fd)
89 {
90 const struct got_error *error = NULL;
92 *fd = dup(*priv_fd);
94 if (*fd < 0)
95 return NULL;
97 *f = fdopen(*fd, "w+");
98 if (*f == NULL) {
99 close(*fd);
100 error = got_error(GOT_ERR_PRIVSEP_NO_FD);
103 return error;
106 static const struct got_error *
107 got_gotweb_dupfd(int *priv_fd, int *fd)
109 const struct got_error *error = NULL;
111 *fd = dup(*priv_fd);
113 if (*fd < 0)
114 return NULL;
116 return error;
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;
128 *owner = NULL;
130 if (srv->show_repo_owner == 0)
131 return NULL;
133 gitconfig_owner = got_repo_get_gitconfig_owner(repo);
134 if (gitconfig_owner) {
135 *owner = strdup(gitconfig_owner);
136 if (*owner == NULL)
137 return got_error_from_errno("strdup");
139 return error;
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;
155 *repo_age = NULL;
156 TAILQ_INIT(&refs);
158 if (srv->show_repo_age == 0)
159 return NULL;
161 error = got_ref_list(&refs, repo, "refs/heads",
162 got_ref_cmp_by_name, NULL);
163 if (error)
164 goto done;
166 /*
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.
169 */
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)
174 continue;
176 error = got_ref_resolve(&id, repo, re->ref);
177 if (error)
178 goto done;
180 error = got_object_open_as_commit(&commit, repo, id);
181 free(id);
182 if (error)
183 goto done;
185 committer_time =
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;
191 if (refname)
192 break;
195 if (cmp_time != 0) {
196 committer_time = cmp_time;
197 error = gotweb_get_time_str(repo_age, committer_time, ref_tm);
199 done:
200 got_ref_list_free(&refs);
201 return error;
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) {
218 char *s;
219 const char *name;
220 struct got_tag_object *tag = NULL;
221 struct got_object_id *ref_id;
222 int cmp;
224 if (got_ref_is_symbolic(re->ref))
225 continue;
227 name = got_ref_get_name(re->ref);
228 if (strncmp(name, "refs/", 5) == 0)
229 name += 5;
230 if (strncmp(name, "got/", 4) == 0)
231 continue;
232 if (strncmp(name, "heads/", 6) == 0)
233 name += 6;
234 if (strncmp(name, "remotes/", 8) == 0) {
235 name += 8;
236 s = strstr(name, "/" GOT_REF_HEAD);
237 if (s != NULL && s[strlen(s)] == '\0')
238 continue;
240 error = got_ref_resolve(&ref_id, t->repo, re->ref);
241 if (error)
242 return error;
243 if (strncmp(name, "tags/", 5) == 0) {
244 error = got_object_open_as_tag(&tag, t->repo, ref_id);
245 if (error) {
246 if (error->code != GOT_ERR_OBJ_TYPE) {
247 free(ref_id);
248 continue;
250 /*
251 * Ref points at something other
252 * than a tag.
253 */
254 error = NULL;
255 tag = NULL;
258 cmp = got_object_id_cmp(tag ?
259 got_object_tag_get_object_id(tag) : ref_id, id);
260 free(ref_id);
261 if (tag)
262 got_object_tag_close(tag);
263 if (cmp != 0)
264 continue;
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");
269 free(s);
270 repo_commit->refs_str = NULL;
271 return error;
273 free(s);
276 error = got_object_id_str(&repo_commit->commit_id, id);
277 if (error)
278 return error;
280 error = got_object_id_str(&repo_commit->tree_id,
281 got_object_commit_get_tree_id(commit));
282 if (error)
283 return error;
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);
291 if (error)
292 return error;
293 free(id2);
294 } else {
295 repo_commit->parent_id = strdup("/dev/null");
296 if (repo_commit->parent_id == NULL) {
297 error = got_error_from_errno("strdup");
298 return error;
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");
310 return error;
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");
316 return error;
318 error = got_object_commit_get_logmsg(&commit_msg0, commit);
319 if (error)
320 return error;
322 commit_msg = commit_msg0;
323 while (*commit_msg == '\n')
324 commit_msg++;
326 repo_commit->commit_msg = strdup(commit_msg);
327 if (repo_commit->commit_msg == NULL)
328 error = got_error_from_errno("strdup");
329 free(commit_msg0);
330 return error;
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;
352 TAILQ_INIT(&refs);
354 if (qs->file != NULL && strlen(qs->file) > 0)
355 if (asprintf(&file_path, "%s/%s", qs->folder ? qs->folder : "",
356 qs->file) == -1)
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);
364 if (error)
365 return error;
367 /*
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
372 * the headref.
373 */
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);
377 if (error)
378 goto done;
380 error = got_ref_resolve(&id, repo, ref);
381 got_ref_close(ref);
382 if (error)
383 goto done;
384 } else if (qs->commit != NULL) {
385 error = got_ref_open(&ref, repo, qs->commit, 0);
386 if (error == NULL) {
387 error = got_ref_resolve(&id, repo, ref);
388 if (error)
389 goto done;
390 error = got_object_get_type(&obj_type, repo, id);
391 got_ref_close(ref);
392 if (error)
393 goto done;
394 if (obj_type == GOT_OBJ_TYPE_TAG) {
395 struct got_tag_object *tag;
396 error = got_object_open_as_tag(&tag, repo, id);
397 if (error)
398 goto done;
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);
403 goto done;
405 free(id);
406 id = got_object_id_dup(
407 got_object_tag_get_object_id(tag));
408 if (id == NULL)
409 error = got_error_from_errno(
410 "got_object_id_dup");
411 got_object_tag_close(tag);
412 if (error)
413 goto done;
414 } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
415 error = got_error(GOT_ERR_OBJ_TYPE);
416 goto done;
419 error = got_repo_match_object_id_prefix(&id, qs->commit,
420 GOT_OBJ_TYPE_COMMIT, repo);
421 if (error)
422 goto done;
425 error = got_repo_map_path(&in_repo_path, repo, repo_path);
426 if (error)
427 goto done;
429 error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
430 if (error)
431 goto done;
433 if (qs->file != NULL && strlen(qs->file) > 0) {
434 error = got_commit_graph_open(&graph, file_path, 0);
435 if (error)
436 goto done;
437 } else {
438 error = got_commit_graph_open(&graph, in_repo_path, 0);
439 if (error)
440 goto done;
443 error = got_commit_graph_iter_start(graph, id, repo, NULL, NULL);
444 if (error)
445 goto done;
447 for (;;) {
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");
453 goto done;
457 error = got_commit_graph_iter_next(&id, graph, repo, NULL,
458 NULL);
459 if (error) {
460 if (error->code == GOT_ERR_ITER_COMPLETED)
461 error = NULL;
462 goto done;
464 if (id == NULL)
465 goto done;
467 error = got_object_open_as_commit(&commit, repo, id);
468 if (error)
469 goto done;
471 error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name,
472 NULL);
473 if (error)
474 goto done;
476 error = got_get_repo_commit(c, repo_commit, commit,
477 &refs, id);
478 if (error)
479 goto done;
481 if (qs->commit != NULL && commit_found == 0 && limit != 1) {
482 if (strcmp(qs->commit, repo_commit->commit_id) == 0)
483 commit_found = 1;
484 else if (qs->file != NULL && strlen(qs->file) > 0 &&
485 qs->page == 0)
486 commit_found = 1;
487 else {
488 limit_chk++;
489 free(id);
490 id = NULL;
491 continue;
495 struct repo_commit *new_repo_commit = NULL;
496 error = got_init_repo_commit(&new_repo_commit);
497 if (error)
498 goto done;
500 TAILQ_INSERT_TAIL(&t->repo_commits, new_repo_commit, entry);
502 error = got_get_repo_commit(c, new_repo_commit, commit,
503 &refs, id);
504 if (error)
505 goto done;
507 free(id);
508 id = NULL;
510 if (limit == 1 && chk_multi == 0 &&
511 srv->max_commits_display != 1)
512 commit_found = 1;
513 else {
514 chk_multi = 1;
516 /*
517 * check for one more commit before breaking,
518 * so we know whether to navigate through briefs
519 * commits and summary
520 */
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");
526 goto done;
528 if (commit) {
529 got_object_commit_close(commit);
530 commit = NULL;
532 if (t->next_id == NULL) {
533 error = got_error_from_errno("strdup");
534 goto done;
536 TAILQ_REMOVE(&t->repo_commits, new_repo_commit,
537 entry);
538 gotweb_free_repo_commit(new_repo_commit);
539 goto done;
542 got_ref_list_free(&refs);
543 if (error || (limit && --limit == 0)) {
544 if (commit_found || (qs->file != NULL &&
545 strlen(qs->file) > 0))
546 if (chk_multi == 0)
547 break;
548 chk_next = 1;
550 if (commit) {
551 got_object_commit_close(commit);
552 commit = NULL;
555 done:
556 gotweb_free_repo_commit(repo_commit);
557 if (commit)
558 got_object_commit_close(commit);
559 if (graph)
560 got_commit_graph_close(graph);
561 got_ref_list_free(&refs);
562 free(file_path);
563 free(repo_path);
564 free(id);
565 return error;
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;
588 TAILQ_INIT(&refs);
590 if (asprintf(&repo_path, "%s/%s", srv->repos_path,
591 repo_dir->name) == -1)
592 return got_error_from_errno("asprintf");
594 if (error)
595 return error;
597 if (qs->commit == NULL && qs->action == TAGS) {
598 error = got_ref_open(&ref, repo, qs->headref, 0);
599 if (error)
600 goto err;
601 error = got_ref_resolve(&id, repo, ref);
602 got_ref_close(ref);
603 if (error)
604 goto err;
605 } else if (qs->commit == NULL && qs->action == TAG) {
606 error = got_error_msg(GOT_ERR_EOF, "commit id missing");
607 goto err;
608 } else {
609 error = got_repo_match_object_id_prefix(&id, qs->commit,
610 GOT_OBJ_TYPE_COMMIT, repo);
611 if (error)
612 goto err;
615 if (qs->action != SUMMARY && qs->action != TAGS) {
616 error = got_object_open_as_commit(&commit, repo, id);
617 if (error)
618 goto err;
619 error = got_object_commit_get_logmsg(&commit_msg0, commit);
620 if (error)
621 goto err;
622 if (commit) {
623 got_object_commit_close(commit);
624 commit = NULL;
628 error = got_repo_map_path(&in_repo_path, repo, repo_path);
629 if (error)
630 goto err;
632 error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags,
633 repo);
634 if (error)
635 goto err;
637 if (limit == 1)
638 chk_multi = 0;
640 /*
641 * XXX: again, see previous message about caching
642 */
644 TAILQ_FOREACH(re, &refs, entry) {
645 struct repo_tag *new_repo_tag = NULL;
646 error = got_init_repo_tag(&new_repo_tag);
647 if (error)
648 goto err;
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");
655 goto err;
658 error = got_ref_resolve(&id, repo, re->ref);
659 if (error)
660 goto done;
662 error = got_object_open_as_tag(&tag, repo, id);
663 if (error) {
664 if (error->code != GOT_ERR_OBJ_TYPE) {
665 free(id);
666 id = NULL;
667 goto done;
669 /* "lightweight" tag */
670 error = got_object_open_as_commit(&commit, repo, id);
671 if (error) {
672 free(id);
673 id = NULL;
674 goto done;
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");
680 goto err;
682 new_repo_tag->tagger_time =
683 got_object_commit_get_committer_time(commit);
684 error = got_object_id_str(&id_str, id);
685 if (error)
686 goto err;
687 free(id);
688 id = NULL;
689 } else {
690 free(id);
691 id = NULL;
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");
696 goto err;
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));
702 if (error)
703 goto err;
706 new_repo_tag->commit_id = strdup(id_str);
707 if (new_repo_tag->commit_id == NULL)
708 goto err;
710 if (commit_found == 0 && qs->commit != NULL &&
711 strncmp(id_str, qs->commit, strlen(id_str)) != 0)
712 continue;
713 else
714 commit_found = 1;
716 t->tag_count++;
718 /*
719 * check for one more commit before breaking,
720 * so we know whether to navigate through briefs
721 * commits and summary
722 */
723 if (chk_next) {
724 t->next_id = strdup(new_repo_tag->commit_id);
725 if (t->next_id == NULL) {
726 error = got_error_from_errno("strdup");
727 goto err;
729 if (commit) {
730 got_object_commit_close(commit);
731 commit = NULL;
733 if (t->next_id == NULL) {
734 error = got_error_from_errno("strdup");
735 goto err;
737 TAILQ_REMOVE(&t->repo_tags, new_repo_tag, entry);
738 gotweb_free_repo_tag(new_repo_tag);
739 goto done;
742 if (commit) {
743 error = got_object_commit_get_logmsg(&new_repo_tag->
744 tag_commit, commit);
745 if (error)
746 goto done;
747 got_object_commit_close(commit);
748 commit = NULL;
749 } else {
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");
754 goto done;
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')
764 commit_msg++;
766 new_repo_tag->commit_msg = strdup(commit_msg);
767 if (new_repo_tag->commit_msg == NULL) {
768 error = got_error_from_errno("strdup");
769 free(commit_msg0);
770 goto err;
772 free(commit_msg0);
775 if (limit && --limit == 0) {
776 if (chk_multi == 0)
777 break;
778 chk_next = 1;
780 free(id);
781 id = NULL;
784 done:
785 /*
786 * we have tailq populated, so find previous commit id
787 * for navigation through briefs and commits
788 */
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) {
796 commit_found = 0;
797 TAILQ_FOREACH_REVERSE(rt, &t->repo_tags, repo_tags_head,
798 entry) {
799 if (commit_found == 0 && rt->commit_id != NULL &&
800 strcmp(qs->commit, rt->commit_id) != 0) {
801 continue;
802 } else
803 commit_found = 1;
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");
809 break;
811 c_cnt++;
814 err:
815 if (commit)
816 got_object_commit_close(commit);
817 got_ref_list_free(&refs);
818 free(repo_path);
819 free(id);
820 return error;
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;
836 char *id_str = NULL;
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;
841 TAILQ_INIT(&refs);
843 rc = TAILQ_FIRST(&t->repo_commits);
845 if (qs->folder != NULL) {
846 path = strdup(qs->folder);
847 if (path == NULL) {
848 error = got_error_from_errno("strdup");
849 goto done;
851 } else {
852 error = got_repo_map_path(&in_repo_path, repo, repo_dir->path);
853 if (error)
854 goto done;
855 free(path);
856 path = in_repo_path;
859 error = got_repo_match_object_id(&commit_id, NULL, rc->commit_id,
860 GOT_OBJ_TYPE_COMMIT, &refs, repo);
861 if (error)
862 goto done;
864 error = got_object_open_as_commit(&commit, repo, commit_id);
865 if (error)
866 goto done;
868 error = got_object_id_by_path(&tree_id, repo, commit, path);
869 if (error)
870 goto done;
872 error = got_object_open_as_tree(&tree, repo, tree_id);
873 if (error)
874 goto done;
876 nentries = got_object_tree_get_nentries(tree);
878 for (i = 0; i < nentries; i++) {
879 struct got_tree_entry *te;
880 mode_t mode;
882 te = got_object_tree_get_entry(tree, i);
884 error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
885 if (error)
886 goto done;
888 modestr = strdup("");
889 if (modestr == NULL) {
890 error = got_error_from_errno("strdup");
891 goto done;
893 mode = got_tree_entry_get_mode(te);
894 if (got_object_tree_entry_is_submodule(te)) {
895 free(modestr);
896 modestr = strdup("$");
897 if (modestr == NULL) {
898 error = got_error_from_errno("strdup");
899 goto done;
901 } else if (S_ISLNK(mode)) {
902 free(modestr);
903 modestr = strdup("@");
904 if (modestr == NULL) {
905 error = got_error_from_errno("strdup");
906 goto done;
908 } else if (S_ISDIR(mode)) {
909 free(modestr);
910 modestr = strdup("/");
911 if (modestr == NULL) {
912 error = got_error_from_errno("strdup");
913 goto done;
915 } else if (mode & S_IXUSR) {
916 free(modestr);
917 modestr = strdup("*");
918 if (modestr == NULL) {
919 error = got_error_from_errno("strdup");
920 goto done;
924 if (class_flip == 0) {
925 class = strdup("back_lightgray");
926 if (class == NULL) {
927 error = got_error_from_errno("strdup");
928 goto done;
930 class_flip = 1;
931 } else {
932 class = strdup("back_white");
933 if (class == NULL) {
934 error = got_error_from_errno("strdup");
935 goto done;
937 class_flip = 0;
940 name = strdup(got_tree_entry_get_name(te));
941 if (name == NULL) {
942 error = got_error_from_errno("strdup");
943 goto done;
945 if (S_ISDIR(mode)) {
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");
950 goto done;
953 if (fcgi_gen_response(c,
954 "<div id='tree_wrapper'>\n") == -1)
955 goto done;
957 if (fcgi_gen_response(c, "<div id='tree_line' "
958 "class='") == -1)
959 goto done;
960 if (fcgi_gen_response(c, class) == -1)
961 goto done;
962 if (fcgi_gen_response(c, "'>") == -1)
963 goto done;
965 if (fcgi_gen_response(c, "<a class='diff_directory' "
966 "href='?index_page=") == -1)
967 goto done;
968 if (fcgi_gen_response(c, qs->index_page_str) == -1)
969 goto done;
970 if (fcgi_gen_response(c, "&path=") == -1)
971 goto done;
972 if (fcgi_gen_response(c, qs->path) == -1)
973 goto done;
974 if (fcgi_gen_response(c, "&action=tree") == -1)
975 goto done;
976 if (fcgi_gen_response(c, "&commit=") == -1)
977 goto done;
978 if (fcgi_gen_response(c, rc->commit_id) == -1)
979 goto done;
980 if (fcgi_gen_response(c, "&folder=") == -1)
981 goto done;
982 if (fcgi_gen_response(c, build_folder) == -1)
983 goto done;
984 if (fcgi_gen_response(c, "'>") == -1)
985 goto done;
986 if (fcgi_gen_response(c, name) == -1)
987 goto done;
988 if (fcgi_gen_response(c, modestr) == -1)
989 goto done;
990 if (fcgi_gen_response(c, "</a>") == -1)
991 goto done;
993 if (fcgi_gen_response(c, "</div>\n") == -1)
994 goto done;
996 if (fcgi_gen_response(c, "<div id='tree_line_blank' "
997 "class='") == -1)
998 goto done;
999 if (fcgi_gen_response(c, class) == -1)
1000 goto done;
1001 if (fcgi_gen_response(c, "'>") == -1)
1002 goto done;
1003 if (fcgi_gen_response(c, "&nbsp;") == -1)
1004 goto done;
1005 if (fcgi_gen_response(c, "</div>\n") == -1)
1006 goto done;
1008 if (fcgi_gen_response(c, "</div>\n") == -1)
1009 goto done;
1011 } else {
1012 free(name);
1013 name = strdup(got_tree_entry_get_name(te));
1014 if (name == NULL) {
1015 error = got_error_from_errno("strdup");
1016 goto done;
1019 if (fcgi_gen_response(c,
1020 "<div id='tree_wrapper'>\n") == -1)
1021 goto done;
1022 if (fcgi_gen_response(c, "<div id='tree_line' "
1023 "class='") == -1)
1024 goto done;
1025 if (fcgi_gen_response(c, class) == -1)
1026 goto done;
1027 if (fcgi_gen_response(c, "'>") == -1)
1028 goto done;
1030 if (fcgi_gen_response(c,
1031 "<a href='?index_page=") == -1)
1032 goto done;
1034 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1035 goto done;
1037 if (fcgi_gen_response(c, "&path=") == -1)
1038 goto done;
1039 if (fcgi_gen_response(c, qs->path) == -1)
1040 goto done;
1042 if (fcgi_gen_response(c, "&action=blob") == -1)
1043 goto done;
1045 if (fcgi_gen_response(c, "&commit=") == -1)
1046 goto done;
1047 if (fcgi_gen_response(c, rc->commit_id) == -1)
1048 goto done;
1050 if (fcgi_gen_response(c, "&folder=") == -1)
1051 goto done;
1052 if (fcgi_gen_response(c, qs->folder) == -1)
1053 goto done;
1055 if (fcgi_gen_response(c, "&file=") == -1)
1056 goto done;
1057 if (fcgi_gen_response(c, name) == -1)
1058 goto done;
1060 if (fcgi_gen_response(c, "'>") == -1)
1061 goto done;
1062 if (fcgi_gen_response(c, name) == -1)
1063 goto done;
1064 if (fcgi_gen_response(c, modestr) == -1)
1065 goto done;
1067 if (fcgi_gen_response(c, "</a>") == -1)
1068 goto done;
1070 if (fcgi_gen_response(c, "</div>\n") == -1)
1071 goto done;
1073 if (fcgi_gen_response(c, "<div id='tree_line_blank' "
1074 "class='") == -1)
1075 goto done;
1076 if (fcgi_gen_response(c, class) == -1)
1077 goto done;
1078 if (fcgi_gen_response(c, "'>") == -1)
1079 goto done;
1081 if (fcgi_gen_response(c,
1082 "<a href='?index_page=") == -1)
1083 goto done;
1085 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1086 goto done;
1088 if (fcgi_gen_response(c, "&path=") == -1)
1089 goto done;
1090 if (fcgi_gen_response(c, qs->path) == -1)
1091 goto done;
1093 if (fcgi_gen_response(c, "&action=commits") == -1)
1094 goto done;
1096 if (fcgi_gen_response(c, "&commit=") == -1)
1097 goto done;
1098 if (fcgi_gen_response(c, rc->commit_id) == -1)
1099 goto done;
1101 if (fcgi_gen_response(c, "&folder=") == -1)
1102 goto done;
1103 if (fcgi_gen_response(c, qs->folder) == -1)
1104 goto done;
1106 if (fcgi_gen_response(c, "&file=") == -1)
1107 goto done;
1108 if (fcgi_gen_response(c, name) == -1)
1109 goto done;
1111 if (fcgi_gen_response(c, "'>") == -1)
1112 goto done;
1114 if (fcgi_gen_response(c, "commits") == -1)
1115 goto done;
1116 if (fcgi_gen_response(c, "</a>\n") == -1)
1117 goto done;
1119 if (fcgi_gen_response(c, " | \n") == -1)
1120 goto done;
1122 if (fcgi_gen_response(c,
1123 "<a href='?index_page=") == -1)
1124 goto done;
1126 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1127 goto done;
1129 if (fcgi_gen_response(c, "&path=") == -1)
1130 goto done;
1131 if (fcgi_gen_response(c, qs->path) == -1)
1132 goto done;
1134 if (fcgi_gen_response(c, "&action=blame") == -1)
1135 goto done;
1137 if (fcgi_gen_response(c, "&commit=") == -1)
1138 goto done;
1139 if (fcgi_gen_response(c, rc->commit_id) == -1)
1140 goto done;
1142 if (fcgi_gen_response(c, "&folder=") == -1)
1143 goto done;
1144 if (fcgi_gen_response(c, qs->folder) == -1)
1145 goto done;
1147 if (fcgi_gen_response(c, "&file=") == -1)
1148 goto done;
1149 if (fcgi_gen_response(c, name) == -1)
1150 goto done;
1152 if (fcgi_gen_response(c, "'>") == -1)
1153 goto done;
1155 if (fcgi_gen_response(c, "blame") == -1)
1156 goto done;
1157 if (fcgi_gen_response(c, "</a>\n") == -1)
1158 goto done;
1160 if (fcgi_gen_response(c, "</div>\n") == -1)
1161 goto done;
1162 if (fcgi_gen_response(c, "</div>\n") == -1)
1163 goto done;
1165 free(id_str);
1166 id_str = NULL;
1167 free(build_folder);
1168 build_folder = NULL;
1169 free(name);
1170 name = NULL;
1171 free(modestr);
1172 modestr = NULL;
1173 free(class);
1174 class = NULL;
1176 done:
1177 free(id_str);
1178 free(build_folder);
1179 free(modestr);
1180 free(path);
1181 free(name);
1182 free(class);
1183 got_ref_list_free(&refs);
1184 if (commit)
1185 got_object_commit_close(commit);
1186 free(commit_id);
1187 free(tree_id);
1188 return error;
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;
1205 size_t len, hdrlen;
1206 const uint8_t *buf;
1208 TAILQ_INIT(&refs);
1210 if (asprintf(&path, "%s%s%s", qs->folder ? qs->folder : "",
1211 qs->folder ? "/" : "", qs->file) == -1) {
1212 error = got_error_from_errno("asprintf");
1213 goto done;
1216 error = got_repo_map_path(&in_repo_path, repo, path);
1217 if (error)
1218 goto done;
1220 error = got_repo_match_object_id(&commit_id, NULL, qs->commit,
1221 GOT_OBJ_TYPE_COMMIT, &refs, repo);
1222 if (error)
1223 goto done;
1225 error = got_object_open_as_commit(&commit, repo, commit_id);
1226 if (error)
1227 goto done;
1229 error = got_object_id_by_path(&commit_id, repo, commit, in_repo_path);
1230 if (error)
1231 goto done;
1233 if (commit_id == NULL) {
1234 error = got_error(GOT_ERR_NO_OBJ);
1235 goto done;
1238 error = got_object_get_type(&obj_type, repo, commit_id);
1239 if (error)
1240 goto done;
1242 if (obj_type != GOT_OBJ_TYPE_BLOB) {
1243 error = got_error(GOT_ERR_OBJ_TYPE);
1244 goto done;
1247 error = got_gotweb_dupfd(&c->priv_fd[BLOB_FD_1], &fd);
1248 if (error)
1249 goto done;
1251 error = got_object_open_as_blob(&blob, repo, commit_id, BUF, fd);
1252 if (error)
1253 goto done;
1254 hdrlen = got_object_blob_get_hdrlen(blob);
1255 do {
1256 error = got_object_blob_read_block(&len, blob);
1257 if (error)
1258 goto done;
1259 buf = got_object_blob_get_read_buf(blob);
1262 * Skip blob object header first time around,
1263 * which also contains a zero byte.
1265 buf += hdrlen;
1266 if (set_mime == 0) {
1267 if (isbinary(buf, len - hdrlen)) {
1268 error = gotweb_render_content_type_file(c,
1269 "application/octet-stream",
1270 qs->file);
1271 if (error) {
1272 log_warnx("%s: %s", __func__,
1273 error->msg);
1274 goto done;
1276 type = 0;
1277 } else {
1278 error = gotweb_render_content_type(c,
1279 "text/text");
1280 if (error) {
1281 log_warnx("%s: %s", __func__,
1282 error->msg);
1283 goto done;
1285 type = 1;
1288 set_mime = 1;
1289 if (type) {
1290 buf_output = calloc(len - hdrlen + 1,
1291 sizeof(*buf_output));
1292 if (buf_output == NULL) {
1293 error = got_error_from_errno("calloc");
1294 goto done;
1296 memcpy(buf_output, buf, len - hdrlen);
1297 fcgi_gen_response(c, buf_output);
1298 free(buf_output);
1299 buf_output = NULL;
1300 } else
1301 fcgi_gen_binary_response(c, buf, len - hdrlen);
1303 hdrlen = 0;
1304 } while (len != 0);
1305 done:
1306 if (commit)
1307 got_object_commit_close(commit);
1308 if (fd != -1 && close(fd) == -1 && error == NULL)
1309 error = got_error_from_errno("close");
1310 if (blob)
1311 got_object_blob_close(blob);
1312 free(buf_output);
1313 free(in_repo_path);
1314 free(commit_id);
1315 free(path);
1316 return error;
1319 struct blame_line {
1320 int annotated;
1321 char *id_str;
1322 char *committer;
1323 char datebuf[11]; /* YYYY-MM-DD + NUL */
1326 struct blame_cb_args {
1327 struct blame_line *lines;
1328 int nlines;
1329 int nlines_prec;
1330 int lineno_cur;
1331 off_t *line_offsets;
1332 FILE *f;
1333 struct got_repository *repo;
1334 struct request *c;
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;
1350 off_t offset;
1351 struct tm tm;
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);
1358 if (lineno == -1)
1359 return NULL; /* no change in this commit */
1361 /* Annotate this line. */
1362 bline = &a->lines[lineno - 1];
1363 if (bline->annotated)
1364 return NULL;
1365 err = got_object_id_str(&bline->id_str, id);
1366 if (err)
1367 return err;
1369 bline->committer = strdup(got_object_commit_get_committer(commit));
1370 if (bline->committer == NULL) {
1371 err = got_error_from_errno("strdup");
1372 goto done;
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",
1379 &tm) == 0) {
1380 err = got_error(GOT_ERR_NO_SPACE);
1381 goto done;
1383 bline->annotated = 1;
1385 /* Print lines annotated so far. */
1386 bline = &a->lines[a->lineno_cur - 1];
1387 if (!bline->annotated)
1388 goto done;
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");
1393 goto done;
1396 while (bline->annotated) {
1397 int out_buff_size = 100;
1398 char *smallerthan, *at, *nl, *committer;
1399 char out_buff[out_buff_size];
1400 size_t len;
1402 if (getline(&line, &linesize, a->f) == -1) {
1403 if (ferror(a->f))
1404 err = got_error_from_errno("getline");
1405 break;
1408 committer = bline->committer;
1409 smallerthan = strchr(committer, '<');
1410 if (smallerthan && smallerthan[1] != '\0')
1411 committer = smallerthan + 1;
1412 at = strchr(committer, '@');
1413 if (at)
1414 *at = '\0';
1415 len = strlen(committer);
1416 if (len >= 9)
1417 committer[8] = '\0';
1419 nl = strchr(line, '\n');
1420 if (nl)
1421 *nl = '\0';
1423 if (fcgi_gen_response(c, "<div id='blame_wrapper'>") == -1)
1424 goto done;
1425 if (fcgi_gen_response(c, "<div id='blame_number'>") == -1)
1426 goto done;
1427 if (snprintf(out_buff, strlen(out_buff), "%.*d", a->nlines_prec,
1428 a->lineno_cur) < 0)
1429 goto done;
1430 if (fcgi_gen_response(c, out_buff) == -1)
1431 goto done;
1432 if (fcgi_gen_response(c, "</div>") == -1)
1433 goto done;
1435 if (fcgi_gen_response(c, "<div id='blame_hash'>") == -1)
1436 goto done;
1438 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1439 goto done;
1440 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1441 goto done;
1442 if (fcgi_gen_response(c, "&path=") == -1)
1443 goto done;
1444 if (fcgi_gen_response(c, repo_dir->name) == -1)
1445 goto done;
1446 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1447 goto done;
1448 if (fcgi_gen_response(c, bline->id_str) == -1)
1449 goto done;
1450 if (fcgi_gen_response(c, "'>") == -1)
1451 goto done;
1452 if (snprintf(out_buff, 10, "%.8s", bline->id_str) < 0)
1453 goto done;
1454 if (fcgi_gen_response(c, out_buff) == -1)
1455 goto done;
1456 if (fcgi_gen_response(c, "</a></div>") == -1)
1457 goto done;
1459 if (fcgi_gen_response(c, "<div id='blame_date'>") == -1)
1460 goto done;
1461 if (fcgi_gen_response(c, bline->datebuf) == -1)
1462 goto done;
1463 if (fcgi_gen_response(c, "</div>") == -1)
1464 goto done;
1466 if (fcgi_gen_response(c, "<div id='blame_author'>") == -1)
1467 goto done;
1468 if (fcgi_gen_response(c, committer) == -1)
1469 goto done;
1470 if (fcgi_gen_response(c, "</div>") == -1)
1471 goto done;
1473 if (fcgi_gen_response(c, "<div id='blame_code'>") == -1)
1474 goto done;
1475 err = gotweb_escape_html(&eline, line);
1476 if (err)
1477 goto done;
1478 if (fcgi_gen_response(c, eline) == -1)
1479 goto done;
1480 if (fcgi_gen_response(c, "</div>") == -1)
1481 goto done;
1483 if (fcgi_gen_response(c, "</div>") == -1)
1484 goto done;
1485 a->lineno_cur++;
1486 bline = &a->lines[a->lineno_cur - 1];
1488 done:
1489 free(line);
1490 free(eline);
1491 return err;
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;
1508 int fd6 = -1;
1509 off_t filesize;
1510 FILE *f1 = NULL, *f2 = NULL;
1512 TAILQ_INIT(&refs);
1513 bca.f = NULL;
1514 bca.lines = 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");
1519 goto done;
1522 error = got_repo_map_path(&in_repo_path, repo, path);
1523 if (error)
1524 goto done;
1526 error = got_repo_match_object_id(&commit_id, NULL, qs->commit,
1527 GOT_OBJ_TYPE_COMMIT, &refs, repo);
1528 if (error)
1529 goto done;
1531 error = got_object_open_as_commit(&commit, repo, commit_id);
1532 if (error)
1533 goto done;
1535 error = got_object_id_by_path(&obj_id, repo, commit, in_repo_path);
1536 if (error)
1537 goto done;
1539 if (commit_id == NULL) {
1540 error = got_error(GOT_ERR_NO_OBJ);
1541 goto done;
1544 error = got_object_get_type(&obj_type, repo, obj_id);
1545 if (error)
1546 goto done;
1548 if (obj_type != GOT_OBJ_TYPE_BLOB) {
1549 error = got_error(GOT_ERR_OBJ_TYPE);
1550 goto done;
1553 error = got_gotweb_openfile(&bca.f, &c->priv_fd[BLAME_FD_1], &fd1);
1554 if (error)
1555 goto done;
1557 error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_2], &fd2);
1558 if (error)
1559 goto done;
1561 error = got_object_open_as_blob(&blob, repo, obj_id, BUF, fd2);
1562 if (error)
1563 goto done;
1565 error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
1566 &bca.line_offsets, bca.f, blob);
1567 if (error || bca.nlines == 0)
1568 goto done;
1570 /* Don't include \n at EOF in the blame line count. */
1571 if (bca.line_offsets[bca.nlines - 1] == filesize)
1572 bca.nlines--;
1574 bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
1575 if (bca.lines == NULL) {
1576 error = got_error_from_errno("calloc");
1577 goto done;
1579 bca.lineno_cur = 1;
1580 bca.nlines_prec = 0;
1581 i = bca.nlines;
1582 while (i > 0) {
1583 i /= 10;
1584 bca.nlines_prec++;
1586 bca.repo = repo;
1587 bca.c = c;
1589 error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_3], &fd3);
1590 if (error)
1591 goto done;
1593 error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_4], &fd4);
1594 if (error)
1595 goto done;
1597 error = got_gotweb_openfile(&f1, &c->priv_fd[BLAME_FD_5], &fd5);
1598 if (error)
1599 goto done;
1601 error = got_gotweb_openfile(&f2, &c->priv_fd[BLAME_FD_6], &fd6);
1602 if (error)
1603 goto done;
1605 error = got_blame(in_repo_path, commit_id, repo,
1606 GOT_DIFF_ALGORITHM_MYERS, got_gotweb_blame_cb, &bca, NULL, NULL,
1607 fd3, fd4, f1, f2);
1609 if (blob) {
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);
1617 done:
1618 free(bca.lines);
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");
1625 if (bca.f) {
1626 const struct got_error *bca_err =
1627 got_gotweb_flushfile(bca.f, fd1);
1628 if (error == NULL)
1629 error = bca_err;
1631 if (f1) {
1632 const struct got_error *f1_err =
1633 got_gotweb_flushfile(f1, fd5);
1634 if (error == NULL)
1635 error = f1_err;
1637 if (f2) {
1638 const struct got_error *f2_err =
1639 got_gotweb_flushfile(f2, fd6);
1640 if (error == NULL)
1641 error = f2_err;
1643 if (commit)
1644 got_object_commit_close(commit);
1645 if (blob)
1646 got_object_blob_close(blob);
1647 free(in_repo_path);
1648 free(commit_id);
1649 free(path);
1650 return error;
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;
1667 ssize_t linelen;
1668 int wrlen = 0;
1670 TAILQ_INIT(&refs);
1672 error = got_gotweb_openfile(&f1, &c->priv_fd[DIFF_FD_1], &fd1);
1673 if (error)
1674 return error;
1676 error = got_gotweb_openfile(&f2, &c->priv_fd[DIFF_FD_2], &fd2);
1677 if (error)
1678 return error;
1680 error = got_gotweb_openfile(&f3, &c->priv_fd[DIFF_FD_3], &fd3);
1681 if (error)
1682 return error;
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,
1690 &refs, repo);
1691 if (error)
1692 goto done;
1695 error = got_repo_match_object_id(&id2, &label2, rc->commit_id,
1696 GOT_OBJ_TYPE_ANY, &refs, repo);
1697 if (error)
1698 goto done;
1700 error = got_object_get_type(&obj_type, repo, id2);
1701 if (error)
1702 goto done;
1704 error = got_gotweb_dupfd(&c->priv_fd[DIFF_FD_4], &fd4);
1705 if (error)
1706 goto done;
1708 error = got_gotweb_dupfd(&c->priv_fd[DIFF_FD_5], &fd5);
1709 if (error)
1710 goto done;
1712 switch (obj_type) {
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,
1716 repo, f3);
1717 break;
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,
1721 repo, f3);
1722 break;
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,
1726 repo, f3);
1727 break;
1728 default:
1729 error = got_error(GOT_ERR_OBJ_TYPE);
1731 if (error)
1732 goto done;
1734 if (fseek(f1, 0, SEEK_SET) == -1) {
1735 error = got_ferror(f1, GOT_ERR_IO);
1736 goto done;
1739 if (fseek(f2, 0, SEEK_SET) == -1) {
1740 error = got_ferror(f2, GOT_ERR_IO);
1741 goto done;
1744 if (fseek(f3, 0, SEEK_SET) == -1) {
1745 error = got_ferror(f3, GOT_ERR_IO);
1746 goto done;
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");
1754 goto done;
1756 } else if (strncmp(line, "+", 1) == 0) {
1757 color = strdup("diff_plus");
1758 if (color == NULL) {
1759 error = got_error_from_errno("strdup");
1760 goto done;
1762 } else if (strncmp(line, "@@", 2) == 0) {
1763 color = strdup("diff_chunk_header");
1764 if (color == NULL) {
1765 error = got_error_from_errno("strdup");
1766 goto done;
1768 } else if (strncmp(line, "@@", 2) == 0) {
1769 color = strdup("diff_chunk_header");
1770 if (color == NULL) {
1771 error = got_error_from_errno("strdup");
1772 goto done;
1774 } else if (strncmp(line, "commit +", 8) == 0) {
1775 color = strdup("diff_meta");
1776 if (color == NULL) {
1777 error = got_error_from_errno("strdup");
1778 goto done;
1780 } else if (strncmp(line, "commit -", 8) == 0) {
1781 color = strdup("diff_meta");
1782 if (color == NULL) {
1783 error = got_error_from_errno("strdup");
1784 goto done;
1786 } else if (strncmp(line, "blob +", 6) == 0) {
1787 color = strdup("diff_meta");
1788 if (color == NULL) {
1789 error = got_error_from_errno("strdup");
1790 goto done;
1792 } else if (strncmp(line, "blob -", 6) == 0) {
1793 color = strdup("diff_meta");
1794 if (color == NULL) {
1795 error = got_error_from_errno("strdup");
1796 goto done;
1798 } else if (strncmp(line, "file +", 6) == 0) {
1799 color = strdup("diff_meta");
1800 if (color == NULL) {
1801 error = got_error_from_errno("strdup");
1802 goto done;
1804 } else if (strncmp(line, "file -", 6) == 0) {
1805 color = strdup("diff_meta");
1806 if (color == NULL) {
1807 error = got_error_from_errno("strdup");
1808 goto done;
1810 } else if (strncmp(line, "from:", 5) == 0) {
1811 color = strdup("diff_author");
1812 if (color == NULL) {
1813 error = got_error_from_errno("strdup");
1814 goto done;
1816 } else if (strncmp(line, "via:", 4) == 0) {
1817 color = strdup("diff_author");
1818 if (color == NULL) {
1819 error = got_error_from_errno("strdup");
1820 goto done;
1822 } else if (strncmp(line, "date:", 5) == 0) {
1823 color = strdup("diff_date");
1824 if (color == NULL) {
1825 error = got_error_from_errno("strdup");
1826 goto done;
1829 if (fcgi_gen_response(c, "<div id='diff_line' class='") == -1)
1830 goto done;
1831 if (fcgi_gen_response(c, color ? color : "") == -1)
1832 goto done;
1833 if (fcgi_gen_response(c, "'>") == -1)
1834 goto done;
1835 newline = strchr(line, '\n');
1836 if (newline)
1837 *newline = '\0';
1839 error = gotweb_escape_html(&eline, line);
1840 if (error)
1841 goto done;
1842 if (fcgi_gen_response(c, eline) == -1)
1843 goto done;
1844 free(eline);
1845 eline = NULL;
1847 if (fcgi_gen_response(c, "</div>\n") == -1)
1848 goto done;
1849 if (linelen > 0)
1850 wrlen = wrlen + linelen;
1851 free(color);
1852 color = NULL;
1854 if (linelen == -1 && ferror(f3))
1855 error = got_error_from_errno("getline");
1856 done:
1857 free(color);
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");
1862 if (f1) {
1863 const struct got_error *f1_err =
1864 got_gotweb_flushfile(f1, fd1);
1865 if (error == NULL)
1866 error = f1_err;
1868 if (f2) {
1869 const struct got_error *f2_err =
1870 got_gotweb_flushfile(f2, fd2);
1871 if (error == NULL)
1872 error = f2_err;
1874 if (f3) {
1875 const struct got_error *f3_err =
1876 got_gotweb_flushfile(f3, fd3);
1877 if (error == NULL)
1878 error = f3_err;
1880 got_ref_list_free(&refs);
1881 free(line);
1882 free(eline);
1883 free(label1);
1884 free(label2);
1885 free(id1);
1886 free(id2);
1887 return error;
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));
1896 if (*rc == NULL)
1897 return got_error_from_errno2("%s: calloc", __func__);
1899 (*rc)->path = NULL;
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;
1908 return error;
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));
1917 if (*rt == NULL)
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;
1926 return error;