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 "got_compat.h"
20 #include <sys/queue.h>
21 #include <sys/socket.h>
31 #include "got_error.h"
32 #include "got_object.h"
33 #include "got_reference.h"
34 #include "got_repository.h"
36 #include "got_cancel.h"
38 #include "got_commit_graph.h"
39 #include "got_blame.h"
40 #include "got_privsep.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_blame_cb(void *, int, int,
53 struct got_commit_object
*,struct got_object_id
*);
55 const struct got_error
*
56 got_gotweb_flushfile(FILE *f
, int fd
)
58 if (fseek(f
, 0, SEEK_SET
) == -1)
59 return got_error_from_errno("fseek");
61 if (ftruncate(fd
, 0) == -1)
62 return got_error_from_errno("ftruncate");
65 return got_error_from_errno("fsync");
67 if (f
&& fclose(f
) == EOF
)
68 return got_error_from_errno("fclose");
70 if (fd
!= -1 && close(fd
) != -1)
71 return got_error_from_errno("close");
76 static const struct got_error
*
77 got_gotweb_openfile(FILE **f
, int *priv_fd
, int *fd
)
81 return got_error_from_errno("dup");
83 *f
= fdopen(*fd
, "w+");
86 return got_error(GOT_ERR_PRIVSEP_NO_FD
);
92 static const struct got_error
*
93 got_gotweb_dupfd(int *priv_fd
, int *fd
)
98 return got_error_from_errno("dup");
103 const struct got_error
*
104 got_get_repo_owner(char **owner
, struct request
*c
)
106 struct server
*srv
= c
->srv
;
107 struct transport
*t
= c
->t
;
108 struct got_repository
*repo
= t
->repo
;
109 const char *gitconfig_owner
;
113 if (srv
->show_repo_owner
== 0)
116 gitconfig_owner
= got_repo_get_gitconfig_owner(repo
);
117 if (gitconfig_owner
) {
118 *owner
= strdup(gitconfig_owner
);
120 return got_error_from_errno("strdup");
124 return got_error_from_errno("strdup");
129 const struct got_error
*
130 got_get_repo_age(time_t *repo_age
, struct request
*c
, const char *refname
)
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;
143 if (srv
->show_repo_age
== 0)
146 error
= got_ref_list(&refs
, repo
, "refs/heads",
147 got_ref_cmp_by_name
, NULL
);
152 * Find the youngest branch tip in the repository, or the age of
153 * the a specific branch tip if a name was provided by the caller.
155 TAILQ_FOREACH(re
, &refs
, entry
) {
156 struct got_object_id
*id
= NULL
;
158 if (refname
&& strcmp(got_ref_get_name(re
->ref
), refname
) != 0)
161 error
= got_ref_resolve(&id
, repo
, re
->ref
);
165 error
= got_object_open_as_commit(&commit
, repo
, id
);
171 got_object_commit_get_committer_time(commit
);
172 got_object_commit_close(commit
);
173 if (cmp_time
< committer_time
)
174 cmp_time
= committer_time
;
181 *repo_age
= cmp_time
;
183 got_ref_list_free(&refs
);
187 static const struct got_error
*
188 got_get_repo_commit(struct request
*c
, struct repo_commit
*repo_commit
,
189 struct got_commit_object
*commit
, struct got_reflist_head
*refs
,
190 struct got_object_id
*id
)
192 const struct got_error
*error
= NULL
;
193 struct got_reflist_entry
*re
;
194 struct got_object_id
*id2
= NULL
;
195 struct got_object_qid
*parent_id
;
196 struct transport
*t
= c
->t
;
197 struct querystring
*qs
= c
->t
->qs
;
198 char *commit_msg
= NULL
, *commit_msg0
;
200 TAILQ_FOREACH(re
, refs
, entry
) {
203 struct got_tag_object
*tag
= NULL
;
204 struct got_object_id
*ref_id
;
207 if (got_ref_is_symbolic(re
->ref
))
210 name
= got_ref_get_name(re
->ref
);
211 if (strncmp(name
, "refs/", 5) == 0)
213 if (strncmp(name
, "got/", 4) == 0)
215 if (strncmp(name
, "heads/", 6) == 0)
217 if (strncmp(name
, "remotes/", 8) == 0) {
219 if (strstr(name
, "/" GOT_REF_HEAD
) != NULL
)
222 error
= got_ref_resolve(&ref_id
, t
->repo
, re
->ref
);
225 if (strncmp(name
, "tags/", 5) == 0) {
226 error
= got_object_open_as_tag(&tag
, t
->repo
, ref_id
);
228 if (error
->code
!= GOT_ERR_OBJ_TYPE
) {
233 * Ref points at something other
240 cmp
= got_object_id_cmp(tag
?
241 got_object_tag_get_object_id(tag
) : ref_id
, id
);
244 got_object_tag_close(tag
);
247 s
= repo_commit
->refs_str
;
248 if (asprintf(&repo_commit
->refs_str
, "%s%s%s", s
? s
: "",
249 s
? ", " : "", name
) == -1) {
250 error
= got_error_from_errno("asprintf");
252 repo_commit
->refs_str
= NULL
;
258 error
= got_object_id_str(&repo_commit
->commit_id
, id
);
262 error
= got_object_id_str(&repo_commit
->tree_id
,
263 got_object_commit_get_tree_id(commit
));
267 if (qs
->action
== DIFF
) {
268 parent_id
= STAILQ_FIRST(
269 got_object_commit_get_parent_ids(commit
));
270 if (parent_id
!= NULL
) {
271 id2
= got_object_id_dup(&parent_id
->id
);
272 error
= got_object_id_str(&repo_commit
->parent_id
, id2
);
277 repo_commit
->parent_id
= strdup("/dev/null");
278 if (repo_commit
->parent_id
== NULL
) {
279 error
= got_error_from_errno("strdup");
285 repo_commit
->committer_time
=
286 got_object_commit_get_committer_time(commit
);
288 repo_commit
->author
=
289 strdup(got_object_commit_get_author(commit
));
290 if (repo_commit
->author
== NULL
) {
291 error
= got_error_from_errno("strdup");
294 repo_commit
->committer
=
295 strdup(got_object_commit_get_committer(commit
));
296 if (repo_commit
->committer
== NULL
) {
297 error
= got_error_from_errno("strdup");
300 error
= got_object_commit_get_logmsg(&commit_msg0
, commit
);
304 commit_msg
= commit_msg0
;
305 while (*commit_msg
== '\n')
308 repo_commit
->commit_msg
= strdup(commit_msg
);
309 if (repo_commit
->commit_msg
== NULL
)
310 error
= got_error_from_errno("strdup");
315 const struct got_error
*
316 got_get_repo_commits(struct request
*c
, int limit
)
318 const struct got_error
*error
= NULL
;
319 struct got_object_id
*id
= NULL
;
320 struct got_commit_graph
*graph
= NULL
;
321 struct got_commit_object
*commit
= NULL
;
322 struct got_reflist_head refs
;
323 struct got_reference
*ref
= NULL
;
324 struct repo_commit
*repo_commit
= NULL
;
325 struct server
*srv
= c
->srv
;
326 struct transport
*t
= c
->t
;
327 struct got_repository
*repo
= t
->repo
;
328 struct querystring
*qs
= t
->qs
;
329 struct repo_dir
*repo_dir
= t
->repo_dir
;
330 char *in_repo_path
= NULL
, *repo_path
= NULL
, *file_path
= NULL
;
331 int chk_next
= 0, chk_multi
= 0;
335 if (qs
->file
!= NULL
&& strlen(qs
->file
) > 0)
336 if (asprintf(&file_path
, "%s/%s", qs
->folder
? qs
->folder
: "",
338 return got_error_from_errno("asprintf");
340 if (asprintf(&repo_path
, "%s/%s", srv
->repos_path
,
341 repo_dir
->name
) == -1) {
342 error
= got_error_from_errno("asprintf");
347 error
= got_repo_match_object_id_prefix(&id
, qs
->commit
,
348 GOT_OBJ_TYPE_COMMIT
, repo
);
352 error
= got_ref_open(&ref
, repo
, qs
->headref
, 0);
356 error
= got_ref_resolve(&id
, repo
, ref
);
361 error
= got_repo_map_path(&in_repo_path
, repo
, repo_path
);
365 error
= got_ref_list(&refs
, repo
, NULL
, got_ref_cmp_by_name
, NULL
);
369 if (qs
->file
!= NULL
&& strlen(qs
->file
) > 0) {
370 error
= got_commit_graph_open(&graph
, file_path
, 0);
374 error
= got_commit_graph_open(&graph
, in_repo_path
, 0);
379 error
= got_commit_graph_iter_start(graph
, id
, repo
, NULL
, NULL
);
384 struct got_object_id next_id
;
386 error
= got_commit_graph_iter_next(&next_id
, graph
, repo
, NULL
,
389 if (error
->code
== GOT_ERR_ITER_COMPLETED
)
394 error
= got_object_open_as_commit(&commit
, repo
, &next_id
);
398 error
= got_init_repo_commit(&repo_commit
);
402 error
= got_get_repo_commit(c
, repo_commit
, commit
,
405 gotweb_free_repo_commit(repo_commit
);
409 TAILQ_INSERT_TAIL(&t
->repo_commits
, repo_commit
, entry
);
411 if (!chk_multi
|| limit
!= 1 ||
412 srv
->max_commits_display
== 1) {
416 * check for one more commit before breaking,
417 * so we know whether to navigate through briefs
418 * commits and summary
420 if (chk_next
&& (qs
->action
== BRIEFS
||
421 qs
->action
== COMMITS
|| qs
->action
== SUMMARY
)) {
422 t
->more_id
= strdup(repo_commit
->commit_id
);
423 if (t
->more_id
== NULL
) {
424 error
= got_error_from_errno("strdup");
427 got_object_commit_close(commit
);
429 TAILQ_REMOVE(&t
->repo_commits
, repo_commit
,
431 gotweb_free_repo_commit(repo_commit
);
435 if (error
|| (limit
&& --limit
== 0)) {
436 if (qs
->file
!= NULL
&& *qs
->file
!= '\0')
442 got_object_commit_close(commit
);
450 got_object_commit_close(commit
);
452 got_commit_graph_close(graph
);
453 got_ref_list_free(&refs
);
461 const struct got_error
*
462 got_get_repo_tags(struct request
*c
, int limit
)
464 const struct got_error
*error
= NULL
;
465 struct got_object_id
*id
= NULL
;
466 struct got_commit_object
*commit
= NULL
;
467 struct got_reflist_head refs
;
468 struct got_reference
*ref
;
469 struct got_reflist_entry
*re
;
470 struct server
*srv
= c
->srv
;
471 struct transport
*t
= c
->t
;
472 struct got_repository
*repo
= t
->repo
;
473 struct querystring
*qs
= t
->qs
;
474 struct repo_dir
*repo_dir
= t
->repo_dir
;
475 struct got_tag_object
*tag
= NULL
;
476 struct repo_tag
*rt
= NULL
, *trt
= NULL
;
477 char *in_repo_path
= NULL
, *repo_path
= NULL
, *id_str
= NULL
;
478 char *tag_commit
= NULL
, *tag_commit0
= NULL
;
479 char *commit_msg
= NULL
, *commit_msg0
= NULL
;
480 int chk_next
= 0, chk_multi
= 1, commit_found
= 0, c_cnt
= 0;
484 if (asprintf(&repo_path
, "%s/%s", srv
->repos_path
,
485 repo_dir
->name
) == -1)
486 return got_error_from_errno("asprintf");
488 if (qs
->commit
== NULL
&& (qs
->action
== TAGS
|| qs
->action
== RSS
)) {
489 error
= got_ref_open(&ref
, repo
, qs
->headref
, 0);
492 error
= got_ref_resolve(&id
, repo
, ref
);
496 } else if (qs
->commit
== NULL
&& qs
->action
== TAG
) {
497 error
= got_error_msg(GOT_ERR_EOF
, "commit id missing");
500 error
= got_repo_match_object_id_prefix(&id
, qs
->commit
,
501 GOT_OBJ_TYPE_COMMIT
, repo
);
506 if (qs
->action
!= SUMMARY
&& qs
->action
!= TAGS
) {
507 error
= got_object_open_as_commit(&commit
, repo
, id
);
510 error
= got_object_commit_get_logmsg(&commit_msg0
, commit
);
514 got_object_commit_close(commit
);
519 error
= got_repo_map_path(&in_repo_path
, repo
, repo_path
);
523 error
= got_ref_list(&refs
, repo
, "refs/tags", got_ref_cmp_tags
,
532 * XXX: again, see previous message about caching
535 TAILQ_FOREACH(re
, &refs
, entry
) {
536 struct repo_tag
*new_repo_tag
= NULL
;
537 error
= got_init_repo_tag(&new_repo_tag
);
541 TAILQ_INSERT_TAIL(&t
->repo_tags
, new_repo_tag
, entry
);
543 new_repo_tag
->tag_name
= strdup(got_ref_get_name(re
->ref
));
544 if (new_repo_tag
->tag_name
== NULL
) {
545 error
= got_error_from_errno("strdup");
555 error
= got_ref_resolve(&id
, repo
, re
->ref
);
560 got_object_tag_close(tag
);
561 error
= got_object_open_as_tag(&tag
, repo
, id
);
563 if (error
->code
!= GOT_ERR_OBJ_TYPE
)
565 /* "lightweight" tag */
566 error
= got_object_open_as_commit(&commit
, repo
, id
);
569 new_repo_tag
->tagger
=
570 strdup(got_object_commit_get_committer(commit
));
571 if (new_repo_tag
->tagger
== NULL
) {
572 error
= got_error_from_errno("strdup");
575 new_repo_tag
->tagger_time
=
576 got_object_commit_get_committer_time(commit
);
577 error
= got_object_id_str(&id_str
, id
);
581 new_repo_tag
->tagger
=
582 strdup(got_object_tag_get_tagger(tag
));
583 if (new_repo_tag
->tagger
== NULL
) {
584 error
= got_error_from_errno("strdup");
587 new_repo_tag
->tagger_time
=
588 got_object_tag_get_tagger_time(tag
);
589 error
= got_object_id_str(&id_str
,
590 got_object_tag_get_object_id(tag
));
595 new_repo_tag
->commit_id
= strdup(id_str
);
596 if (new_repo_tag
->commit_id
== NULL
)
599 if (commit_found
== 0 && qs
->commit
!= NULL
&&
600 strncmp(id_str
, qs
->commit
, strlen(id_str
)) != 0)
608 * check for one more commit before breaking,
609 * so we know whether to navigate through briefs
610 * commits and summary
613 t
->next_id
= strdup(new_repo_tag
->commit_id
);
614 if (t
->next_id
== NULL
) {
615 error
= got_error_from_errno("strdup");
619 got_object_commit_close(commit
);
622 TAILQ_REMOVE(&t
->repo_tags
, new_repo_tag
, entry
);
623 gotweb_free_repo_tag(new_repo_tag
);
628 error
= got_object_commit_get_logmsg(&tag_commit0
,
632 got_object_commit_close(commit
);
635 tag_commit0
= strdup(got_object_tag_get_message(tag
));
636 if (tag_commit0
== NULL
) {
637 error
= got_error_from_errno("strdup");
642 tag_commit
= tag_commit0
;
643 while (*tag_commit
== '\n')
645 new_repo_tag
->tag_commit
= strdup(tag_commit
);
646 if (new_repo_tag
->tag_commit
== NULL
) {
647 error
= got_error_from_errno("strdup");
653 if (qs
->action
!= SUMMARY
&& qs
->action
!= TAGS
) {
654 commit_msg
= commit_msg0
;
655 while (*commit_msg
== '\n')
658 new_repo_tag
->commit_msg
= strdup(commit_msg
);
659 if (new_repo_tag
->commit_msg
== NULL
) {
660 error
= got_error_from_errno("strdup");
665 if (limit
&& --limit
== 0) {
674 * we have tailq populated, so find previous commit id
675 * for navigation through briefs and commits
677 if (t
->tag_count
== 0) {
678 TAILQ_FOREACH_SAFE(rt
, &t
->repo_tags
, entry
, trt
) {
679 TAILQ_REMOVE(&t
->repo_tags
, rt
, entry
);
680 gotweb_free_repo_tag(rt
);
683 if (t
->tag_count
> 0 && t
->prev_id
== NULL
&& qs
->commit
!= NULL
) {
685 TAILQ_FOREACH_REVERSE(rt
, &t
->repo_tags
, repo_tags_head
,
687 if (commit_found
== 0 && rt
->commit_id
!= NULL
&&
688 strcmp(qs
->commit
, rt
->commit_id
) != 0) {
692 if (c_cnt
== srv
->max_commits_display
||
693 rt
== TAILQ_FIRST(&t
->repo_tags
)) {
694 t
->prev_id
= strdup(rt
->commit_id
);
695 if (t
->prev_id
== NULL
)
696 error
= got_error_from_errno("strdup");
704 got_object_commit_close(commit
);
706 got_object_tag_close(tag
);
707 got_ref_list_free(&refs
);
717 got_output_repo_tree(struct request
*c
,
718 int (*cb
)(struct template *, struct got_tree_entry
*))
720 const struct got_error
*error
= NULL
;
721 struct transport
*t
= c
->t
;
722 struct got_commit_object
*commit
= NULL
;
723 struct got_repository
*repo
= t
->repo
;
724 struct querystring
*qs
= t
->qs
;
725 struct repo_commit
*rc
= NULL
;
726 struct got_object_id
*tree_id
= NULL
, *commit_id
= NULL
;
727 struct got_reflist_head refs
;
728 struct got_tree_object
*tree
= NULL
;
729 struct got_tree_entry
*te
;
730 struct repo_dir
*repo_dir
= t
->repo_dir
;
731 char *escaped_name
= NULL
, *path
= NULL
;
736 rc
= TAILQ_FIRST(&t
->repo_commits
);
738 if (qs
->folder
!= NULL
) {
739 path
= strdup(qs
->folder
);
741 error
= got_error_from_errno("strdup");
745 error
= got_repo_map_path(&path
, repo
, repo_dir
->path
);
750 error
= got_repo_match_object_id(&commit_id
, NULL
, rc
->commit_id
,
751 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
755 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
759 error
= got_object_id_by_path(&tree_id
, repo
, commit
, path
);
763 error
= got_object_open_as_tree(&tree
, repo
, tree_id
);
767 nentries
= got_object_tree_get_nentries(tree
);
769 for (i
= 0; i
< nentries
; i
++) {
770 te
= got_object_tree_get_entry(tree
, i
);
771 if (cb(c
->tp
, te
) == -1) {
772 error
= got_error(GOT_ERR_CANCELLED
);
779 got_ref_list_free(&refs
);
781 got_object_commit_close(commit
);
783 got_object_tree_close(tree
);
787 log_warnx("%s: %s", __func__
, error
->msg
);
793 const struct got_error
*
794 got_open_blob_for_output(struct got_blob_object
**blob
, int *fd
,
795 int *binary
, struct request
*c
)
797 const struct got_error
*error
= NULL
;
798 struct transport
*t
= c
->t
;
799 struct got_repository
*repo
= t
->repo
;
800 struct querystring
*qs
= c
->t
->qs
;
801 struct got_commit_object
*commit
= NULL
;
802 struct got_object_id
*commit_id
= NULL
;
803 struct got_reflist_head refs
;
804 char *path
= NULL
, *in_repo_path
= NULL
;
813 if (asprintf(&path
, "%s%s%s", qs
->folder
? qs
->folder
: "",
814 qs
->folder
? "/" : "", qs
->file
) == -1) {
815 error
= got_error_from_errno("asprintf");
819 error
= got_repo_map_path(&in_repo_path
, repo
, path
);
823 error
= got_repo_match_object_id(&commit_id
, NULL
, qs
->commit
,
824 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
828 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
832 error
= got_object_id_by_path(&commit_id
, repo
, commit
, in_repo_path
);
836 if (commit_id
== NULL
) {
837 error
= got_error(GOT_ERR_NO_OBJ
);
841 error
= got_object_get_type(&obj_type
, repo
, commit_id
);
845 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
846 error
= got_error(GOT_ERR_OBJ_TYPE
);
850 error
= got_gotweb_dupfd(&c
->priv_fd
[BLOB_FD_1
], fd
);
854 error
= got_object_open_as_blob(blob
, repo
, commit_id
, BUF
, *fd
);
858 error
= got_object_blob_is_binary(binary
, *blob
);
864 got_object_commit_close(commit
);
870 got_object_blob_close(*blob
);
882 got_output_blob_by_lines(struct template *tp
, struct got_blob_object
*blob
,
883 int (*cb
)(struct template *, const char *, size_t))
885 const struct got_error
*err
;
892 err
= got_object_blob_getline(&line
, &linelen
, &linesize
,
894 if (err
|| linelen
== -1)
897 if (cb(tp
, line
, lineno
) == -1) {
898 err
= got_error(GOT_ERR_CANCELLED
);
906 log_warnx("%s: got_object_blob_getline failed: %s",
913 struct blame_cb_args
{
914 struct blame_line
*lines
;
920 struct got_repository
*repo
;
922 got_render_blame_line_cb cb
;
925 static const struct got_error
*
926 got_gotweb_blame_cb(void *arg
, int nlines
, int lineno
,
927 struct got_commit_object
*commit
, struct got_object_id
*id
)
929 const struct got_error
*err
= NULL
;
930 struct blame_cb_args
*a
= arg
;
931 struct blame_line
*bline
;
932 struct request
*c
= a
->c
;
937 time_t committer_time
;
939 if (nlines
!= a
->nlines
||
940 (lineno
!= -1 && lineno
< 1) || lineno
> a
->nlines
)
941 return got_error(GOT_ERR_RANGE
);
944 return NULL
; /* no change in this commit */
946 /* Annotate this line. */
947 bline
= &a
->lines
[lineno
- 1];
948 if (bline
->annotated
)
950 err
= got_object_id_str(&bline
->id_str
, id
);
954 bline
->committer
= strdup(got_object_commit_get_committer(commit
));
955 if (bline
->committer
== NULL
) {
956 err
= got_error_from_errno("strdup");
960 committer_time
= got_object_commit_get_committer_time(commit
);
961 if (gmtime_r(&committer_time
, &tm
) == NULL
)
962 return got_error_from_errno("gmtime_r");
963 if (strftime(bline
->datebuf
, sizeof(bline
->datebuf
), "%G-%m-%d",
965 err
= got_error(GOT_ERR_NO_SPACE
);
968 bline
->annotated
= 1;
970 /* Print lines annotated so far. */
971 bline
= &a
->lines
[a
->lineno_cur
- 1];
972 if (!bline
->annotated
)
975 offset
= a
->line_offsets
[a
->lineno_cur
- 1];
976 if (fseeko(a
->f
, offset
, SEEK_SET
) == -1) {
977 err
= got_error_from_errno("fseeko");
981 while (a
->lineno_cur
<= a
->nlines
&& bline
->annotated
) {
982 if (getline(&line
, &linesize
, a
->f
) == -1) {
984 err
= got_error_from_errno("getline");
988 if (a
->cb(c
->tp
, line
, bline
, a
->nlines_prec
,
989 a
->lineno_cur
) == -1) {
990 err
= got_error(GOT_ERR_CANCELLED
);
995 bline
= &a
->lines
[a
->lineno_cur
- 1];
1002 const struct got_error
*
1003 got_output_file_blame(struct request
*c
, got_render_blame_line_cb cb
)
1005 const struct got_error
*error
= NULL
;
1006 struct transport
*t
= c
->t
;
1007 struct got_repository
*repo
= t
->repo
;
1008 struct querystring
*qs
= c
->t
->qs
;
1009 struct got_object_id
*obj_id
= NULL
, *commit_id
= NULL
;
1010 struct got_commit_object
*commit
= NULL
;
1011 struct got_reflist_head refs
;
1012 struct got_blob_object
*blob
= NULL
;
1013 char *path
= NULL
, *in_repo_path
= NULL
;
1014 struct blame_cb_args bca
;
1015 int i
, obj_type
, fd1
= -1, fd2
= -1, fd3
= -1, fd4
= -1, fd5
= -1;
1018 FILE *f1
= NULL
, *f2
= NULL
;
1025 if (asprintf(&path
, "%s%s%s", qs
->folder
? qs
->folder
: "",
1026 qs
->folder
? "/" : "", qs
->file
) == -1) {
1027 error
= got_error_from_errno("asprintf");
1031 error
= got_repo_map_path(&in_repo_path
, repo
, path
);
1035 error
= got_repo_match_object_id(&commit_id
, NULL
, qs
->commit
,
1036 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
1040 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
1044 error
= got_object_id_by_path(&obj_id
, repo
, commit
, in_repo_path
);
1048 error
= got_object_get_type(&obj_type
, repo
, obj_id
);
1052 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
1053 error
= got_error(GOT_ERR_OBJ_TYPE
);
1057 error
= got_gotweb_openfile(&bca
.f
, &c
->priv_fd
[BLAME_FD_1
], &fd1
);
1061 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_2
], &fd2
);
1065 error
= got_object_open_as_blob(&blob
, repo
, obj_id
, BUF
, fd2
);
1069 error
= got_object_blob_dump_to_file(&filesize
, &bca
.nlines
,
1070 &bca
.line_offsets
, bca
.f
, blob
);
1071 if (error
|| bca
.nlines
== 0)
1074 /* Don't include \n at EOF in the blame line count. */
1075 if (bca
.line_offsets
[bca
.nlines
- 1] == filesize
)
1078 bca
.lines
= calloc(bca
.nlines
, sizeof(*bca
.lines
));
1079 if (bca
.lines
== NULL
) {
1080 error
= got_error_from_errno("calloc");
1084 bca
.nlines_prec
= 0;
1093 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_3
], &fd3
);
1097 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_4
], &fd4
);
1101 error
= got_gotweb_openfile(&f1
, &c
->priv_fd
[BLAME_FD_5
], &fd5
);
1105 error
= got_gotweb_openfile(&f2
, &c
->priv_fd
[BLAME_FD_6
], &fd6
);
1109 error
= got_blame(in_repo_path
, commit_id
, repo
,
1110 GOT_DIFF_ALGORITHM_MYERS
, got_gotweb_blame_cb
, &bca
, NULL
, NULL
,
1115 free(bca
.line_offsets
);
1116 for (i
= 0; i
< bca
.nlines
; i
++) {
1117 struct blame_line
*bline
= &bca
.lines
[i
];
1118 free(bline
->id_str
);
1119 free(bline
->committer
);
1123 if (fd2
!= -1 && close(fd2
) == -1 && error
== NULL
)
1124 error
= got_error_from_errno("close");
1125 if (fd3
!= -1 && close(fd3
) == -1 && error
== NULL
)
1126 error
= got_error_from_errno("close");
1127 if (fd4
!= -1 && close(fd4
) == -1 && error
== NULL
)
1128 error
= got_error_from_errno("close");
1130 const struct got_error
*bca_err
=
1131 got_gotweb_flushfile(bca
.f
, fd1
);
1136 const struct got_error
*f1_err
=
1137 got_gotweb_flushfile(f1
, fd5
);
1142 const struct got_error
*f2_err
=
1143 got_gotweb_flushfile(f2
, fd6
);
1148 got_object_commit_close(commit
);
1150 got_object_blob_close(blob
);
1155 got_ref_list_free(&refs
);
1159 const struct got_error
*
1160 got_open_diff_for_output(FILE **fp
, int *fd
, struct request
*c
)
1162 const struct got_error
*error
= NULL
;
1163 struct transport
*t
= c
->t
;
1164 struct got_repository
*repo
= t
->repo
;
1165 struct repo_commit
*rc
= NULL
;
1166 struct got_object_id
*id1
= NULL
, *id2
= NULL
;
1167 struct got_reflist_head refs
;
1168 FILE *f1
= NULL
, *f2
= NULL
, *f3
= NULL
;
1169 int obj_type
, fd1
, fd2
, fd3
, fd4
= -1, fd5
= -1;
1176 error
= got_gotweb_openfile(&f1
, &c
->priv_fd
[DIFF_FD_1
], &fd1
);
1180 error
= got_gotweb_openfile(&f2
, &c
->priv_fd
[DIFF_FD_2
], &fd2
);
1184 error
= got_gotweb_openfile(&f3
, &c
->priv_fd
[DIFF_FD_3
], &fd3
);
1188 rc
= TAILQ_FIRST(&t
->repo_commits
);
1190 if (rc
->parent_id
!= NULL
&&
1191 strncmp(rc
->parent_id
, "/dev/null", 9) != 0) {
1192 error
= got_repo_match_object_id(&id1
, NULL
,
1193 rc
->parent_id
, GOT_OBJ_TYPE_ANY
,
1199 error
= got_repo_match_object_id(&id2
, NULL
, rc
->commit_id
,
1200 GOT_OBJ_TYPE_ANY
, &refs
, repo
);
1204 error
= got_object_get_type(&obj_type
, repo
, id2
);
1208 error
= got_gotweb_dupfd(&c
->priv_fd
[DIFF_FD_4
], &fd4
);
1212 error
= got_gotweb_dupfd(&c
->priv_fd
[DIFF_FD_5
], &fd5
);
1217 case GOT_OBJ_TYPE_BLOB
:
1218 error
= got_diff_objects_as_blobs(NULL
, NULL
, f1
, f2
, fd4
, fd5
,
1219 id1
, id2
, NULL
, NULL
, GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1222 case GOT_OBJ_TYPE_TREE
:
1223 error
= got_diff_objects_as_trees(NULL
, NULL
, f1
, f2
, fd4
, fd5
,
1224 id1
, id2
, NULL
, "", "", GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1227 case GOT_OBJ_TYPE_COMMIT
:
1228 error
= got_diff_objects_as_commits(NULL
, NULL
, f1
, f2
, fd4
,
1229 fd5
, id1
, id2
, NULL
, GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1233 error
= got_error(GOT_ERR_OBJ_TYPE
);
1238 if (fseek(f1
, 0, SEEK_SET
) == -1) {
1239 error
= got_ferror(f1
, GOT_ERR_IO
);
1243 if (fseek(f2
, 0, SEEK_SET
) == -1) {
1244 error
= got_ferror(f2
, GOT_ERR_IO
);
1248 if (fseek(f3
, 0, SEEK_SET
) == -1) {
1249 error
= got_ferror(f3
, GOT_ERR_IO
);
1257 if (fd4
!= -1 && close(fd4
) == -1 && error
== NULL
)
1258 error
= got_error_from_errno("close");
1259 if (fd5
!= -1 && close(fd5
) == -1 && error
== NULL
)
1260 error
= got_error_from_errno("close");
1262 const struct got_error
*f1_err
=
1263 got_gotweb_flushfile(f1
, fd1
);
1268 const struct got_error
*f2_err
=
1269 got_gotweb_flushfile(f2
, fd2
);
1274 got_gotweb_flushfile(f3
, fd3
);
1278 got_ref_list_free(&refs
);
1284 static const struct got_error
*
1285 got_init_repo_commit(struct repo_commit
**rc
)
1287 *rc
= calloc(1, sizeof(**rc
));
1289 return got_error_from_errno2("%s: calloc", __func__
);
1292 (*rc
)->refs_str
= NULL
;
1293 (*rc
)->commit_id
= NULL
;
1294 (*rc
)->committer
= NULL
;
1295 (*rc
)->author
= NULL
;
1296 (*rc
)->parent_id
= NULL
;
1297 (*rc
)->tree_id
= NULL
;
1298 (*rc
)->commit_msg
= NULL
;
1303 static const struct got_error
*
1304 got_init_repo_tag(struct repo_tag
**rt
)
1306 *rt
= calloc(1, sizeof(**rt
));
1308 return got_error_from_errno2("%s: calloc", __func__
);
1310 (*rt
)->commit_id
= NULL
;
1311 (*rt
)->tag_name
= NULL
;
1312 (*rt
)->tag_commit
= NULL
;
1313 (*rt
)->commit_msg
= NULL
;
1314 (*rt
)->tagger
= NULL
;