Blob


1 /*
2 * Copyright (c) 2018 Stefan Sperling <stsp@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <sys/types.h>
18 #include <sys/queue.h>
19 #include <sys/uio.h>
20 #include <sys/stat.h>
21 #include <sys/wait.h>
23 #include <limits.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <sha1.h>
27 #include <string.h>
28 #include <zlib.h>
29 #include <errno.h>
30 #include <libgen.h>
31 #include <stdint.h>
32 #include <imsg.h>
34 #include "got_error.h"
35 #include "got_reference.h"
36 #include "got_repository.h"
37 #include "got_worktree.h"
38 #include "got_object.h"
40 #include "got_lib_path.h"
41 #include "got_lib_delta.h"
42 #include "got_lib_inflate.h"
43 #include "got_lib_object.h"
44 #include "got_lib_pack.h"
45 #include "got_lib_repository.h"
46 #include "got_lib_worktree.h"
47 #include "got_lib_object_idcache.h"
48 #include "got_lib_privsep.h"
50 #ifndef nitems
51 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
52 #endif
54 #define GOT_GIT_DIR ".git"
56 /* Mandatory files and directories inside the git directory. */
57 #define GOT_OBJECTS_DIR "objects"
58 #define GOT_REFS_DIR "refs"
59 #define GOT_HEAD_FILE "HEAD"
61 /* Other files and directories inside the git directory. */
62 #define GOT_FETCH_HEAD_FILE "FETCH_HEAD"
63 #define GOT_ORIG_HEAD_FILE "ORIG_HEAD"
64 #define GOT_OBJECTS_PACK_DIR "objects/pack"
66 char *
67 got_repo_get_path(struct got_repository *repo)
68 {
69 return strdup(repo->path);
70 }
72 char *
73 got_repo_get_path_git_dir(struct got_repository *repo)
74 {
75 return strdup(repo->path_git_dir);
76 }
78 int
79 got_repo_is_bare(struct got_repository *repo)
80 {
81 return (strcmp(repo->path, repo->path_git_dir) == 0);
82 }
84 static char *
85 get_path_git_child(struct got_repository *repo, const char *basename)
86 {
87 char *path_child;
89 if (asprintf(&path_child, "%s/%s", repo->path_git_dir,
90 basename) == -1)
91 return NULL;
93 return path_child;
94 }
96 char *
97 got_repo_get_path_objects(struct got_repository *repo)
98 {
99 return get_path_git_child(repo, GOT_OBJECTS_DIR);
102 char *
103 got_repo_get_path_objects_pack(struct got_repository *repo)
105 return get_path_git_child(repo, GOT_OBJECTS_PACK_DIR);
108 char *
109 got_repo_get_path_refs(struct got_repository *repo)
111 return get_path_git_child(repo, GOT_REFS_DIR);
114 static char *
115 get_path_head(struct got_repository *repo)
117 return get_path_git_child(repo, GOT_HEAD_FILE);
120 static int
121 is_git_repo(struct got_repository *repo)
123 char *path_git = got_repo_get_path_git_dir(repo);
124 char *path_objects = got_repo_get_path_objects(repo);
125 char *path_refs = got_repo_get_path_refs(repo);
126 char *path_head = get_path_head(repo);
127 int ret = 0;
128 struct stat sb;
129 struct got_reference *head_ref;
131 if (lstat(path_git, &sb) == -1)
132 goto done;
133 if (!S_ISDIR(sb.st_mode))
134 goto done;
136 if (lstat(path_objects, &sb) == -1)
137 goto done;
138 if (!S_ISDIR(sb.st_mode))
139 goto done;
141 if (lstat(path_refs, &sb) == -1)
142 goto done;
143 if (!S_ISDIR(sb.st_mode))
144 goto done;
146 if (lstat(path_head, &sb) == -1)
147 goto done;
148 if (!S_ISREG(sb.st_mode))
149 goto done;
151 /* Check if the HEAD reference can be opened. */
152 if (got_ref_open(&head_ref, repo, GOT_REF_HEAD) != NULL)
153 goto done;
154 got_ref_close(head_ref);
156 ret = 1;
157 done:
158 free(path_git);
159 free(path_objects);
160 free(path_refs);
161 free(path_head);
162 return ret;
166 #ifndef GOT_NO_OBJ_CACHE
167 static const struct got_error *
168 cache_add(struct got_object_cache *cache, struct got_object_id *id, void *item)
170 const struct got_error *err = NULL;
171 struct got_object_cache_entry *ce;
172 int nelem;
174 nelem = got_object_idcache_num_elements(cache->idcache);
175 if (nelem >= cache->size) {
176 err = got_object_idcache_remove_least_used((void **)&ce,
177 cache->idcache);
178 if (err)
179 return err;
180 switch (cache->type) {
181 case GOT_OBJECT_CACHE_TYPE_OBJ:
182 got_object_close(ce->data.obj);
183 break;
184 case GOT_OBJECT_CACHE_TYPE_TREE:
185 got_object_tree_close(ce->data.tree);
186 break;
187 case GOT_OBJECT_CACHE_TYPE_COMMIT:
188 got_object_commit_close(ce->data.commit);
189 break;
191 free(ce);
194 ce = calloc(1, sizeof(*ce));
195 if (ce == NULL)
196 return got_error_from_errno();
197 memcpy(&ce->id, id, sizeof(ce->id));
198 switch (cache->type) {
199 case GOT_OBJECT_CACHE_TYPE_OBJ:
200 ce->data.obj = (struct got_object *)item;
201 break;
202 case GOT_OBJECT_CACHE_TYPE_TREE:
203 ce->data.tree = (struct got_tree_object *)item;
204 break;
205 case GOT_OBJECT_CACHE_TYPE_COMMIT:
206 ce->data.commit = (struct got_commit_object *)item;
207 break;
210 err = got_object_idcache_add(cache->idcache, id, ce);
211 if (err) {
212 if (err->code == GOT_ERR_OBJ_EXISTS) {
213 free(ce);
214 err = NULL;
217 return err;
219 #endif
221 const struct got_error *
222 got_repo_cache_object(struct got_repository *repo, struct got_object_id *id,
223 struct got_object *obj)
225 #ifndef GOT_NO_OBJ_CACHE
226 const struct got_error *err = NULL;
227 err = cache_add(&repo->objcache, id, obj);
228 if (err)
229 return err;
230 obj->refcnt++;
231 #endif
232 return NULL;
235 struct got_object *
236 got_repo_get_cached_object(struct got_repository *repo,
237 struct got_object_id *id)
239 struct got_object_cache_entry *ce;
241 ce = got_object_idcache_get(repo->objcache.idcache, id);
242 if (ce) {
243 repo->objcache.cache_hit++;
244 return ce->data.obj;
247 repo->objcache.cache_miss++;
248 return NULL;
251 const struct got_error *
252 got_repo_cache_tree(struct got_repository *repo, struct got_object_id *id,
253 struct got_tree_object *tree)
255 #ifndef GOT_NO_OBJ_CACHE
256 const struct got_error *err = NULL;
257 err = cache_add(&repo->treecache, id, tree);
258 if (err)
259 return err;
260 tree->refcnt++;
261 #endif
262 return NULL;
265 struct got_tree_object *
266 got_repo_get_cached_tree(struct got_repository *repo,
267 struct got_object_id *id)
269 struct got_object_cache_entry *ce;
271 ce = got_object_idcache_get(repo->treecache.idcache, id);
272 if (ce) {
273 repo->treecache.cache_hit++;
274 return ce->data.tree;
277 repo->treecache.cache_miss++;
278 return NULL;
281 const struct got_error *
282 got_repo_cache_commit(struct got_repository *repo, struct got_object_id *id,
283 struct got_commit_object *commit)
285 #ifndef GOT_NO_OBJ_CACHE
286 const struct got_error *err = NULL;
287 err = cache_add(&repo->commitcache, id, commit);
288 if (err)
289 return err;
291 commit->refcnt++;
292 #endif
293 return NULL;
296 struct got_commit_object *
297 got_repo_get_cached_commit(struct got_repository *repo,
298 struct got_object_id *id)
300 struct got_object_cache_entry *ce;
302 ce = got_object_idcache_get(repo->commitcache.idcache, id);
303 if (ce) {
304 repo->commitcache.cache_hit++;
305 return ce->data.commit;
308 repo->commitcache.cache_miss++;
309 return NULL;
312 const struct got_error *
313 open_repo(struct got_repository *repo, const char *path)
315 const struct got_error *err = NULL;
316 struct got_worktree *worktree = NULL;
318 /* bare git repository? */
319 repo->path_git_dir = strdup(path);
320 if (repo->path_git_dir == NULL) {
321 err = got_error_from_errno();
322 goto done;
324 if (is_git_repo(repo)) {
325 repo->path = strdup(repo->path_git_dir);
326 if (repo->path == NULL) {
327 err = got_error_from_errno();
328 goto done;
330 return NULL;
333 /* git repository with working tree? */
334 free(repo->path_git_dir);
335 if (asprintf(&repo->path_git_dir, "%s/%s", path, GOT_GIT_DIR) == -1) {
336 err = got_error_from_errno();
337 goto done;
339 if (is_git_repo(repo)) {
340 repo->path = strdup(path);
341 if (repo->path == NULL) {
342 err = got_error_from_errno();
343 goto done;
345 return NULL;
348 /* got work tree checked out from bare git repository? */
349 free(repo->path_git_dir);
350 repo->path_git_dir = NULL;
351 err = got_worktree_open(&worktree, path);
352 if (err) {
353 if (err->code == GOT_ERR_ERRNO && errno == ENOENT)
354 err = got_error(GOT_ERR_NOT_GIT_REPO);
355 goto done;
357 repo->path_git_dir = strdup(worktree->repo_path);
358 if (repo->path_git_dir == NULL) {
359 err = got_error_from_errno();
360 goto done;
363 /* got work tree checked out from git repository with working tree? */
364 if (!is_git_repo(repo)) {
365 free(repo->path_git_dir);
366 if (asprintf(&repo->path_git_dir, "%s/%s", worktree->repo_path,
367 GOT_GIT_DIR) == -1) {
368 err = got_error_from_errno();
369 repo->path_git_dir = NULL;
370 goto done;
372 if (!is_git_repo(repo)) {
373 err = got_error(GOT_ERR_NOT_GIT_REPO);
374 goto done;
376 repo->path = strdup(worktree->repo_path);
377 if (repo->path == NULL) {
378 err = got_error_from_errno();
379 goto done;
381 } else {
382 repo->path = strdup(repo->path_git_dir);
383 if (repo->path == NULL) {
384 err = got_error_from_errno();
385 goto done;
388 done:
389 if (worktree)
390 got_worktree_close(worktree);
391 return err;
394 const struct got_error *
395 got_repo_open(struct got_repository **repop, const char *path)
397 struct got_repository *repo = NULL;
398 const struct got_error *err = NULL;
399 char *abspath, *normpath = NULL;
400 int i, tried_root = 0;
402 *repop = NULL;
404 if (got_path_is_absolute(path))
405 abspath = strdup(path);
406 else
407 abspath = got_path_get_absolute(path);
408 if (abspath == NULL)
409 return got_error(GOT_ERR_BAD_PATH);
411 repo = calloc(1, sizeof(*repo));
412 if (repo == NULL) {
413 err = got_error_from_errno();
414 goto done;
417 for (i = 0; i < nitems(repo->privsep_children); i++) {
418 repo->privsep_children[i].imsg_fd = -1;
419 repo->privsep_children[i].pid = 0;
422 repo->objcache.type = GOT_OBJECT_CACHE_TYPE_OBJ;
423 repo->objcache.size = GOT_OBJECT_CACHE_SIZE_OBJ;
424 repo->objcache.idcache = got_object_idcache_alloc(repo->objcache.size);
425 if (repo->objcache.idcache == NULL) {
426 err = got_error_from_errno();
427 goto done;
430 repo->treecache.type = GOT_OBJECT_CACHE_TYPE_TREE;
431 repo->treecache.size = GOT_OBJECT_CACHE_SIZE_TREE;
432 repo->treecache.idcache =
433 got_object_idcache_alloc(repo->treecache.size);
434 if (repo->treecache.idcache == NULL) {
435 err = got_error_from_errno();
436 goto done;
439 repo->commitcache.type = GOT_OBJECT_CACHE_TYPE_COMMIT;
440 repo->commitcache.size = GOT_OBJECT_CACHE_SIZE_COMMIT;
441 repo->commitcache.idcache =
442 got_object_idcache_alloc(repo->commitcache.size);
443 if (repo->commitcache.idcache == NULL) {
444 err = got_error_from_errno();
445 goto done;
448 normpath = got_path_normalize(abspath);
449 if (normpath == NULL) {
450 err = got_error(GOT_ERR_BAD_PATH);
451 goto done;
454 path = normpath;
455 do {
456 err = open_repo(repo, path);
457 if (err == NULL)
458 break;
459 if (err->code != GOT_ERR_NOT_GIT_REPO)
460 break;
461 if (path[0] == '/' && path[1] == '\0') {
462 if (tried_root) {
463 err = got_error(GOT_ERR_NOT_GIT_REPO);
464 break;
466 tried_root = 1;
468 path = dirname(path);
469 if (path == NULL)
470 err = got_error_from_errno();
471 } while (path);
472 done:
473 if (err)
474 err = got_repo_close(repo);
475 else
476 *repop = repo;
477 free(abspath);
478 free(normpath);
479 return err;
482 #if 0
483 static void
484 print_cache_stats(struct got_object_cache *cache, const char *name)
486 fprintf(stderr, "%s cache: %d elements, %d hits, %d missed\n",
487 name, got_object_idcache_num_elements(cache->idcache),
488 cache->cache_hit, cache->cache_miss);
491 void check_refcount(struct got_object_id *id, void *data, void *arg)
493 struct got_object_cache *cache = arg;
494 struct got_object_cache_entry *ce = data;
495 struct got_object *obj;
496 struct got_tree_object *tree;
497 struct got_commit_object *commit;
498 char *id_str;
500 if (got_object_id_str(&id_str, id) != NULL)
501 return;
503 switch (cache->type) {
504 case GOT_OBJECT_CACHE_TYPE_OBJ:
505 obj = ce->data.obj;
506 if (obj->refcnt == 1)
507 break;
508 fprintf(stderr, "object %s has %d unclaimed references\n",
509 id_str, obj->refcnt - 1);
510 break;
511 case GOT_OBJECT_CACHE_TYPE_TREE:
512 tree = ce->data.tree;
513 if (tree->refcnt == 1)
514 break;
515 fprintf(stderr, "tree %s has %d unclaimed references\n",
516 id_str, tree->refcnt - 1);
517 break;
518 case GOT_OBJECT_CACHE_TYPE_COMMIT:
519 commit = ce->data.commit;
520 if (commit->refcnt == 1)
521 break;
522 fprintf(stderr, "commit %s has %d unclaimed references\n",
523 id_str, commit->refcnt);
524 break;
526 free(id_str);
528 #endif
530 static const struct got_error *
531 wait_for_child(pid_t pid)
533 int child_status;
535 waitpid(pid, &child_status, 0);
537 if (!WIFEXITED(child_status))
538 return got_error(GOT_ERR_PRIVSEP_DIED);
540 if (WEXITSTATUS(child_status) != 0)
541 return got_error(GOT_ERR_PRIVSEP_EXIT);
543 return NULL;
546 const struct got_error *
547 got_repo_close(struct got_repository *repo)
549 const struct got_error *err = NULL, *child_err;
550 int i;
552 for (i = 0; i < nitems(repo->packidx_cache); i++) {
553 if (repo->packidx_cache[i] == NULL)
554 break;
555 got_packidx_close(repo->packidx_cache[i]);
558 for (i = 0; i < nitems(repo->packs); i++) {
559 if (repo->packs[i].path_packfile == NULL)
560 break;
561 got_pack_close(&repo->packs[i]);
564 free(repo->path);
565 free(repo->path_git_dir);
567 #if 0
568 print_cache_stats(&repo->objcache, "object");
569 print_cache_stats(&repo->treecache, "tree");
570 print_cache_stats(&repo->commitcache, "commit");
571 got_object_idcache_for_each(repo->objcache.idcache, check_refcount,
572 &repo->objcache);
573 got_object_idcache_for_each(repo->treecache.idcache, check_refcount,
574 &repo->treecache);
575 got_object_idcache_for_each(repo->commitcache.idcache, check_refcount,
576 &repo->commitcache);
577 #endif
579 if (repo->objcache.idcache)
580 got_object_idcache_free(repo->objcache.idcache);
581 if (repo->treecache.idcache)
582 got_object_idcache_free(repo->treecache.idcache);
583 if (repo->commitcache.idcache)
584 got_object_idcache_free(repo->commitcache.idcache);
586 for (i = 0; i < nitems(repo->privsep_children); i++) {
587 if (repo->privsep_children[i].imsg_fd == -1)
588 continue;
589 err = got_privsep_send_stop(repo->privsep_children[i].imsg_fd);
590 child_err = wait_for_child(repo->privsep_children[i].pid);
591 if (child_err && err == NULL)
592 err = child_err;
593 close(repo->privsep_children[i].imsg_fd);
595 free(repo);
597 return err;
600 const struct got_error *
601 got_repo_map_path(char **in_repo_path, struct got_repository *repo,
602 const char *input_path)
604 const struct got_error *err = NULL;
605 char *repo_abspath = NULL, *cwd = NULL;
606 struct stat sb;
607 size_t repolen, cwdlen, len;
608 char *canonpath, *path;
610 *in_repo_path = NULL;
612 cwd = getcwd(NULL, 0);
613 if (cwd == NULL)
614 return got_error_from_errno();
616 canonpath = strdup(input_path);
617 if (canonpath == NULL) {
618 err = got_error_from_errno();
619 goto done;
621 err = got_canonpath(input_path, canonpath, strlen(canonpath) + 1);
622 if (err)
623 goto done;
625 repo_abspath = got_repo_get_path(repo);
626 if (repo_abspath == NULL) {
627 err = got_error_from_errno();
628 goto done;
631 /* TODO: Call "get in-repository path of work-tree node" API. */
633 if (lstat(canonpath, &sb) != 0) {
634 if (errno != ENOENT) {
635 err = got_error_from_errno();
636 goto done;
638 /*
639 * Path is not on disk.
640 * Assume it is already relative to repository root.
641 */
642 path = strdup(canonpath);
643 } else {
644 int is_repo_child = 0, is_cwd_child = 0;
646 path = realpath(canonpath, NULL);
647 if (path == NULL) {
648 err = got_error_from_errno();
649 goto done;
652 repolen = strlen(repo_abspath);
653 cwdlen = strlen(cwd);
654 len = strlen(path);
656 if (len > repolen && strncmp(path, repo_abspath, repolen) == 0)
657 is_repo_child = 1;
658 if (len > cwdlen && strncmp(path, cwd, cwdlen) == 0)
659 is_cwd_child = 1;
661 if (strcmp(path, repo_abspath) == 0) {
662 free(path);
663 path = strdup("");
664 if (path == NULL) {
665 err = got_error_from_errno();
666 goto done;
668 } else if (is_repo_child && is_cwd_child) {
669 char *child;
670 /* TODO: Is path inside a got worktree? */
671 /* Strip common prefix with repository path. */
672 err = got_path_skip_common_ancestor(&child,
673 repo_abspath, path);
674 if (err)
675 goto done;
676 free(path);
677 path = child;
678 } else if (is_repo_child) {
679 /* Matched an on-disk path inside repository. */
680 if (got_repo_is_bare(repo)) {
681 /*
682 * Matched an on-disk path inside repository
683 * database. Treat as repository-relative.
684 */
685 } else {
686 char *child;
687 /* Strip common prefix with repository path. */
688 err = got_path_skip_common_ancestor(&child,
689 repo_abspath, path);
690 if (err)
691 goto done;
692 free(path);
693 path = child;
695 } else if (is_cwd_child) {
696 char *child;
697 /* TODO: Is path inside a got worktree? */
698 /* Strip common prefix with cwd. */
699 err = got_path_skip_common_ancestor(&child, cwd,
700 path);
701 if (err)
702 goto done;
703 free(path);
704 path = child;
705 } else {
706 /*
707 * Matched unrelated on-disk path.
708 * Treat it as repository-relative.
709 */
713 /* Make in-repository path absolute */
714 if (path[0] != '/') {
715 char *abspath;
716 if (asprintf(&abspath, "/%s", path) == -1) {
717 err = got_error_from_errno();
718 goto done;
720 free(path);
721 path = abspath;
724 done:
725 free(repo_abspath);
726 free(cwd);
727 free(canonpath);
728 if (err)
729 free(path);
730 else
731 *in_repo_path = path;
732 return err;