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"
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 *);
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_closefile(FILE *f
)
57 const struct got_error
*err
= NULL
;
59 if (fseek(f
, 0, SEEK_SET
) == -1)
60 err
= got_error_from_errno("fseek");
62 if (err
== NULL
&& ftruncate(fileno(f
), 0) == -1)
63 err
= got_error_from_errno("ftruncate");
65 if (fclose(f
) == EOF
&& err
== NULL
)
66 err
= got_error_from_errno("fclose");
71 static const struct got_error
*
72 got_gotweb_openfile(FILE **f
, int *priv_fd
)
78 return got_error_from_errno("dup");
80 *f
= fdopen(fd
, "w+");
83 return got_error(GOT_ERR_PRIVSEP_NO_FD
);
89 static const struct got_error
*
90 got_gotweb_dupfd(int *priv_fd
, int *fd
)
95 return got_error_from_errno("dup");
100 const struct got_error
*
101 got_get_repo_owner(char **owner
, struct request
*c
)
103 struct server
*srv
= c
->srv
;
104 struct transport
*t
= c
->t
;
105 struct got_repository
*repo
= t
->repo
;
106 const char *gitconfig_owner
;
110 if (srv
->show_repo_owner
== 0)
113 gitconfig_owner
= got_repo_get_gitconfig_owner(repo
);
114 if (gitconfig_owner
) {
115 *owner
= strdup(gitconfig_owner
);
117 return got_error_from_errno("strdup");
121 return got_error_from_errno("strdup");
126 const struct got_error
*
127 got_get_repo_age(time_t *repo_age
, struct request
*c
, const char *refname
)
129 const struct got_error
*error
= NULL
;
130 struct transport
*t
= c
->t
;
131 struct got_repository
*repo
= t
->repo
;
132 struct got_commit_object
*commit
= NULL
;
133 struct got_reflist_head refs
;
134 struct got_reflist_entry
*re
;
135 time_t committer_time
= 0, cmp_time
= 0;
141 error
= got_ref_list(&refs
, repo
, "refs/heads",
142 got_ref_cmp_by_name
, NULL
);
147 * Find the youngest branch tip in the repository, or the age of
148 * the a specific branch tip if a name was provided by the caller.
150 TAILQ_FOREACH(re
, &refs
, entry
) {
151 struct got_object_id
*id
= NULL
;
153 if (refname
&& strcmp(got_ref_get_name(re
->ref
), refname
) != 0)
156 error
= got_ref_resolve(&id
, repo
, re
->ref
);
160 error
= got_object_open_as_commit(&commit
, repo
, id
);
166 got_object_commit_get_committer_time(commit
);
167 got_object_commit_close(commit
);
168 if (cmp_time
< committer_time
)
169 cmp_time
= committer_time
;
176 *repo_age
= cmp_time
;
178 got_ref_list_free(&refs
);
182 static const struct got_error
*
183 got_get_repo_commit(struct request
*c
, struct repo_commit
*repo_commit
,
184 struct got_commit_object
*commit
, struct got_reflist_head
*refs
,
185 struct got_object_id
*id
)
187 const struct got_error
*error
= NULL
;
188 struct got_reflist_entry
*re
;
189 struct got_object_id
*id2
= NULL
;
190 struct got_object_qid
*parent_id
;
191 struct transport
*t
= c
->t
;
192 struct querystring
*qs
= c
->t
->qs
;
193 char *commit_msg
= NULL
, *commit_msg0
;
195 TAILQ_FOREACH(re
, refs
, entry
) {
198 struct got_tag_object
*tag
= NULL
;
199 struct got_object_id
*ref_id
;
202 if (got_ref_is_symbolic(re
->ref
))
205 name
= got_ref_get_name(re
->ref
);
206 if (strncmp(name
, "refs/", 5) == 0)
208 if (strncmp(name
, "got/", 4) == 0)
210 if (strncmp(name
, "heads/", 6) == 0)
212 if (strncmp(name
, "remotes/", 8) == 0) {
214 if (strstr(name
, "/" GOT_REF_HEAD
) != NULL
)
217 error
= got_ref_resolve(&ref_id
, t
->repo
, re
->ref
);
220 if (strncmp(name
, "tags/", 5) == 0) {
221 error
= got_object_open_as_tag(&tag
, t
->repo
, ref_id
);
223 if (error
->code
!= GOT_ERR_OBJ_TYPE
) {
228 * Ref points at something other
235 cmp
= got_object_id_cmp(tag
?
236 got_object_tag_get_object_id(tag
) : ref_id
, id
);
239 got_object_tag_close(tag
);
242 s
= repo_commit
->refs_str
;
243 if (asprintf(&repo_commit
->refs_str
, "%s%s%s", s
? s
: "",
244 s
? ", " : "", name
) == -1) {
245 error
= got_error_from_errno("asprintf");
247 repo_commit
->refs_str
= NULL
;
253 error
= got_object_id_str(&repo_commit
->commit_id
, id
);
257 error
= got_object_id_str(&repo_commit
->tree_id
,
258 got_object_commit_get_tree_id(commit
));
262 if (qs
->action
== DIFF
|| qs
->action
== PATCH
) {
263 parent_id
= STAILQ_FIRST(
264 got_object_commit_get_parent_ids(commit
));
265 if (parent_id
!= NULL
) {
266 id2
= got_object_id_dup(&parent_id
->id
);
267 error
= got_object_id_str(&repo_commit
->parent_id
, id2
);
272 repo_commit
->parent_id
= strdup("/dev/null");
273 if (repo_commit
->parent_id
== NULL
) {
274 error
= got_error_from_errno("strdup");
280 repo_commit
->committer_time
=
281 got_object_commit_get_committer_time(commit
);
283 repo_commit
->author
=
284 strdup(got_object_commit_get_author(commit
));
285 if (repo_commit
->author
== NULL
) {
286 error
= got_error_from_errno("strdup");
289 repo_commit
->committer
=
290 strdup(got_object_commit_get_committer(commit
));
291 if (repo_commit
->committer
== NULL
) {
292 error
= got_error_from_errno("strdup");
295 error
= got_object_commit_get_logmsg(&commit_msg0
, commit
);
299 commit_msg
= commit_msg0
;
300 while (*commit_msg
== '\n')
303 repo_commit
->commit_msg
= strdup(commit_msg
);
304 if (repo_commit
->commit_msg
== NULL
)
305 error
= got_error_from_errno("strdup");
310 const struct got_error
*
311 got_get_repo_commits(struct request
*c
, size_t limit
)
313 const struct got_error
*error
= NULL
;
314 struct got_object_id
*id
= NULL
;
315 struct got_commit_graph
*graph
= NULL
;
316 struct got_commit_object
*commit
= NULL
;
317 struct got_reflist_head refs
;
318 struct got_reference
*ref
= NULL
;
319 struct repo_commit
*repo_commit
= NULL
;
320 struct server
*srv
= c
->srv
;
321 struct transport
*t
= c
->t
;
322 struct got_repository
*repo
= t
->repo
;
323 struct querystring
*qs
= t
->qs
;
324 struct repo_dir
*repo_dir
= t
->repo_dir
;
325 char *in_repo_path
= NULL
, *repo_path
= NULL
, *file_path
= NULL
;
329 return got_error(GOT_ERR_RANGE
);
333 * Traverse one commit more than requested to provide
342 if (qs
->file
!= NULL
&& *qs
->file
!= '\0')
343 if (asprintf(&file_path
, "%s/%s", qs
->folder
? qs
->folder
: "",
345 return got_error_from_errno("asprintf");
347 if (asprintf(&repo_path
, "%s/%s", srv
->repos_path
,
348 repo_dir
->name
) == -1) {
349 error
= got_error_from_errno("asprintf");
354 error
= got_repo_match_object_id_prefix(&id
, qs
->commit
,
355 GOT_OBJ_TYPE_COMMIT
, repo
);
359 error
= got_ref_open(&ref
, repo
, qs
->headref
, 0);
363 error
= got_ref_resolve(&id
, repo
, ref
);
368 error
= got_repo_map_path(&in_repo_path
, repo
, repo_path
);
372 error
= got_ref_list(&refs
, repo
, NULL
, got_ref_cmp_by_name
, NULL
);
376 if (qs
->file
!= NULL
&& *qs
->file
!= '\0') {
377 error
= got_commit_graph_open(&graph
, file_path
, 0);
381 error
= got_commit_graph_open(&graph
, in_repo_path
, 0);
386 error
= got_commit_graph_iter_start(graph
, id
, repo
, NULL
, NULL
);
391 struct got_object_id next_id
;
393 error
= got_commit_graph_iter_next(&next_id
, graph
, repo
, NULL
,
396 if (error
->code
== GOT_ERR_ITER_COMPLETED
)
401 error
= got_object_open_as_commit(&commit
, repo
, &next_id
);
405 error
= got_init_repo_commit(&repo_commit
);
409 error
= got_get_repo_commit(c
, repo_commit
, commit
,
412 gotweb_free_repo_commit(repo_commit
);
416 if (--limit
== 0 && chk_next
) {
417 t
->more_id
= strdup(repo_commit
->commit_id
);
418 if (t
->more_id
== NULL
)
419 error
= got_error_from_errno("strdup");
423 TAILQ_INSERT_TAIL(&t
->repo_commits
, repo_commit
, entry
);
429 got_object_commit_close(commit
);
437 got_object_commit_close(commit
);
439 got_commit_graph_close(graph
);
440 got_ref_list_free(&refs
);
448 const struct got_error
*
449 got_get_repo_tags(struct request
*c
, size_t limit
)
451 const struct got_error
*error
= NULL
;
452 struct got_object_id
*id
= NULL
;
453 struct got_commit_object
*commit
= NULL
;
454 struct got_reflist_head refs
;
455 struct got_reference
*ref
;
456 struct got_reflist_entry
*re
;
457 struct server
*srv
= c
->srv
;
458 struct transport
*t
= c
->t
;
459 struct got_repository
*repo
= t
->repo
;
460 struct querystring
*qs
= t
->qs
;
461 struct repo_dir
*repo_dir
= t
->repo_dir
;
462 struct got_tag_object
*tag
= NULL
;
463 struct repo_tag
*rt
= NULL
, *trt
= NULL
;
464 char *in_repo_path
= NULL
, *repo_path
= NULL
, *id_str
= NULL
;
465 char *tag_commit
= NULL
, *tag_commit0
= NULL
;
466 char *commit_msg
= NULL
, *commit_msg0
= NULL
;
467 int chk_next
= 0, chk_multi
= 1, commit_found
= 0, c_cnt
= 0;
472 return got_error(GOT_ERR_RANGE
);
474 if (asprintf(&repo_path
, "%s/%s", srv
->repos_path
,
475 repo_dir
->name
) == -1)
476 return got_error_from_errno("asprintf");
478 if (qs
->commit
== NULL
&& (qs
->action
== TAGS
|| qs
->action
== RSS
)) {
479 error
= got_ref_open(&ref
, repo
, qs
->headref
, 0);
482 error
= got_ref_resolve(&id
, repo
, ref
);
486 } else if (qs
->commit
== NULL
&& qs
->action
== TAG
) {
487 error
= got_error_msg(GOT_ERR_EOF
, "commit id missing");
490 error
= got_repo_match_object_id_prefix(&id
, qs
->commit
,
491 GOT_OBJ_TYPE_COMMIT
, repo
);
496 if (qs
->action
!= SUMMARY
&& qs
->action
!= TAGS
) {
497 error
= got_object_open_as_commit(&commit
, repo
, id
);
500 error
= got_object_commit_get_logmsg(&commit_msg0
, commit
);
504 got_object_commit_close(commit
);
509 error
= got_repo_map_path(&in_repo_path
, repo
, repo_path
);
513 error
= got_ref_list(&refs
, repo
, "refs/tags", got_ref_cmp_tags
,
522 * XXX: again, see previous message about caching
525 TAILQ_FOREACH(re
, &refs
, entry
) {
526 struct repo_tag
*new_repo_tag
= NULL
;
527 error
= got_init_repo_tag(&new_repo_tag
);
531 TAILQ_INSERT_TAIL(&t
->repo_tags
, new_repo_tag
, entry
);
533 new_repo_tag
->tag_name
= strdup(got_ref_get_name(re
->ref
));
534 if (new_repo_tag
->tag_name
== NULL
) {
535 error
= got_error_from_errno("strdup");
545 error
= got_ref_resolve(&id
, repo
, re
->ref
);
550 got_object_tag_close(tag
);
551 error
= got_object_open_as_tag(&tag
, repo
, id
);
553 if (error
->code
!= GOT_ERR_OBJ_TYPE
)
555 /* "lightweight" tag */
556 error
= got_object_open_as_commit(&commit
, repo
, id
);
559 new_repo_tag
->tagger
=
560 strdup(got_object_commit_get_committer(commit
));
561 if (new_repo_tag
->tagger
== NULL
) {
562 error
= got_error_from_errno("strdup");
565 new_repo_tag
->tagger_time
=
566 got_object_commit_get_committer_time(commit
);
567 error
= got_object_id_str(&id_str
, id
);
571 new_repo_tag
->tagger
=
572 strdup(got_object_tag_get_tagger(tag
));
573 if (new_repo_tag
->tagger
== NULL
) {
574 error
= got_error_from_errno("strdup");
577 new_repo_tag
->tagger_time
=
578 got_object_tag_get_tagger_time(tag
);
579 error
= got_object_id_str(&id_str
,
580 got_object_tag_get_object_id(tag
));
585 new_repo_tag
->commit_id
= strdup(id_str
);
586 if (new_repo_tag
->commit_id
== NULL
)
589 if (commit_found
== 0 && qs
->commit
!= NULL
&&
590 strncmp(id_str
, qs
->commit
, strlen(id_str
)) != 0)
598 * check for one more commit before breaking,
599 * so we know whether to navigate through briefs
600 * commits and summary
603 t
->next_id
= strdup(new_repo_tag
->commit_id
);
604 if (t
->next_id
== NULL
) {
605 error
= got_error_from_errno("strdup");
609 got_object_commit_close(commit
);
612 TAILQ_REMOVE(&t
->repo_tags
, new_repo_tag
, entry
);
613 gotweb_free_repo_tag(new_repo_tag
);
618 error
= got_object_commit_get_logmsg(&tag_commit0
,
622 got_object_commit_close(commit
);
625 tag_commit0
= strdup(got_object_tag_get_message(tag
));
626 if (tag_commit0
== NULL
) {
627 error
= got_error_from_errno("strdup");
632 tag_commit
= tag_commit0
;
633 while (*tag_commit
== '\n')
635 new_repo_tag
->tag_commit
= strdup(tag_commit
);
636 if (new_repo_tag
->tag_commit
== NULL
) {
637 error
= got_error_from_errno("strdup");
643 if (qs
->action
!= SUMMARY
&& qs
->action
!= TAGS
) {
644 commit_msg
= commit_msg0
;
645 while (*commit_msg
== '\n')
648 new_repo_tag
->commit_msg
= strdup(commit_msg
);
649 if (new_repo_tag
->commit_msg
== NULL
) {
650 error
= got_error_from_errno("strdup");
655 if (limit
&& --limit
== 0) {
664 * we have tailq populated, so find previous commit id
665 * for navigation through briefs and commits
667 if (t
->tag_count
== 0) {
668 TAILQ_FOREACH_SAFE(rt
, &t
->repo_tags
, entry
, trt
) {
669 TAILQ_REMOVE(&t
->repo_tags
, rt
, entry
);
670 gotweb_free_repo_tag(rt
);
673 if (t
->tag_count
> 0 && t
->prev_id
== NULL
&& qs
->commit
!= NULL
) {
675 TAILQ_FOREACH_REVERSE(rt
, &t
->repo_tags
, repo_tags_head
,
677 if (commit_found
== 0 && rt
->commit_id
!= NULL
&&
678 strcmp(qs
->commit
, rt
->commit_id
) != 0) {
682 if (c_cnt
== srv
->max_commits_display
||
683 rt
== TAILQ_FIRST(&t
->repo_tags
)) {
684 t
->prev_id
= strdup(rt
->commit_id
);
685 if (t
->prev_id
== NULL
)
686 error
= got_error_from_errno("strdup");
694 got_object_commit_close(commit
);
696 got_object_tag_close(tag
);
697 got_ref_list_free(&refs
);
707 got_output_repo_tree(struct request
*c
, char **readme
,
708 int (*cb
)(struct template *, struct got_tree_entry
*))
710 const struct got_error
*error
= NULL
;
711 struct transport
*t
= c
->t
;
712 struct got_commit_object
*commit
= NULL
;
713 struct got_repository
*repo
= t
->repo
;
714 struct querystring
*qs
= t
->qs
;
715 struct repo_commit
*rc
= NULL
;
716 struct got_object_id
*tree_id
= NULL
, *commit_id
= NULL
;
717 struct got_reflist_head refs
;
718 struct got_tree_object
*tree
= NULL
;
719 struct got_tree_entry
*te
;
720 struct repo_dir
*repo_dir
= t
->repo_dir
;
723 char *escaped_name
= NULL
, *path
= NULL
;
729 rc
= TAILQ_FIRST(&t
->repo_commits
);
731 if (qs
->folder
!= NULL
) {
732 path
= strdup(qs
->folder
);
734 error
= got_error_from_errno("strdup");
738 error
= got_repo_map_path(&path
, repo
, repo_dir
->path
);
743 error
= got_repo_match_object_id(&commit_id
, NULL
, rc
->commit_id
,
744 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
748 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
752 error
= got_object_id_by_path(&tree_id
, repo
, commit
, path
);
756 error
= got_object_open_as_tree(&tree
, repo
, tree_id
);
760 nentries
= got_object_tree_get_nentries(tree
);
762 for (i
= 0; i
< nentries
; i
++) {
763 te
= got_object_tree_get_entry(tree
, i
);
765 name
= got_tree_entry_get_name(te
);
766 mode
= got_tree_entry_get_mode(te
);
767 if (!S_ISDIR(mode
) && (!strcasecmp(name
, "README") ||
768 !strcasecmp(name
, "README.md") ||
769 !strcasecmp(name
, "README.txt"))) {
771 *readme
= strdup(name
);
774 if (cb(c
->tp
, te
) == -1) {
775 error
= got_error(GOT_ERR_CANCELLED
);
782 got_ref_list_free(&refs
);
784 got_object_commit_close(commit
);
786 got_object_tree_close(tree
);
792 if (error
->code
!= GOT_ERR_CANCELLED
)
793 log_warnx("%s: %s", __func__
, error
->msg
);
799 const struct got_error
*
800 got_open_blob_for_output(struct got_blob_object
**blob
, int *fd
,
801 int *binary
, struct request
*c
, const char *directory
, const char *file
,
802 const char *commitstr
)
804 const struct got_error
*error
= NULL
;
805 struct got_repository
*repo
= c
->t
->repo
;
806 struct got_commit_object
*commit
= NULL
;
807 struct got_object_id
*commit_id
= NULL
;
808 struct got_reflist_head refs
;
809 char *path
= NULL
, *in_repo_path
= NULL
;
818 error
= got_ref_list(&refs
, repo
, "refs/heads",
819 got_ref_cmp_by_name
, NULL
);
823 if (asprintf(&path
, "%s%s%s", directory
? directory
: "",
824 directory
? "/" : "", file
) == -1) {
825 error
= got_error_from_errno("asprintf");
829 error
= got_repo_map_path(&in_repo_path
, repo
, path
);
833 if (commitstr
== NULL
)
834 commitstr
= GOT_REF_HEAD
;
836 error
= got_repo_match_object_id(&commit_id
, NULL
, commitstr
,
837 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
841 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
845 error
= got_object_id_by_path(&commit_id
, repo
, commit
, in_repo_path
);
849 if (commit_id
== NULL
) {
850 error
= got_error(GOT_ERR_NO_OBJ
);
854 error
= got_object_get_type(&obj_type
, repo
, commit_id
);
858 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
859 error
= got_error(GOT_ERR_OBJ_TYPE
);
863 error
= got_gotweb_dupfd(&c
->priv_fd
[BLOB_FD_1
], fd
);
867 error
= got_object_open_as_blob(blob
, repo
, commit_id
, BUF
, *fd
);
871 error
= got_object_blob_is_binary(binary
, *blob
);
877 got_object_commit_close(commit
);
883 got_object_blob_close(*blob
);
888 got_ref_list_free(&refs
);
896 got_output_blob_by_lines(struct template *tp
, struct got_blob_object
*blob
,
897 int (*cb
)(struct template *, const char *, size_t))
899 const struct got_error
*err
;
906 err
= got_object_blob_getline(&line
, &linelen
, &linesize
,
908 if (err
|| linelen
== -1)
911 if (cb(tp
, line
, lineno
) == -1) {
912 err
= got_error(GOT_ERR_CANCELLED
);
920 if (err
->code
!= GOT_ERR_CANCELLED
)
921 log_warnx("%s: got_object_blob_getline failed: %s",
928 struct blame_cb_args
{
929 struct blame_line
*lines
;
935 struct got_repository
*repo
;
937 got_render_blame_line_cb cb
;
940 static const struct got_error
*
941 got_gotweb_blame_cb(void *arg
, int nlines
, int lineno
,
942 struct got_commit_object
*commit
, struct got_object_id
*id
)
944 const struct got_error
*err
= NULL
;
945 struct blame_cb_args
*a
= arg
;
946 struct blame_line
*bline
;
947 struct request
*c
= a
->c
;
952 time_t committer_time
;
954 if (nlines
!= a
->nlines
||
955 (lineno
!= -1 && lineno
< 1) || lineno
> a
->nlines
)
956 return got_error(GOT_ERR_RANGE
);
959 return NULL
; /* no change in this commit */
961 /* Annotate this line. */
962 bline
= &a
->lines
[lineno
- 1];
963 if (bline
->annotated
)
965 err
= got_object_id_str(&bline
->id_str
, id
);
969 bline
->committer
= strdup(got_object_commit_get_committer(commit
));
970 if (bline
->committer
== NULL
) {
971 err
= got_error_from_errno("strdup");
975 committer_time
= got_object_commit_get_committer_time(commit
);
976 if (gmtime_r(&committer_time
, &tm
) == NULL
)
977 return got_error_from_errno("gmtime_r");
978 if (strftime(bline
->datebuf
, sizeof(bline
->datebuf
), "%G-%m-%d",
980 err
= got_error(GOT_ERR_NO_SPACE
);
983 bline
->annotated
= 1;
985 /* Print lines annotated so far. */
986 bline
= &a
->lines
[a
->lineno_cur
- 1];
987 if (!bline
->annotated
)
990 offset
= a
->line_offsets
[a
->lineno_cur
- 1];
991 if (fseeko(a
->f
, offset
, SEEK_SET
) == -1) {
992 err
= got_error_from_errno("fseeko");
996 while (a
->lineno_cur
<= a
->nlines
&& bline
->annotated
) {
997 if (getline(&line
, &linesize
, a
->f
) == -1) {
999 err
= got_error_from_errno("getline");
1003 if (a
->cb(c
->tp
, line
, bline
, a
->nlines_prec
,
1004 a
->lineno_cur
) == -1) {
1005 err
= got_error(GOT_ERR_CANCELLED
);
1010 bline
= &a
->lines
[a
->lineno_cur
- 1];
1017 const struct got_error
*
1018 got_output_file_blame(struct request
*c
, got_render_blame_line_cb cb
)
1020 const struct got_error
*error
= NULL
;
1021 struct transport
*t
= c
->t
;
1022 struct got_repository
*repo
= t
->repo
;
1023 struct querystring
*qs
= c
->t
->qs
;
1024 struct got_object_id
*obj_id
= NULL
, *commit_id
= NULL
;
1025 struct got_commit_object
*commit
= NULL
;
1026 struct got_reflist_head refs
;
1027 struct got_blob_object
*blob
= NULL
;
1028 char *path
= NULL
, *in_repo_path
= NULL
;
1029 struct blame_cb_args bca
;
1030 int i
, obj_type
, blobfd
= -1, fd1
= -1, fd2
= -1;
1032 FILE *f1
= NULL
, *f2
= NULL
;
1039 if (asprintf(&path
, "%s%s%s", qs
->folder
? qs
->folder
: "",
1040 qs
->folder
? "/" : "", qs
->file
) == -1) {
1041 error
= got_error_from_errno("asprintf");
1045 error
= got_repo_map_path(&in_repo_path
, repo
, path
);
1049 error
= got_repo_match_object_id(&commit_id
, NULL
, qs
->commit
,
1050 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
1054 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
1058 error
= got_object_id_by_path(&obj_id
, repo
, commit
, in_repo_path
);
1062 error
= got_object_get_type(&obj_type
, repo
, obj_id
);
1066 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
1067 error
= got_error(GOT_ERR_OBJ_TYPE
);
1071 error
= got_gotweb_openfile(&bca
.f
, &c
->priv_fd
[BLAME_FD_1
]);
1075 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_2
], &blobfd
);
1079 error
= got_object_open_as_blob(&blob
, repo
, obj_id
, BUF
, blobfd
);
1083 error
= got_object_blob_dump_to_file(&filesize
, &bca
.nlines
,
1084 &bca
.line_offsets
, bca
.f
, blob
);
1085 if (error
|| bca
.nlines
== 0)
1088 /* Don't include \n at EOF in the blame line count. */
1089 if (bca
.line_offsets
[bca
.nlines
- 1] == filesize
)
1092 bca
.lines
= calloc(bca
.nlines
, sizeof(*bca
.lines
));
1093 if (bca
.lines
== NULL
) {
1094 error
= got_error_from_errno("calloc");
1098 bca
.nlines_prec
= 0;
1107 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_3
], &fd1
);
1111 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_4
], &fd2
);
1115 error
= got_gotweb_openfile(&f1
, &c
->priv_fd
[BLAME_FD_5
]);
1119 error
= got_gotweb_openfile(&f2
, &c
->priv_fd
[BLAME_FD_6
]);
1123 error
= got_blame(in_repo_path
, commit_id
, repo
,
1124 GOT_DIFF_ALGORITHM_MYERS
, got_gotweb_blame_cb
, &bca
, NULL
, NULL
,
1129 free(bca
.line_offsets
);
1130 for (i
= 0; i
< bca
.nlines
; i
++) {
1131 struct blame_line
*bline
= &bca
.lines
[i
];
1132 free(bline
->id_str
);
1133 free(bline
->committer
);
1137 if (blobfd
!= -1 && close(blobfd
) == -1 && error
== NULL
)
1138 error
= got_error_from_errno("close");
1139 if (fd1
!= -1 && close(fd1
) == -1 && error
== NULL
)
1140 error
= got_error_from_errno("close");
1141 if (fd2
!= -1 && close(fd2
) == -1 && error
== NULL
)
1142 error
= got_error_from_errno("close");
1144 const struct got_error
*bca_err
= got_gotweb_closefile(bca
.f
);
1149 const struct got_error
*f1_err
= got_gotweb_closefile(f1
);
1154 const struct got_error
*f2_err
= got_gotweb_closefile(f2
);
1159 got_object_commit_close(commit
);
1161 got_object_blob_close(blob
);
1166 got_ref_list_free(&refs
);
1170 const struct got_error
*
1171 got_open_diff_for_output(FILE **fp
, struct request
*c
)
1173 const struct got_error
*error
= NULL
;
1174 struct transport
*t
= c
->t
;
1175 struct got_repository
*repo
= t
->repo
;
1176 struct repo_commit
*rc
= NULL
;
1177 struct got_object_id
*id1
= NULL
, *id2
= NULL
;
1178 struct got_reflist_head refs
;
1179 FILE *f1
= NULL
, *f2
= NULL
, *f3
= NULL
;
1180 int obj_type
, fd1
= -1, fd2
= -1;
1186 error
= got_gotweb_openfile(&f1
, &c
->priv_fd
[DIFF_FD_1
]);
1190 error
= got_gotweb_openfile(&f2
, &c
->priv_fd
[DIFF_FD_2
]);
1194 error
= got_gotweb_openfile(&f3
, &c
->priv_fd
[DIFF_FD_3
]);
1198 rc
= TAILQ_FIRST(&t
->repo_commits
);
1200 if (rc
->parent_id
!= NULL
&&
1201 strncmp(rc
->parent_id
, "/dev/null", 9) != 0) {
1202 error
= got_repo_match_object_id(&id1
, NULL
,
1203 rc
->parent_id
, GOT_OBJ_TYPE_ANY
,
1209 error
= got_repo_match_object_id(&id2
, NULL
, rc
->commit_id
,
1210 GOT_OBJ_TYPE_ANY
, &refs
, repo
);
1214 error
= got_object_get_type(&obj_type
, repo
, id2
);
1218 error
= got_gotweb_dupfd(&c
->priv_fd
[DIFF_FD_4
], &fd1
);
1222 error
= got_gotweb_dupfd(&c
->priv_fd
[DIFF_FD_5
], &fd2
);
1227 case GOT_OBJ_TYPE_BLOB
:
1228 error
= got_diff_objects_as_blobs(NULL
, NULL
, f1
, f2
, fd1
, fd2
,
1229 id1
, id2
, NULL
, NULL
, GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1232 case GOT_OBJ_TYPE_TREE
:
1233 error
= got_diff_objects_as_trees(NULL
, NULL
, f1
, f2
, fd1
, fd2
,
1234 id1
, id2
, NULL
, "", "", GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1237 case GOT_OBJ_TYPE_COMMIT
:
1238 error
= got_diff_objects_as_commits(NULL
, NULL
, f1
, f2
, fd1
,
1239 fd2
, id1
, id2
, NULL
, GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1243 error
= got_error(GOT_ERR_OBJ_TYPE
);
1248 if (fseek(f3
, 0, SEEK_SET
) == -1) {
1249 error
= got_ferror(f3
, GOT_ERR_IO
);
1256 if (fd1
!= -1 && close(fd1
) == -1 && error
== NULL
)
1257 error
= got_error_from_errno("close");
1258 if (fd2
!= -1 && close(fd2
) == -1 && error
== NULL
)
1259 error
= got_error_from_errno("close");
1261 const struct got_error
*f1_err
= got_gotweb_closefile(f1
);
1266 const struct got_error
*f2_err
= got_gotweb_closefile(f2
);
1271 got_gotweb_closefile(f3
);
1274 got_ref_list_free(&refs
);
1280 static const struct got_error
*
1281 got_init_repo_commit(struct repo_commit
**rc
)
1283 *rc
= calloc(1, sizeof(**rc
));
1285 return got_error_from_errno2(__func__
, "calloc");
1288 (*rc
)->refs_str
= NULL
;
1289 (*rc
)->commit_id
= NULL
;
1290 (*rc
)->committer
= NULL
;
1291 (*rc
)->author
= NULL
;
1292 (*rc
)->parent_id
= NULL
;
1293 (*rc
)->tree_id
= NULL
;
1294 (*rc
)->commit_msg
= NULL
;
1299 static const struct got_error
*
1300 got_init_repo_tag(struct repo_tag
**rt
)
1302 *rt
= calloc(1, sizeof(**rt
));
1304 return got_error_from_errno2(__func__
, "calloc");
1306 (*rt
)->commit_id
= NULL
;
1307 (*rt
)->tag_name
= NULL
;
1308 (*rt
)->tag_commit
= NULL
;
1309 (*rt
)->commit_msg
= NULL
;
1310 (*rt
)->tagger
= NULL
;