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>
22 #include <sys/socket.h>
32 #include "got_error.h"
33 #include "got_object.h"
34 #include "got_reference.h"
35 #include "got_repository.h"
37 #include "got_cancel.h"
39 #include "got_commit_graph.h"
40 #include "got_blame.h"
41 #include "got_privsep.h"
46 static const struct got_error
*got_init_repo_commit(struct repo_commit
**);
47 static const struct got_error
*got_init_repo_tag(struct repo_tag
**);
48 static const struct got_error
*got_get_repo_commit(struct request
*,
49 struct repo_commit
*, struct got_commit_object
*, struct got_reflist_head
*,
50 struct got_object_id
*);
51 static const struct got_error
*got_gotweb_dupfd(int *, int *);
52 static const struct got_error
*got_gotweb_openfile(FILE **, int *);
53 static const struct got_error
*got_gotweb_blame_cb(void *, int, int,
54 struct got_commit_object
*,struct got_object_id
*);
56 const struct got_error
*
57 got_gotweb_closefile(FILE *f
)
59 const struct got_error
*err
= NULL
;
61 if (fseek(f
, 0, SEEK_SET
) == -1)
62 err
= got_error_from_errno("fseek");
64 if (ftruncate(fileno(f
), 0) == -1 && err
== NULL
)
65 err
= got_error_from_errno("ftruncate");
67 if (fclose(f
) == EOF
&& err
== NULL
)
68 err
= got_error_from_errno("fclose");
73 static const struct got_error
*
74 got_gotweb_openfile(FILE **f
, int *priv_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
)
97 return got_error_from_errno("dup");
102 const struct got_error
*
103 got_get_repo_owner(char **owner
, struct request
*c
)
105 struct transport
*t
= c
->t
;
106 struct got_repository
*repo
= t
->repo
;
107 const char *gitconfig_owner
;
110 gitconfig_owner
= got_repo_get_gitconfig_owner(repo
);
111 if (gitconfig_owner
) {
112 *owner
= strdup(gitconfig_owner
);
114 return got_error_from_errno("strdup");
120 static const struct got_error
*
121 get_ref_commit_timestamp(time_t *timestamp
,
122 struct got_reference
*ref
, struct got_repository
*repo
)
124 const struct got_error
*error
;
125 struct got_object_id
*id
= NULL
;
127 struct got_commit_object
*commit
= NULL
;
129 /* We might have a cached timestamp available. */
130 *timestamp
= got_ref_get_cached_committer_time(ref
);
134 error
= got_ref_resolve(&id
, repo
, ref
);
138 error
= got_object_get_type(&obj_type
, repo
, id
);
139 if (error
|| obj_type
!= GOT_OBJ_TYPE_COMMIT
)
142 error
= got_object_open_as_commit(&commit
, repo
, id
);
146 *timestamp
= got_object_commit_get_committer_time(commit
);
150 got_object_commit_close(commit
);
156 * Find the youngest branch tip in the repository, or the age of
157 * a specific branch tip if a name was provided by the caller.
159 const struct got_error
*
160 got_get_repo_age(time_t *repo_age
, struct request
*c
, const char *refname
)
162 const struct got_error
*error
= NULL
;
163 struct transport
*t
= c
->t
;
164 struct got_repository
*repo
= t
->repo
;
169 struct got_reference
*ref
;
171 error
= got_ref_open(&ref
, repo
, refname
, 0);
175 error
= get_ref_commit_timestamp(repo_age
, ref
, repo
);
178 struct got_reflist_head refs
;
179 struct got_reflist_entry
*re
;
183 error
= got_ref_list(&refs
, repo
, "refs/heads",
184 got_ref_cmp_by_commit_timestamp_descending
, repo
);
188 re
= TAILQ_FIRST(&refs
);
190 error
= get_ref_commit_timestamp(repo_age
,
193 got_ref_list_free(&refs
);
199 static const struct got_error
*
200 got_get_repo_commit(struct request
*c
, struct repo_commit
*repo_commit
,
201 struct got_commit_object
*commit
, struct got_reflist_head
*refs
,
202 struct got_object_id
*id
)
204 const struct got_error
*error
= NULL
;
205 struct got_reflist_entry
*re
;
206 struct got_object_id
*id2
= NULL
;
207 struct got_object_qid
*parent_id
;
208 struct transport
*t
= c
->t
;
209 struct querystring
*qs
= c
->t
->qs
;
210 char *commit_msg
= NULL
, *commit_msg0
;
212 TAILQ_FOREACH(re
, refs
, entry
) {
215 struct got_tag_object
*tag
= NULL
;
216 struct got_object_id
*ref_id
;
219 if (got_ref_is_symbolic(re
->ref
))
222 name
= got_ref_get_name(re
->ref
);
223 if (strncmp(name
, "refs/", 5) == 0)
225 if (strncmp(name
, "got/", 4) == 0)
227 if (strncmp(name
, "heads/", 6) == 0)
229 if (strncmp(name
, "remotes/", 8) == 0) {
231 if (strstr(name
, "/" GOT_REF_HEAD
) != NULL
)
234 error
= got_ref_resolve(&ref_id
, t
->repo
, re
->ref
);
237 if (strncmp(name
, "tags/", 5) == 0) {
238 error
= got_object_open_as_tag(&tag
, t
->repo
, ref_id
);
240 if (error
->code
!= GOT_ERR_OBJ_TYPE
) {
245 * Ref points at something other
252 cmp
= got_object_id_cmp(tag
?
253 got_object_tag_get_object_id(tag
) : ref_id
, id
);
256 got_object_tag_close(tag
);
259 s
= repo_commit
->refs_str
;
260 if (asprintf(&repo_commit
->refs_str
, "%s%s%s", s
? s
: "",
261 s
? ", " : "", name
) == -1) {
262 error
= got_error_from_errno("asprintf");
264 repo_commit
->refs_str
= NULL
;
270 error
= got_object_id_str(&repo_commit
->commit_id
, id
);
274 error
= got_object_id_str(&repo_commit
->tree_id
,
275 got_object_commit_get_tree_id(commit
));
279 if (qs
->action
== DIFF
|| qs
->action
== PATCH
) {
280 parent_id
= STAILQ_FIRST(
281 got_object_commit_get_parent_ids(commit
));
282 if (parent_id
!= NULL
) {
283 id2
= got_object_id_dup(&parent_id
->id
);
284 error
= got_object_id_str(&repo_commit
->parent_id
, id2
);
289 repo_commit
->parent_id
= strdup("/dev/null");
290 if (repo_commit
->parent_id
== NULL
) {
291 error
= got_error_from_errno("strdup");
297 repo_commit
->committer_time
=
298 got_object_commit_get_committer_time(commit
);
300 repo_commit
->author
=
301 strdup(got_object_commit_get_author(commit
));
302 if (repo_commit
->author
== NULL
) {
303 error
= got_error_from_errno("strdup");
306 repo_commit
->committer
=
307 strdup(got_object_commit_get_committer(commit
));
308 if (repo_commit
->committer
== NULL
) {
309 error
= got_error_from_errno("strdup");
312 error
= got_object_commit_get_logmsg(&commit_msg0
, commit
);
316 commit_msg
= commit_msg0
;
317 while (*commit_msg
== '\n')
320 repo_commit
->commit_msg
= strdup(commit_msg
);
321 if (repo_commit
->commit_msg
== NULL
)
322 error
= got_error_from_errno("strdup");
327 const struct got_error
*
328 got_get_repo_commits(struct request
*c
, size_t limit
)
330 const struct got_error
*error
= NULL
;
331 struct got_object_id
*id
= NULL
;
332 struct got_commit_graph
*graph
= NULL
;
333 struct got_commit_object
*commit
= NULL
;
334 struct got_reflist_head refs
;
335 struct got_reference
*ref
= NULL
;
336 struct repo_commit
*repo_commit
= NULL
;
337 struct server
*srv
= c
->srv
;
338 struct transport
*t
= c
->t
;
339 struct got_repository
*repo
= t
->repo
;
340 struct querystring
*qs
= t
->qs
;
341 struct repo_dir
*repo_dir
= t
->repo_dir
;
342 char *in_repo_path
= NULL
, *repo_path
= NULL
, *file_path
= NULL
;
346 return got_error(GOT_ERR_RANGE
);
350 * Traverse one commit more than requested to provide
359 if (qs
->file
!= NULL
&& *qs
->file
!= '\0')
360 if (asprintf(&file_path
, "%s/%s", qs
->folder
? qs
->folder
: "",
362 return got_error_from_errno("asprintf");
364 if (asprintf(&repo_path
, "%s/%s", srv
->repos_path
,
365 repo_dir
->name
) == -1) {
366 error
= got_error_from_errno("asprintf");
371 error
= got_repo_match_object_id_prefix(&id
, qs
->commit
,
372 GOT_OBJ_TYPE_COMMIT
, repo
);
376 error
= got_ref_open(&ref
, repo
, qs
->headref
, 0);
380 error
= got_ref_resolve(&id
, repo
, ref
);
385 error
= got_repo_map_path(&in_repo_path
, repo
, repo_path
);
389 error
= got_ref_list(&refs
, repo
, NULL
, got_ref_cmp_by_name
, NULL
);
393 if (qs
->file
!= NULL
&& *qs
->file
!= '\0') {
394 error
= got_commit_graph_open(&graph
, file_path
, 0);
398 error
= got_commit_graph_open(&graph
, in_repo_path
, 0);
403 error
= got_commit_graph_bfsort(graph
, id
, repo
, NULL
, NULL
);
408 struct got_object_id next_id
;
410 error
= got_commit_graph_iter_next(&next_id
, graph
, repo
, NULL
,
413 if (error
->code
== GOT_ERR_ITER_COMPLETED
)
418 error
= got_object_open_as_commit(&commit
, repo
, &next_id
);
422 error
= got_init_repo_commit(&repo_commit
);
426 error
= got_get_repo_commit(c
, repo_commit
, commit
,
429 gotweb_free_repo_commit(repo_commit
);
433 if (--limit
== 0 && chk_next
) {
434 t
->more_id
= strdup(repo_commit
->commit_id
);
435 if (t
->more_id
== NULL
)
436 error
= got_error_from_errno("strdup");
437 gotweb_free_repo_commit(repo_commit
);
441 TAILQ_INSERT_TAIL(&t
->repo_commits
, repo_commit
, entry
);
447 got_object_commit_close(commit
);
455 got_object_commit_close(commit
);
457 got_commit_graph_close(graph
);
458 got_ref_list_free(&refs
);
466 const struct got_error
*
467 got_get_repo_tags(struct request
*c
, size_t limit
)
469 const struct got_error
*error
= NULL
;
470 struct got_object_id
*id
= NULL
;
471 struct got_commit_object
*commit
= NULL
;
472 struct got_reflist_head refs
;
473 struct got_reference
*ref
;
474 struct got_reflist_entry
*re
;
475 struct server
*srv
= c
->srv
;
476 struct transport
*t
= c
->t
;
477 struct got_repository
*repo
= t
->repo
;
478 struct querystring
*qs
= t
->qs
;
479 struct repo_dir
*repo_dir
= t
->repo_dir
;
480 struct got_tag_object
*tag
= NULL
;
481 char *in_repo_path
= NULL
, *repo_path
= NULL
, *id_str
= NULL
;
482 char *tag_commit
= NULL
, *tag_commit0
= NULL
;
483 char *commit_msg
= NULL
, *commit_msg0
= NULL
;
484 int chk_next
= 0, chk_multi
= 1, commit_found
= 0;
489 return got_error(GOT_ERR_RANGE
);
491 if (asprintf(&repo_path
, "%s/%s", srv
->repos_path
,
492 repo_dir
->name
) == -1)
493 return got_error_from_errno("asprintf");
495 if (qs
->commit
== NULL
&& (qs
->action
== TAGS
|| qs
->action
== RSS
)) {
496 error
= got_ref_open(&ref
, repo
, qs
->headref
, 0);
499 error
= got_ref_resolve(&id
, repo
, ref
);
503 } else if (qs
->commit
== NULL
&& qs
->action
== TAG
) {
504 error
= got_error_msg(GOT_ERR_EOF
, "commit id missing");
507 error
= got_repo_match_object_id_prefix(&id
, qs
->commit
,
508 GOT_OBJ_TYPE_COMMIT
, repo
);
513 if (qs
->action
!= SUMMARY
&& qs
->action
!= TAGS
) {
514 error
= got_object_open_as_commit(&commit
, repo
, id
);
517 error
= got_object_commit_get_logmsg(&commit_msg0
, commit
);
521 got_object_commit_close(commit
);
526 error
= got_repo_map_path(&in_repo_path
, repo
, repo_path
);
530 error
= got_ref_list(&refs
, repo
, "refs/tags", got_ref_cmp_tags
,
538 TAILQ_FOREACH(re
, &refs
, entry
) {
539 struct repo_tag
*new_repo_tag
= NULL
;
540 error
= got_init_repo_tag(&new_repo_tag
);
544 TAILQ_INSERT_TAIL(&t
->repo_tags
, new_repo_tag
, entry
);
546 new_repo_tag
->tag_name
= strdup(got_ref_get_name(re
->ref
));
547 if (new_repo_tag
->tag_name
== NULL
) {
548 error
= got_error_from_errno("strdup");
558 error
= got_ref_resolve(&id
, repo
, re
->ref
);
563 got_object_tag_close(tag
);
564 error
= got_object_open_as_tag(&tag
, repo
, id
);
566 if (error
->code
!= GOT_ERR_OBJ_TYPE
)
568 /* "lightweight" tag */
569 error
= got_object_open_as_commit(&commit
, repo
, id
);
572 new_repo_tag
->tagger
=
573 strdup(got_object_commit_get_committer(commit
));
574 if (new_repo_tag
->tagger
== NULL
) {
575 error
= got_error_from_errno("strdup");
578 new_repo_tag
->tagger_time
=
579 got_object_commit_get_committer_time(commit
);
580 error
= got_object_id_str(&id_str
, id
);
584 new_repo_tag
->tagger
=
585 strdup(got_object_tag_get_tagger(tag
));
586 if (new_repo_tag
->tagger
== NULL
) {
587 error
= got_error_from_errno("strdup");
590 new_repo_tag
->tagger_time
=
591 got_object_tag_get_tagger_time(tag
);
592 error
= got_object_id_str(&id_str
,
593 got_object_tag_get_object_id(tag
));
598 new_repo_tag
->commit_id
= strdup(id_str
);
599 if (new_repo_tag
->commit_id
== NULL
)
602 if (commit_found
== 0 && qs
->commit
!= NULL
&&
603 strncmp(id_str
, qs
->commit
, strlen(id_str
)) != 0) {
605 got_object_commit_close(commit
);
608 TAILQ_REMOVE(&t
->repo_tags
, new_repo_tag
, entry
);
609 gotweb_free_repo_tag(new_repo_tag
);
615 * check for one more commit before breaking,
616 * so we know whether to navigate through briefs
617 * commits and summary
620 t
->tags_more_id
= strdup(new_repo_tag
->commit_id
);
621 if (t
->tags_more_id
== NULL
) {
622 error
= got_error_from_errno("strdup");
626 got_object_commit_close(commit
);
629 TAILQ_REMOVE(&t
->repo_tags
, new_repo_tag
, entry
);
630 gotweb_free_repo_tag(new_repo_tag
);
635 error
= got_object_commit_get_logmsg(&tag_commit0
,
639 got_object_commit_close(commit
);
642 tag_commit0
= strdup(got_object_tag_get_message(tag
));
643 if (tag_commit0
== NULL
) {
644 error
= got_error_from_errno("strdup");
649 tag_commit
= tag_commit0
;
650 while (*tag_commit
== '\n')
652 new_repo_tag
->tag_commit
= strdup(tag_commit
);
653 if (new_repo_tag
->tag_commit
== NULL
) {
654 error
= got_error_from_errno("strdup");
660 if (qs
->action
!= SUMMARY
&& qs
->action
!= TAGS
) {
661 commit_msg
= commit_msg0
;
662 while (*commit_msg
== '\n')
665 new_repo_tag
->commit_msg
= strdup(commit_msg
);
666 if (new_repo_tag
->commit_msg
== NULL
) {
667 error
= got_error_from_errno("strdup");
672 if (limit
&& --limit
== 0) {
681 got_object_commit_close(commit
);
683 got_object_tag_close(tag
);
684 got_ref_list_free(&refs
);
694 got_output_repo_tree(struct request
*c
, char **readme
,
695 int (*cb
)(struct template *, struct got_tree_entry
*))
697 const struct got_error
*error
= NULL
;
698 struct transport
*t
= c
->t
;
699 struct got_commit_object
*commit
= NULL
;
700 struct got_repository
*repo
= t
->repo
;
701 struct querystring
*qs
= t
->qs
;
702 struct repo_commit
*rc
= NULL
;
703 struct got_object_id
*tree_id
= NULL
, *commit_id
= NULL
;
704 struct got_reflist_head refs
;
705 struct got_tree_object
*tree
= NULL
;
706 struct got_tree_entry
*te
;
707 struct repo_dir
*repo_dir
= t
->repo_dir
;
710 char *escaped_name
= NULL
, *path
= NULL
;
716 rc
= TAILQ_FIRST(&t
->repo_commits
);
718 if (qs
->folder
!= NULL
) {
719 path
= strdup(qs
->folder
);
721 error
= got_error_from_errno("strdup");
725 error
= got_repo_map_path(&path
, repo
, repo_dir
->path
);
730 error
= got_repo_match_object_id(&commit_id
, NULL
, rc
->commit_id
,
731 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
735 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
739 error
= got_object_id_by_path(&tree_id
, repo
, commit
, path
);
743 error
= got_object_open_as_tree(&tree
, repo
, tree_id
);
747 nentries
= got_object_tree_get_nentries(tree
);
749 for (i
= 0; i
< nentries
; i
++) {
750 te
= got_object_tree_get_entry(tree
, i
);
752 name
= got_tree_entry_get_name(te
);
753 mode
= got_tree_entry_get_mode(te
);
754 if (!S_ISDIR(mode
) && (!strcasecmp(name
, "README") ||
755 !strcasecmp(name
, "README.md") ||
756 !strcasecmp(name
, "README.txt"))) {
758 *readme
= strdup(name
);
761 if (cb(c
->tp
, te
) == -1) {
762 error
= got_error(GOT_ERR_CANCELLED
);
769 got_ref_list_free(&refs
);
771 got_object_commit_close(commit
);
773 got_object_tree_close(tree
);
779 if (error
->code
!= GOT_ERR_CANCELLED
)
780 log_warnx("%s: %s", __func__
, error
->msg
);
786 const struct got_error
*
787 got_open_blob_for_output(struct got_blob_object
**blob
, int *fd
,
788 int *binary
, struct request
*c
, const char *directory
, const char *file
,
789 const char *commitstr
)
791 const struct got_error
*error
= NULL
;
792 struct got_repository
*repo
= c
->t
->repo
;
793 struct got_commit_object
*commit
= NULL
;
794 struct got_object_id
*commit_id
= NULL
;
795 struct got_object_id
*blob_id
= NULL
;
796 struct got_reflist_head refs
;
797 char *path
= NULL
, *in_repo_path
= NULL
;
806 error
= got_ref_list(&refs
, repo
, "refs/heads",
807 got_ref_cmp_by_name
, NULL
);
811 if (asprintf(&path
, "%s%s%s", directory
? directory
: "",
812 directory
? "/" : "", file
) == -1) {
813 error
= got_error_from_errno("asprintf");
817 error
= got_repo_map_path(&in_repo_path
, repo
, path
);
821 if (commitstr
== NULL
)
822 commitstr
= GOT_REF_HEAD
;
824 error
= got_repo_match_object_id(&commit_id
, NULL
, commitstr
,
825 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
829 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
833 error
= got_object_id_by_path(&blob_id
, repo
, commit
, in_repo_path
);
837 if (blob_id
== NULL
) {
838 error
= got_error(GOT_ERR_NO_OBJ
);
842 error
= got_object_get_type(&obj_type
, repo
, blob_id
);
846 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
847 error
= got_error(GOT_ERR_OBJ_TYPE
);
851 error
= got_gotweb_dupfd(&c
->priv_fd
[BLOB_FD_1
], fd
);
855 error
= got_object_open_as_blob(blob
, repo
, blob_id
, BUF
, *fd
);
859 error
= got_object_blob_is_binary(binary
, *blob
);
865 got_object_commit_close(commit
);
871 got_object_blob_close(*blob
);
876 got_ref_list_free(&refs
);
885 got_output_blob_by_lines(struct template *tp
, struct got_blob_object
*blob
,
886 int (*cb
)(struct template *, const char *, size_t))
888 const struct got_error
*err
;
895 err
= got_object_blob_getline(&line
, &linelen
, &linesize
,
897 if (err
|| linelen
== -1)
900 if (cb(tp
, line
, lineno
) == -1) {
901 err
= got_error(GOT_ERR_CANCELLED
);
909 if (err
->code
!= GOT_ERR_CANCELLED
)
910 log_warnx("%s: got_object_blob_getline failed: %s",
917 struct blame_cb_args
{
918 struct blame_line
*lines
;
924 struct got_repository
*repo
;
926 got_render_blame_line_cb cb
;
929 static const struct got_error
*
930 got_gotweb_blame_cb(void *arg
, int nlines
, int lineno
,
931 struct got_commit_object
*commit
, struct got_object_id
*id
)
933 const struct got_error
*err
= NULL
;
934 struct blame_cb_args
*a
= arg
;
935 struct blame_line
*bline
;
936 struct request
*c
= a
->c
;
941 time_t committer_time
;
943 if (nlines
!= a
->nlines
||
944 (lineno
!= -1 && lineno
< 1) || lineno
> a
->nlines
)
945 return got_error(GOT_ERR_RANGE
);
948 return NULL
; /* no change in this commit */
950 /* Annotate this line. */
951 bline
= &a
->lines
[lineno
- 1];
952 if (bline
->annotated
)
954 err
= got_object_id_str(&bline
->id_str
, id
);
958 bline
->committer
= strdup(got_object_commit_get_committer(commit
));
959 if (bline
->committer
== NULL
) {
960 err
= got_error_from_errno("strdup");
964 committer_time
= got_object_commit_get_committer_time(commit
);
965 if (gmtime_r(&committer_time
, &tm
) == NULL
)
966 return got_error_from_errno("gmtime_r");
967 if (strftime(bline
->datebuf
, sizeof(bline
->datebuf
), "%F", &tm
) == 0) {
968 err
= got_error(GOT_ERR_NO_SPACE
);
971 bline
->annotated
= 1;
973 /* Print lines annotated so far. */
974 bline
= &a
->lines
[a
->lineno_cur
- 1];
975 if (!bline
->annotated
)
978 offset
= a
->line_offsets
[a
->lineno_cur
- 1];
979 if (fseeko(a
->f
, offset
, SEEK_SET
) == -1) {
980 err
= got_error_from_errno("fseeko");
984 while (a
->lineno_cur
<= a
->nlines
&& bline
->annotated
) {
985 if (getline(&line
, &linesize
, a
->f
) == -1) {
987 err
= got_error_from_errno("getline");
991 if (a
->cb(c
->tp
, line
, bline
, a
->nlines_prec
,
992 a
->lineno_cur
) == -1) {
993 err
= got_error(GOT_ERR_CANCELLED
);
998 bline
= &a
->lines
[a
->lineno_cur
- 1];
1005 const struct got_error
*
1006 got_output_file_blame(struct request
*c
, got_render_blame_line_cb cb
)
1008 const struct got_error
*error
= NULL
;
1009 struct transport
*t
= c
->t
;
1010 struct got_repository
*repo
= t
->repo
;
1011 struct querystring
*qs
= c
->t
->qs
;
1012 struct got_object_id
*obj_id
= NULL
, *commit_id
= NULL
;
1013 struct got_commit_object
*commit
= NULL
;
1014 struct got_reflist_head refs
;
1015 struct got_blob_object
*blob
= NULL
;
1016 char *path
= NULL
, *in_repo_path
= NULL
;
1017 struct blame_cb_args bca
;
1018 int i
, obj_type
, blobfd
= -1, fd1
= -1, fd2
= -1;
1020 FILE *f1
= NULL
, *f2
= NULL
;
1027 if (asprintf(&path
, "%s/%s", qs
->folder
, qs
->file
) == -1) {
1028 error
= got_error_from_errno("asprintf");
1032 error
= got_repo_map_path(&in_repo_path
, repo
, path
);
1036 error
= got_repo_match_object_id(&commit_id
, NULL
, qs
->commit
,
1037 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
1041 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
1045 error
= got_object_id_by_path(&obj_id
, repo
, commit
, in_repo_path
);
1049 error
= got_object_get_type(&obj_type
, repo
, obj_id
);
1053 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
1054 error
= got_error(GOT_ERR_OBJ_TYPE
);
1058 error
= got_gotweb_openfile(&bca
.f
, &c
->priv_fd
[BLAME_FD_1
]);
1062 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_2
], &blobfd
);
1066 error
= got_object_open_as_blob(&blob
, repo
, obj_id
, BUF
, blobfd
);
1070 error
= got_object_blob_dump_to_file(&filesize
, &bca
.nlines
,
1071 &bca
.line_offsets
, bca
.f
, blob
);
1072 if (error
|| bca
.nlines
== 0)
1075 /* Don't include \n at EOF in the blame line count. */
1076 if (bca
.line_offsets
[bca
.nlines
- 1] == filesize
)
1079 bca
.lines
= calloc(bca
.nlines
, sizeof(*bca
.lines
));
1080 if (bca
.lines
== NULL
) {
1081 error
= got_error_from_errno("calloc");
1085 bca
.nlines_prec
= 0;
1094 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_3
], &fd1
);
1098 error
= got_gotweb_dupfd(&c
->priv_fd
[BLAME_FD_4
], &fd2
);
1102 error
= got_gotweb_openfile(&f1
, &c
->priv_fd
[BLAME_FD_5
]);
1106 error
= got_gotweb_openfile(&f2
, &c
->priv_fd
[BLAME_FD_6
]);
1110 error
= got_blame(in_repo_path
, commit_id
, repo
,
1111 GOT_DIFF_ALGORITHM_MYERS
, got_gotweb_blame_cb
, &bca
, NULL
, NULL
,
1116 free(bca
.line_offsets
);
1117 for (i
= 0; i
< bca
.nlines
; i
++) {
1118 struct blame_line
*bline
= &bca
.lines
[i
];
1119 free(bline
->id_str
);
1120 free(bline
->committer
);
1124 if (blobfd
!= -1 && close(blobfd
) == -1 && error
== NULL
)
1125 error
= got_error_from_errno("close");
1126 if (fd1
!= -1 && close(fd1
) == -1 && error
== NULL
)
1127 error
= got_error_from_errno("close");
1128 if (fd2
!= -1 && close(fd2
) == -1 && error
== NULL
)
1129 error
= got_error_from_errno("close");
1131 const struct got_error
*bca_err
= got_gotweb_closefile(bca
.f
);
1136 const struct got_error
*f1_err
= got_gotweb_closefile(f1
);
1141 const struct got_error
*f2_err
= got_gotweb_closefile(f2
);
1146 got_object_commit_close(commit
);
1148 got_object_blob_close(blob
);
1153 got_ref_list_free(&refs
);
1157 const struct got_error
*
1158 got_open_diff_for_output(FILE **fp
, struct request
*c
)
1160 const struct got_error
*error
= NULL
;
1161 struct transport
*t
= c
->t
;
1162 struct got_repository
*repo
= t
->repo
;
1163 struct repo_commit
*rc
= NULL
;
1164 struct got_object_id
*id1
= NULL
, *id2
= NULL
;
1165 struct got_reflist_head refs
;
1166 FILE *f1
= NULL
, *f2
= NULL
, *f3
= NULL
;
1167 int obj_type
, fd1
= -1, fd2
= -1;
1173 error
= got_gotweb_openfile(&f1
, &c
->priv_fd
[DIFF_FD_1
]);
1177 error
= got_gotweb_openfile(&f2
, &c
->priv_fd
[DIFF_FD_2
]);
1181 error
= got_gotweb_openfile(&f3
, &c
->priv_fd
[DIFF_FD_3
]);
1185 rc
= TAILQ_FIRST(&t
->repo_commits
);
1187 if (rc
->parent_id
!= NULL
&&
1188 strncmp(rc
->parent_id
, "/dev/null", 9) != 0) {
1189 error
= got_repo_match_object_id(&id1
, NULL
,
1190 rc
->parent_id
, GOT_OBJ_TYPE_ANY
,
1196 error
= got_repo_match_object_id(&id2
, NULL
, rc
->commit_id
,
1197 GOT_OBJ_TYPE_ANY
, &refs
, repo
);
1201 error
= got_object_get_type(&obj_type
, repo
, id2
);
1205 error
= got_gotweb_dupfd(&c
->priv_fd
[DIFF_FD_4
], &fd1
);
1209 error
= got_gotweb_dupfd(&c
->priv_fd
[DIFF_FD_5
], &fd2
);
1214 case GOT_OBJ_TYPE_BLOB
:
1215 error
= got_diff_objects_as_blobs(NULL
, NULL
, f1
, f2
, fd1
, fd2
,
1216 id1
, id2
, NULL
, NULL
, GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1219 case GOT_OBJ_TYPE_TREE
:
1220 error
= got_diff_objects_as_trees(NULL
, NULL
, f1
, f2
, fd1
, fd2
,
1221 id1
, id2
, NULL
, "", "", GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1224 case GOT_OBJ_TYPE_COMMIT
:
1225 error
= got_diff_objects_as_commits(NULL
, NULL
, f1
, f2
, fd1
,
1226 fd2
, id1
, id2
, NULL
, GOT_DIFF_ALGORITHM_MYERS
, 3, 0, 0,
1230 error
= got_error(GOT_ERR_OBJ_TYPE
);
1235 if (fseek(f3
, 0, SEEK_SET
) == -1) {
1236 error
= got_ferror(f3
, GOT_ERR_IO
);
1243 if (fd1
!= -1 && close(fd1
) == -1 && error
== NULL
)
1244 error
= got_error_from_errno("close");
1245 if (fd2
!= -1 && close(fd2
) == -1 && error
== NULL
)
1246 error
= got_error_from_errno("close");
1248 const struct got_error
*f1_err
= got_gotweb_closefile(f1
);
1253 const struct got_error
*f2_err
= got_gotweb_closefile(f2
);
1258 got_gotweb_closefile(f3
);
1261 got_ref_list_free(&refs
);
1267 static const struct got_error
*
1268 got_init_repo_commit(struct repo_commit
**rc
)
1270 *rc
= calloc(1, sizeof(**rc
));
1272 return got_error_from_errno2(__func__
, "calloc");
1275 (*rc
)->refs_str
= NULL
;
1276 (*rc
)->commit_id
= NULL
;
1277 (*rc
)->committer
= NULL
;
1278 (*rc
)->author
= NULL
;
1279 (*rc
)->parent_id
= NULL
;
1280 (*rc
)->tree_id
= NULL
;
1281 (*rc
)->commit_msg
= NULL
;
1286 static const struct got_error
*
1287 got_init_repo_tag(struct repo_tag
**rt
)
1289 *rt
= calloc(1, sizeof(**rt
));
1291 return got_error_from_errno2(__func__
, "calloc");
1293 (*rt
)->commit_id
= NULL
;
1294 (*rt
)->tag_name
= NULL
;
1295 (*rt
)->tag_commit
= NULL
;
1296 (*rt
)->commit_msg
= NULL
;
1297 (*rt
)->tagger
= NULL
;