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/socket.h>
28 #include "got_error.h"
29 #include "got_object.h"
30 #include "got_reference.h"
31 #include "got_repository.h"
33 #include "got_cancel.h"
35 #include "got_commit_graph.h"
36 #include "got_blame.h"
37 #include "got_privsep.h"
39 #include "got_compat.h"
44 static const struct got_error
*got_init_repo_commit(struct repo_commit
**);
45 static const struct got_error
*got_init_repo_tag(struct repo_tag
**);
46 static const struct got_error
*got_get_repo_commit(struct request
*,
47 struct repo_commit
*, struct got_commit_object
*, struct got_reflist_head
*,
48 struct got_object_id
*);
49 static const struct got_error
*got_gotweb_dupfd(int *, int *);
50 static const struct got_error
*got_gotweb_openfile(FILE **, int *, int *);
51 static const struct got_error
*got_gotweb_blame_cb(void *, int, int,
52 struct got_commit_object
*,struct got_object_id
*);
54 const struct got_error
*
55 got_gotweb_flushfile(FILE *f
, int fd
)
57 if (fseek(f
, 0, SEEK_SET
) == -1)
58 return got_error_from_errno("fseek");
60 if (ftruncate(fd
, 0) == -1)
61 return got_error_from_errno("ftruncate");
64 return got_error_from_errno("fsync");
66 if (f
&& fclose(f
) == EOF
)
67 return got_error_from_errno("fclose");
69 if (fd
!= -1 && close(fd
) != -1)
70 return got_error_from_errno("close");
75 static const struct got_error
*
76 got_gotweb_openfile(FILE **f
, int *priv_fd
, int *fd
)
80 return got_error_from_errno("dup");
82 *f
= fdopen(*fd
, "w+");
85 return got_error(GOT_ERR_PRIVSEP_NO_FD
);
91 static const struct got_error
*
92 got_gotweb_dupfd(int *priv_fd
, int *fd
)
102 const struct got_error
*
103 got_get_repo_owner(char **owner
, struct request
*c
)
105 struct server
*srv
= c
->srv
;
106 struct transport
*t
= c
->t
;
107 struct got_repository
*repo
= t
->repo
;
108 const char *gitconfig_owner
;
112 if (srv
->show_repo_owner
== 0)
115 gitconfig_owner
= got_repo_get_gitconfig_owner(repo
);
116 if (gitconfig_owner
) {
117 *owner
= strdup(gitconfig_owner
);
119 return got_error_from_errno("strdup");
123 return got_error_from_errno("strdup");
128 const struct got_error
*
129 got_get_repo_age(char **repo_age
, struct request
*c
,
130 const char *refname
, int ref_tm
)
132 const struct got_error
*error
= NULL
;
133 struct server
*srv
= c
->srv
;
134 struct transport
*t
= c
->t
;
135 struct got_repository
*repo
= t
->repo
;
136 struct got_commit_object
*commit
= NULL
;
137 struct got_reflist_head refs
;
138 struct got_reflist_entry
*re
;
139 time_t committer_time
= 0, cmp_time
= 0;
144 if (srv
->show_repo_age
== 0)
147 error
= got_ref_list(&refs
, repo
, "refs/heads",
148 got_ref_cmp_by_name
, NULL
);
153 * Find the youngest branch tip in the repository, or the age of
154 * the a specific branch tip if a name was provided by the caller.
156 TAILQ_FOREACH(re
, &refs
, entry
) {
157 struct got_object_id
*id
= NULL
;
159 if (refname
&& strcmp(got_ref_get_name(re
->ref
), refname
) != 0)
162 error
= got_ref_resolve(&id
, repo
, re
->ref
);
166 error
= got_object_open_as_commit(&commit
, repo
, id
);
172 got_object_commit_get_committer_time(commit
);
173 got_object_commit_close(commit
);
174 if (cmp_time
< committer_time
)
175 cmp_time
= committer_time
;
182 committer_time
= cmp_time
;
183 error
= gotweb_get_time_str(repo_age
, committer_time
, ref_tm
);
186 got_ref_list_free(&refs
);
190 static const struct got_error
*
191 got_get_repo_commit(struct request
*c
, struct repo_commit
*repo_commit
,
192 struct got_commit_object
*commit
, struct got_reflist_head
*refs
,
193 struct got_object_id
*id
)
195 const struct got_error
*error
= NULL
;
196 struct got_reflist_entry
*re
;
197 struct got_object_id
*id2
= NULL
;
198 struct got_object_qid
*parent_id
;
199 struct transport
*t
= c
->t
;
200 struct querystring
*qs
= c
->t
->qs
;
201 char *commit_msg
= NULL
, *commit_msg0
;
203 TAILQ_FOREACH(re
, refs
, entry
) {
206 struct got_tag_object
*tag
= NULL
;
207 struct got_object_id
*ref_id
;
210 if (got_ref_is_symbolic(re
->ref
))
213 name
= got_ref_get_name(re
->ref
);
214 if (strncmp(name
, "refs/", 5) == 0)
216 if (strncmp(name
, "got/", 4) == 0)
218 if (strncmp(name
, "heads/", 6) == 0)
220 if (strncmp(name
, "remotes/", 8) == 0) {
222 if (strstr(name
, "/" GOT_REF_HEAD
) != NULL
)
225 error
= got_ref_resolve(&ref_id
, t
->repo
, re
->ref
);
228 if (strncmp(name
, "tags/", 5) == 0) {
229 error
= got_object_open_as_tag(&tag
, t
->repo
, ref_id
);
231 if (error
->code
!= GOT_ERR_OBJ_TYPE
) {
236 * Ref points at something other
243 cmp
= got_object_id_cmp(tag
?
244 got_object_tag_get_object_id(tag
) : ref_id
, id
);
247 got_object_tag_close(tag
);
250 s
= repo_commit
->refs_str
;
251 if (asprintf(&repo_commit
->refs_str
, "%s%s%s", s
? s
: "",
252 s
? ", " : "", name
) == -1) {
253 error
= got_error_from_errno("asprintf");
255 repo_commit
->refs_str
= NULL
;
261 error
= got_object_id_str(&repo_commit
->commit_id
, id
);
265 error
= got_object_id_str(&repo_commit
->tree_id
,
266 got_object_commit_get_tree_id(commit
));
270 if (qs
->action
== DIFF
) {
271 parent_id
= STAILQ_FIRST(
272 got_object_commit_get_parent_ids(commit
));
273 if (parent_id
!= NULL
) {
274 id2
= got_object_id_dup(&parent_id
->id
);
275 error
= got_object_id_str(&repo_commit
->parent_id
, id2
);
280 repo_commit
->parent_id
= strdup("/dev/null");
281 if (repo_commit
->parent_id
== NULL
) {
282 error
= got_error_from_errno("strdup");
288 repo_commit
->committer_time
=
289 got_object_commit_get_committer_time(commit
);
291 repo_commit
->author
=
292 strdup(got_object_commit_get_author(commit
));
293 if (repo_commit
->author
== NULL
) {
294 error
= got_error_from_errno("strdup");
297 repo_commit
->committer
=
298 strdup(got_object_commit_get_committer(commit
));
299 if (repo_commit
->committer
== NULL
) {
300 error
= got_error_from_errno("strdup");
303 error
= got_object_commit_get_logmsg(&commit_msg0
, commit
);
307 commit_msg
= commit_msg0
;
308 while (*commit_msg
== '\n')
311 repo_commit
->commit_msg
= strdup(commit_msg
);
312 if (repo_commit
->commit_msg
== NULL
)
313 error
= got_error_from_errno("strdup");
318 const struct got_error
*
319 got_get_repo_commits(struct request
*c
, int limit
)
321 const struct got_error
*error
= NULL
;
322 struct got_object_id
*id
= NULL
;
323 struct got_commit_graph
*graph
= NULL
;
324 struct got_commit_object
*commit
= NULL
;
325 struct got_reflist_head refs
;
326 struct got_reference
*ref
= NULL
;
327 struct repo_commit
*repo_commit
= NULL
;
328 struct server
*srv
= c
->srv
;
329 struct transport
*t
= c
->t
;
330 struct got_repository
*repo
= t
->repo
;
331 struct querystring
*qs
= t
->qs
;
332 struct repo_dir
*repo_dir
= t
->repo_dir
;
333 char *in_repo_path
= NULL
, *repo_path
= NULL
, *file_path
= NULL
;
334 int chk_next
= 0, chk_multi
= 0, commit_found
= 0;
335 int obj_type
, limit_chk
= 0;
339 if (qs
->file
!= NULL
&& strlen(qs
->file
) > 0)
340 if (asprintf(&file_path
, "%s/%s", qs
->folder
? qs
->folder
: "",
342 return got_error_from_errno("asprintf");
344 if (asprintf(&repo_path
, "%s/%s", srv
->repos_path
,
345 repo_dir
->name
) == -1) {
346 error
= got_error_from_errno("asprintf");
351 * XXX: jumping directly to a commit id via
352 * got_repo_match_object_id_prefix significantly improves performance,
353 * but does not allow us to create a PREVIOUS button, since commits can
354 * only be itereated forward. So, we have to match as we iterate from
357 if (qs
->action
== BRIEFS
|| qs
->action
== COMMITS
||
358 (qs
->action
== TREE
&& qs
->commit
== NULL
)) {
359 error
= got_ref_open(&ref
, repo
, qs
->headref
, 0);
363 error
= got_ref_resolve(&id
, repo
, ref
);
366 } else if (qs
->commit
!= NULL
) {
367 error
= got_ref_open(&ref
, repo
, qs
->commit
, 0);
369 error
= got_ref_resolve(&id
, repo
, ref
);
372 error
= got_object_get_type(&obj_type
, repo
, id
);
375 if (obj_type
== GOT_OBJ_TYPE_TAG
) {
376 struct got_tag_object
*tag
;
377 error
= got_object_open_as_tag(&tag
, repo
, id
);
380 if (got_object_tag_get_object_type(tag
) !=
381 GOT_OBJ_TYPE_COMMIT
) {
382 got_object_tag_close(tag
);
383 error
= got_error(GOT_ERR_OBJ_TYPE
);
387 id
= got_object_id_dup(
388 got_object_tag_get_object_id(tag
));
390 error
= got_error_from_errno(
391 "got_object_id_dup");
392 got_object_tag_close(tag
);
395 } else if (obj_type
!= GOT_OBJ_TYPE_COMMIT
) {
396 error
= got_error(GOT_ERR_OBJ_TYPE
);
400 error
= got_repo_match_object_id_prefix(&id
, qs
->commit
,
401 GOT_OBJ_TYPE_COMMIT
, repo
);
406 error
= got_repo_map_path(&in_repo_path
, repo
, repo_path
);
410 error
= got_ref_list(&refs
, repo
, NULL
, got_ref_cmp_by_name
, NULL
);
414 if (qs
->file
!= NULL
&& strlen(qs
->file
) > 0) {
415 error
= got_commit_graph_open(&graph
, file_path
, 0);
419 error
= got_commit_graph_open(&graph
, in_repo_path
, 0);
424 error
= got_commit_graph_iter_start(graph
, id
, repo
, NULL
, NULL
);
429 struct got_object_id next_id
;
431 error
= got_commit_graph_iter_next(&next_id
, graph
, repo
, NULL
,
434 if (error
->code
== GOT_ERR_ITER_COMPLETED
)
439 error
= got_object_open_as_commit(&commit
, repo
, &next_id
);
443 error
= got_ref_list(&refs
, repo
, NULL
, got_ref_cmp_by_name
,
448 error
= got_init_repo_commit(&repo_commit
);
452 error
= got_get_repo_commit(c
, repo_commit
, commit
,
455 gotweb_free_repo_commit(repo_commit
);
459 if (limit_chk
== ((limit
* qs
->page
) - limit
) &&
460 commit_found
== 0 && repo_commit
->commit_id
!= NULL
) {
461 t
->prev_id
= strdup(repo_commit
->commit_id
);
462 if (t
->prev_id
== NULL
) {
463 error
= got_error_from_errno("strdup");
464 gotweb_free_repo_commit(repo_commit
);
469 if (qs
->commit
!= NULL
&& commit_found
== 0 && limit
!= 1) {
470 if (strcmp(qs
->commit
, repo_commit
->commit_id
) == 0)
472 else if (qs
->file
!= NULL
&& strlen(qs
->file
) > 0 &&
476 gotweb_free_repo_commit(repo_commit
);
482 TAILQ_INSERT_TAIL(&t
->repo_commits
, repo_commit
, entry
);
484 if (limit
== 1 && chk_multi
== 0 &&
485 srv
->max_commits_display
!= 1)
491 * check for one more commit before breaking,
492 * so we know whether to navigate through briefs
493 * commits and summary
495 if (chk_next
&& (qs
->action
== BRIEFS
||
496 qs
->action
== COMMITS
|| qs
->action
== SUMMARY
)) {
497 t
->next_id
= strdup(repo_commit
->commit_id
);
498 if (t
->next_id
== NULL
) {
499 error
= got_error_from_errno("strdup");
503 got_object_commit_close(commit
);
506 TAILQ_REMOVE(&t
->repo_commits
, repo_commit
,
508 gotweb_free_repo_commit(repo_commit
);
512 got_ref_list_free(&refs
);
513 if (error
|| (limit
&& --limit
== 0)) {
514 if (commit_found
|| (qs
->file
!= NULL
&&
515 strlen(qs
->file
) > 0))
521 got_object_commit_close(commit
);
529 got_object_commit_close(commit
);
531 got_commit_graph_close(graph
);
532 got_ref_list_free(&refs
);
540 const struct got_error
*
541 got_get_repo_tags(struct request
*c
, int limit
)
543 const struct got_error
*error
= NULL
;
544 struct got_object_id
*id
= NULL
;
545 struct got_commit_object
*commit
= NULL
;
546 struct got_reflist_head refs
;
547 struct got_reference
*ref
;
548 struct got_reflist_entry
*re
;
549 struct server
*srv
= c
->srv
;
550 struct transport
*t
= c
->t
;
551 struct got_repository
*repo
= t
->repo
;
552 struct querystring
*qs
= t
->qs
;
553 struct repo_dir
*repo_dir
= t
->repo_dir
;
554 struct got_tag_object
*tag
= NULL
;
555 struct repo_tag
*rt
= NULL
, *trt
= NULL
;
556 char *in_repo_path
= NULL
, *repo_path
= NULL
, *id_str
= NULL
;
557 char *tag_commit
= NULL
, *tag_commit0
= NULL
;
558 char *commit_msg
= NULL
, *commit_msg0
= NULL
;
559 int chk_next
= 0, chk_multi
= 1, commit_found
= 0, c_cnt
= 0;
563 if (asprintf(&repo_path
, "%s/%s", srv
->repos_path
,
564 repo_dir
->name
) == -1)
565 return got_error_from_errno("asprintf");
567 if (qs
->commit
== NULL
&& (qs
->action
== TAGS
|| qs
->action
== RSS
)) {
568 error
= got_ref_open(&ref
, repo
, qs
->headref
, 0);
571 error
= got_ref_resolve(&id
, repo
, ref
);
575 } else if (qs
->commit
== NULL
&& qs
->action
== TAG
) {
576 error
= got_error_msg(GOT_ERR_EOF
, "commit id missing");
579 error
= got_repo_match_object_id_prefix(&id
, qs
->commit
,
580 GOT_OBJ_TYPE_COMMIT
, repo
);
585 if (qs
->action
!= SUMMARY
&& qs
->action
!= TAGS
) {
586 error
= got_object_open_as_commit(&commit
, repo
, id
);
589 error
= got_object_commit_get_logmsg(&commit_msg0
, commit
);
593 got_object_commit_close(commit
);
598 error
= got_repo_map_path(&in_repo_path
, repo
, repo_path
);
602 error
= got_ref_list(&refs
, repo
, "refs/tags", got_ref_cmp_tags
,
611 * XXX: again, see previous message about caching
614 TAILQ_FOREACH(re
, &refs
, entry
) {
615 struct repo_tag
*new_repo_tag
= NULL
;
616 error
= got_init_repo_tag(&new_repo_tag
);
620 TAILQ_INSERT_TAIL(&t
->repo_tags
, new_repo_tag
, entry
);
622 new_repo_tag
->tag_name
= strdup(got_ref_get_name(re
->ref
));
623 if (new_repo_tag
->tag_name
== NULL
) {
624 error
= got_error_from_errno("strdup");
634 error
= got_ref_resolve(&id
, repo
, re
->ref
);
639 got_object_tag_close(tag
);
640 error
= got_object_open_as_tag(&tag
, repo
, id
);
642 if (error
->code
!= GOT_ERR_OBJ_TYPE
)
644 /* "lightweight" tag */
645 error
= got_object_open_as_commit(&commit
, repo
, id
);
648 new_repo_tag
->tagger
=
649 strdup(got_object_commit_get_committer(commit
));
650 if (new_repo_tag
->tagger
== NULL
) {
651 error
= got_error_from_errno("strdup");
654 new_repo_tag
->tagger_time
=
655 got_object_commit_get_committer_time(commit
);
656 error
= got_object_id_str(&id_str
, id
);
660 new_repo_tag
->tagger
=
661 strdup(got_object_tag_get_tagger(tag
));
662 if (new_repo_tag
->tagger
== NULL
) {
663 error
= got_error_from_errno("strdup");
666 new_repo_tag
->tagger_time
=
667 got_object_tag_get_tagger_time(tag
);
668 error
= got_object_id_str(&id_str
,
669 got_object_tag_get_object_id(tag
));
674 new_repo_tag
->commit_id
= strdup(id_str
);
675 if (new_repo_tag
->commit_id
== NULL
)
678 if (commit_found
== 0 && qs
->commit
!= NULL
&&
679 strncmp(id_str
, qs
->commit
, strlen(id_str
)) != 0)
687 * check for one more commit before breaking,
688 * so we know whether to navigate through briefs
689 * commits and summary
692 t
->next_id
= strdup(new_repo_tag
->commit_id
);
693 if (t
->next_id
== NULL
) {
694 error
= got_error_from_errno("strdup");
698 got_object_commit_close(commit
);
701 TAILQ_REMOVE(&t
->repo_tags
, new_repo_tag
, entry
);
702 gotweb_free_repo_tag(new_repo_tag
);
707 error
= got_object_commit_get_logmsg(&tag_commit0
,
711 got_object_commit_close(commit
);
714 tag_commit0
= strdup(got_object_tag_get_message(tag
));
715 if (tag_commit0
== NULL
) {
716 error
= got_error_from_errno("strdup");
721 tag_commit
= tag_commit0
;
722 while (*tag_commit
== '\n')
724 new_repo_tag
->tag_commit
= strdup(tag_commit
);
725 if (new_repo_tag
->tag_commit
== NULL
) {
726 error
= got_error_from_errno("strdup");
732 if (qs
->action
!= SUMMARY
&& qs
->action
!= TAGS
) {
733 commit_msg
= commit_msg0
;
734 while (*commit_msg
== '\n')
737 new_repo_tag
->commit_msg
= strdup(commit_msg
);
738 if (new_repo_tag
->commit_msg
== NULL
) {
739 error
= got_error_from_errno("strdup");
744 if (limit
&& --limit
== 0) {
753 * we have tailq populated, so find previous commit id
754 * for navigation through briefs and commits
756 if (t
->tag_count
== 0) {
757 TAILQ_FOREACH_SAFE(rt
, &t
->repo_tags
, entry
, trt
) {
758 TAILQ_REMOVE(&t
->repo_tags
, rt
, entry
);
759 gotweb_free_repo_tag(rt
);
762 if (t
->tag_count
> 0 && t
->prev_id
== NULL
&& qs
->commit
!= NULL
) {
764 TAILQ_FOREACH_REVERSE(rt
, &t
->repo_tags
, repo_tags_head
,
766 if (commit_found
== 0 && rt
->commit_id
!= NULL
&&
767 strcmp(qs
->commit
, rt
->commit_id
) != 0) {
771 if (c_cnt
== srv
->max_commits_display
||
772 rt
== TAILQ_FIRST(&t
->repo_tags
)) {
773 t
->prev_id
= strdup(rt
->commit_id
);
774 if (t
->prev_id
== NULL
)
775 error
= got_error_from_errno("strdup");
783 got_object_commit_close(commit
);
785 got_object_tag_close(tag
);
786 got_ref_list_free(&refs
);
796 got_output_repo_tree(struct request
*c
,
797 int (*cb
)(struct template *, struct got_tree_entry
*))
799 const struct got_error
*error
= NULL
;
800 struct transport
*t
= c
->t
;
801 struct got_commit_object
*commit
= NULL
;
802 struct got_repository
*repo
= t
->repo
;
803 struct querystring
*qs
= t
->qs
;
804 struct repo_commit
*rc
= NULL
;
805 struct got_object_id
*tree_id
= NULL
, *commit_id
= NULL
;
806 struct got_reflist_head refs
;
807 struct got_tree_object
*tree
= NULL
;
808 struct got_tree_entry
*te
;
809 struct repo_dir
*repo_dir
= t
->repo_dir
;
810 char *escaped_name
= NULL
, *path
= NULL
;
815 rc
= TAILQ_FIRST(&t
->repo_commits
);
817 if (qs
->folder
!= NULL
) {
818 path
= strdup(qs
->folder
);
820 error
= got_error_from_errno("strdup");
824 error
= got_repo_map_path(&path
, repo
, repo_dir
->path
);
829 error
= got_repo_match_object_id(&commit_id
, NULL
, rc
->commit_id
,
830 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
834 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
838 error
= got_object_id_by_path(&tree_id
, repo
, commit
, path
);
842 error
= got_object_open_as_tree(&tree
, repo
, tree_id
);
846 nentries
= got_object_tree_get_nentries(tree
);
848 for (i
= 0; i
< nentries
; i
++) {
849 te
= got_object_tree_get_entry(tree
, i
);
850 if (cb(c
->tp
, te
) == -1)
856 got_ref_list_free(&refs
);
858 got_object_commit_close(commit
);
860 got_object_tree_close(tree
);
864 log_warnx("%s: %s", __func__
, error
->msg
);
870 const struct got_error
*
871 got_open_blob_for_output(struct got_blob_object
**blob
, int *fd
,
872 int *binary
, struct request
*c
)
874 const struct got_error
*error
= NULL
;
875 struct transport
*t
= c
->t
;
876 struct got_repository
*repo
= t
->repo
;
877 struct querystring
*qs
= c
->t
->qs
;
878 struct got_commit_object
*commit
= NULL
;
879 struct got_object_id
*commit_id
= NULL
;
880 struct got_reflist_head refs
;
881 char *path
= NULL
, *in_repo_path
= NULL
;
890 if (asprintf(&path
, "%s%s%s", qs
->folder
? qs
->folder
: "",
891 qs
->folder
? "/" : "", qs
->file
) == -1) {
892 error
= got_error_from_errno("asprintf");
896 error
= got_repo_map_path(&in_repo_path
, repo
, path
);
900 error
= got_repo_match_object_id(&commit_id
, NULL
, qs
->commit
,
901 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
905 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
909 error
= got_object_id_by_path(&commit_id
, repo
, commit
, in_repo_path
);
913 if (commit_id
== NULL
) {
914 error
= got_error(GOT_ERR_NO_OBJ
);
918 error
= got_object_get_type(&obj_type
, repo
, commit_id
);
922 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
923 error
= got_error(GOT_ERR_OBJ_TYPE
);
927 error
= got_gotweb_dupfd(&c
->priv_fd
[BLOB_FD_1
], fd
);
931 error
= got_object_open_as_blob(blob
, repo
, commit_id
, BUF
, *fd
);
935 error
= got_object_blob_is_binary(binary
, *blob
);
941 got_object_commit_close(commit
);
947 got_object_blob_close(*blob
);
958 const struct got_error
*
959 got_output_file_blob(struct request
*c
)
961 const struct got_error
*error
= NULL
;
962 struct querystring
*qs
= c
->t
->qs
;
963 struct got_blob_object
*blob
= NULL
;
968 error
= got_open_blob_for_output(&blob
, &fd
, &binary
, c
);
973 error
= gotweb_render_content_type_file(c
,
974 "application/octet-stream", qs
->file
, NULL
);
976 error
= gotweb_render_content_type(c
, "text/plain");
979 log_warnx("%s: %s", __func__
, error
->msg
);
984 error
= got_object_blob_read_block(&len
, blob
);
989 buf
= got_object_blob_get_read_buf(blob
);
990 fcgi_gen_binary_response(c
, buf
, len
);
993 if (close(fd
) == -1 && error
== NULL
)
994 error
= got_error_from_errno("close");
996 got_object_blob_close(blob
);
1001 got_output_blob_by_lines(struct template *tp
, struct got_blob_object
*blob
,
1002 int (*cb
)(struct template *, const char *, size_t))
1004 const struct got_error
*err
;
1006 size_t linesize
= 0;
1008 ssize_t linelen
= 0;
1011 err
= got_object_blob_getline(&line
, &linelen
, &linesize
,
1013 if (err
|| linelen
== -1)
1016 if (cb(tp
, line
, lineno
) == -1)
1023 log_warnx("%s: got_object_blob_getline failed: %s",
1024 __func__
, err
->msg
);
1034 char datebuf
[11]; /* YYYY-MM-DD + NUL */
1037 struct blame_cb_args
{
1038 struct blame_line
*lines
;
1042 off_t
*line_offsets
;
1044 struct got_repository
*repo
;
1048 static const struct got_error
*
1049 got_gotweb_blame_cb(void *arg
, int nlines
, int lineno
,
1050 struct got_commit_object
*commit
, struct got_object_id
*id
)
1052 const struct got_error
*err
= NULL
;
1053 struct blame_cb_args
*a
= arg
;
1054 struct blame_line
*bline
;
1055 struct request
*c
= a
->c
;
1056 struct transport
*t
= c
->t
;
1057 struct repo_dir
*repo_dir
= t
->repo_dir
;
1058 char *line
= NULL
, *eline
= NULL
;
1059 size_t linesize
= 0;
1062 time_t committer_time
;
1065 if (nlines
!= a
->nlines
||
1066 (lineno
!= -1 && lineno
< 1) || lineno
> a
->nlines
)
1067 return got_error(GOT_ERR_RANGE
);
1070 return NULL
; /* no change in this commit */
1072 /* Annotate this line. */
1073 bline
= &a
->lines
[lineno
- 1];
1074 if (bline
->annotated
)
1076 err
= got_object_id_str(&bline
->id_str
, id
);
1080 bline
->committer
= strdup(got_object_commit_get_committer(commit
));
1081 if (bline
->committer
== NULL
) {
1082 err
= got_error_from_errno("strdup");
1086 committer_time
= got_object_commit_get_committer_time(commit
);
1087 if (gmtime_r(&committer_time
, &tm
) == NULL
)
1088 return got_error_from_errno("gmtime_r");
1089 if (strftime(bline
->datebuf
, sizeof(bline
->datebuf
), "%G-%m-%d",
1091 err
= got_error(GOT_ERR_NO_SPACE
);
1094 bline
->annotated
= 1;
1096 /* Print lines annotated so far. */
1097 bline
= &a
->lines
[a
->lineno_cur
- 1];
1098 if (!bline
->annotated
)
1101 offset
= a
->line_offsets
[a
->lineno_cur
- 1];
1102 if (fseeko(a
->f
, offset
, SEEK_SET
) == -1) {
1103 err
= got_error_from_errno("fseeko");
1107 while (a
->lineno_cur
<= a
->nlines
&& bline
->annotated
) {
1108 char *smallerthan
, *at
, *nl
, *committer
;
1111 if (getline(&line
, &linesize
, a
->f
) == -1) {
1113 err
= got_error_from_errno("getline");
1117 committer
= bline
->committer
;
1118 smallerthan
= strchr(committer
, '<');
1119 if (smallerthan
&& smallerthan
[1] != '\0')
1120 committer
= smallerthan
+ 1;
1121 at
= strchr(committer
, '@');
1124 len
= strlen(committer
);
1126 committer
[8] = '\0';
1128 nl
= strchr(line
, '\n');
1132 err
= gotweb_escape_html(&eline
, line
);
1136 if (fcgi_printf(c
, "<div class='blame_wrapper'>"
1137 "<div class='blame_number'>%.*d</div>"
1138 "<div class='blame_hash'>",
1139 a
->nlines_prec
, a
->lineno_cur
) == -1)
1142 r
= gotweb_link(c
, &(struct gotweb_url
){
1146 .path
= repo_dir
->name
,
1147 .commit
= bline
->id_str
,
1148 }, "%.8s", bline
->id_str
);
1154 "<div class='blame_date'>%s</div>"
1155 "<div class='blame_author'>%s</div>"
1156 "<div class='blame_code'>%s</div>"
1157 "</div>", /* .blame_wrapper */
1165 bline
= &a
->lines
[a
->lineno_cur
- 1];
1176 const struct got_error
*
1177 got_output_file_blame(struct request
*c
)
1179 const struct got_error
*error
= NULL
;
1180 struct transport
*t
= c
->t
;
1181 struct got_repository
*repo
= t
->repo
;
1182 struct querystring
*qs
= c
->t
->qs
;
1183 struct got_object_id
*obj_id
= NULL
, *commit_id
= NULL
;
1184 struct got_commit_object
*commit
= NULL
;
1185 struct got_reflist_head refs
;
1186 struct got_blob_object
*blob
= NULL
;
1187 char *path
= NULL
, *in_repo_path
= NULL
;
1188 struct blame_cb_args bca
;
1189 int i
, obj_type
, fd1
= -1, fd2
= -1, fd3
= -1, fd4
= -1, fd5
= -1;
1192 FILE *f1
= NULL
, *f2
= NULL
;
1198 if (asprintf(&path
, "%s%s%s", qs
->folder
? qs
->folder
: "",
1199 qs
->folder
? "/" : "", qs
->file
) == -1) {
1200 error
= got_error_from_errno("asprintf");
1204 error
= got_repo_map_path(&in_repo_path
, repo
, path
);
1208 error
= got_repo_match_object_id(&commit_id
, NULL
, qs
->commit
,
1209 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
1213 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
1217 error
= got_object_id_by_path(&obj_id
, repo
, commit
, in_repo_path
);
1221 error
= got_object_get_type(&obj_type
, repo
, obj_id
);
1225 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
1226 error
= got_error(GOT_ERR_OBJ_TYPE
);
1230 error
= got_gotweb_openfile(&bca
.f
, &c
->priv_fd
[BLAME_FD_1
], &fd1
);
1234 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_2
], &fd2
);
1238 error
= got_object_open_as_blob(&blob
, repo
, obj_id
, BUF
, fd2
);
1242 error
= got_object_blob_dump_to_file(&filesize
, &bca
.nlines
,
1243 &bca
.line_offsets
, bca
.f
, blob
);
1244 if (error
|| bca
.nlines
== 0)
1247 /* Don't include \n at EOF in the blame line count. */
1248 if (bca
.line_offsets
[bca
.nlines
- 1] == filesize
)
1251 bca
.lines
= calloc(bca
.nlines
, sizeof(*bca
.lines
));
1252 if (bca
.lines
== NULL
) {
1253 error
= got_error_from_errno("calloc");
1257 bca
.nlines_prec
= 0;
1266 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_3
], &fd3
);
1270 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_4
], &fd4
);
1274 error
= got_gotweb_openfile(&f1
, &c
->priv_fd
[BLAME_FD_5
], &fd5
);
1278 error
= got_gotweb_openfile(&f2
, &c
->priv_fd
[BLAME_FD_6
], &fd6
);
1282 error
= got_blame(in_repo_path
, commit_id
, repo
,
1283 GOT_DIFF_ALGORITHM_MYERS
, got_gotweb_blame_cb
, &bca
, NULL
, NULL
,
1288 free(bca
.line_offsets
);
1289 for (i
= 0; i
< bca
.nlines
; i
++) {
1290 struct blame_line
*bline
= &bca
.lines
[i
];
1291 free(bline
->id_str
);
1292 free(bline
->committer
);
1296 if (fd2
!= -1 && close(fd2
) == -1 && error
== NULL
)
1297 error
= got_error_from_errno("close");
1298 if (fd3
!= -1 && close(fd3
) == -1 && error
== NULL
)
1299 error
= got_error_from_errno("close");
1300 if (fd4
!= -1 && close(fd4
) == -1 && error
== NULL
)
1301 error
= got_error_from_errno("close");
1303 const struct got_error
*bca_err
=
1304 got_gotweb_flushfile(bca
.f
, fd1
);
1309 const struct got_error
*f1_err
=
1310 got_gotweb_flushfile(f1
, fd5
);
1315 const struct got_error
*f2_err
=
1316 got_gotweb_flushfile(f2
, fd6
);
1321 got_object_commit_close(commit
);
1323 got_object_blob_close(blob
);
1328 got_ref_list_free(&refs
);
1332 const struct got_error
*
1333 got_open_diff_for_output(FILE **fp
, int *fd
, struct request
*c
)
1335 const struct got_error
*error
= NULL
;
1336 struct transport
*t
= c
->t
;
1337 struct got_repository
*repo
= t
->repo
;
1338 struct repo_commit
*rc
= NULL
;
1339 struct got_object_id
*id1
= NULL
, *id2
= NULL
;
1340 struct got_reflist_head refs
;
1341 FILE *f1
= NULL
, *f2
= NULL
, *f3
= NULL
;
1342 int obj_type
, fd1
, fd2
, fd3
, fd4
= -1, fd5
= -1;
1349 error
= got_gotweb_openfile(&f1
, &c
->priv_fd
[DIFF_FD_1
], &fd1
);
1353 error
= got_gotweb_openfile(&f2
, &c
->priv_fd
[DIFF_FD_2
], &fd2
);
1357 error
= got_gotweb_openfile(&f3
, &c
->priv_fd
[DIFF_FD_3
], &fd3
);
1361 rc
= TAILQ_FIRST(&t
->repo_commits
);
1363 if (rc
->parent_id
!= NULL
&&
1364 strncmp(rc
->parent_id
, "/dev/null", 9) != 0) {
1365 error
= got_repo_match_object_id(&id1
, NULL
,
1366 rc
->parent_id
, GOT_OBJ_TYPE_ANY
,
1372 error
= got_repo_match_object_id(&id2
, NULL
, rc
->commit_id
,
1373 GOT_OBJ_TYPE_ANY
, &refs
, repo
);
1377 error
= got_object_get_type(&obj_type
, repo
, id2
);
1381 error
= got_gotweb_dupfd(&c
->priv_fd
[DIFF_FD_4
], &fd4
);
1385 error
= got_gotweb_dupfd(&c
->priv_fd
[DIFF_FD_5
], &fd5
);
1390 case GOT_OBJ_TYPE_BLOB
:
1391 error
= got_diff_objects_as_blobs(NULL
, NULL
, f1
, f2
, fd4
, fd5
,
1392 id1
, id2
, NULL
, NULL
, GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1395 case GOT_OBJ_TYPE_TREE
:
1396 error
= got_diff_objects_as_trees(NULL
, NULL
, f1
, f2
, fd4
, fd5
,
1397 id1
, id2
, NULL
, "", "", GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1400 case GOT_OBJ_TYPE_COMMIT
:
1401 error
= got_diff_objects_as_commits(NULL
, NULL
, f1
, f2
, fd4
,
1402 fd5
, id1
, id2
, NULL
, GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1406 error
= got_error(GOT_ERR_OBJ_TYPE
);
1411 if (fseek(f1
, 0, SEEK_SET
) == -1) {
1412 error
= got_ferror(f1
, GOT_ERR_IO
);
1416 if (fseek(f2
, 0, SEEK_SET
) == -1) {
1417 error
= got_ferror(f2
, GOT_ERR_IO
);
1421 if (fseek(f3
, 0, SEEK_SET
) == -1) {
1422 error
= got_ferror(f3
, GOT_ERR_IO
);
1430 if (fd4
!= -1 && close(fd4
) == -1 && error
== NULL
)
1431 error
= got_error_from_errno("close");
1432 if (fd5
!= -1 && close(fd5
) == -1 && error
== NULL
)
1433 error
= got_error_from_errno("close");
1435 const struct got_error
*f1_err
=
1436 got_gotweb_flushfile(f1
, fd1
);
1441 const struct got_error
*f2_err
=
1442 got_gotweb_flushfile(f2
, fd2
);
1447 got_gotweb_flushfile(f3
, fd3
);
1451 got_ref_list_free(&refs
);
1457 static const struct got_error
*
1458 got_init_repo_commit(struct repo_commit
**rc
)
1460 *rc
= calloc(1, sizeof(**rc
));
1462 return got_error_from_errno2("%s: calloc", __func__
);
1465 (*rc
)->refs_str
= NULL
;
1466 (*rc
)->commit_id
= NULL
;
1467 (*rc
)->committer
= NULL
;
1468 (*rc
)->author
= NULL
;
1469 (*rc
)->parent_id
= NULL
;
1470 (*rc
)->tree_id
= NULL
;
1471 (*rc
)->commit_msg
= NULL
;
1476 static const struct got_error
*
1477 got_init_repo_tag(struct repo_tag
**rt
)
1479 *rt
= calloc(1, sizeof(**rt
));
1481 return got_error_from_errno2("%s: calloc", __func__
);
1483 (*rt
)->commit_id
= NULL
;
1484 (*rt
)->tag_name
= NULL
;
1485 (*rt
)->tag_commit
= NULL
;
1486 (*rt
)->commit_msg
= NULL
;
1487 (*rt
)->tagger
= NULL
;