2 * Copyright (c) 2018 Stefan Sperling <stsp@openbsd.org>
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.
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.
17 #include <sys/types.h>
18 #include <sys/queue.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"
51 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
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"
67 got_repo_get_path(struct got_repository *repo)
69 return strdup(repo->path);
73 got_repo_get_path_git_dir(struct got_repository *repo)
75 return strdup(repo->path_git_dir);
79 got_repo_is_bare(struct got_repository *repo)
81 return (strcmp(repo->path, repo->path_git_dir) == 0);
85 get_path_git_child(struct got_repository *repo, const char *basename)
89 if (asprintf(&path_child, "%s/%s", repo->path_git_dir,
97 got_repo_get_path_objects(struct got_repository *repo)
99 return get_path_git_child(repo, GOT_OBJECTS_DIR);
103 got_repo_get_path_objects_pack(struct got_repository *repo)
105 return get_path_git_child(repo, GOT_OBJECTS_PACK_DIR);
109 got_repo_get_path_refs(struct got_repository *repo)
111 return get_path_git_child(repo, GOT_REFS_DIR);
115 get_path_head(struct got_repository *repo)
117 return get_path_git_child(repo, GOT_HEAD_FILE);
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);
129 struct got_reference *head_ref;
131 if (lstat(path_git, &sb) == -1)
133 if (!S_ISDIR(sb.st_mode))
136 if (lstat(path_objects, &sb) == -1)
138 if (!S_ISDIR(sb.st_mode))
141 if (lstat(path_refs, &sb) == -1)
143 if (!S_ISDIR(sb.st_mode))
146 if (lstat(path_head, &sb) == -1)
148 if (!S_ISREG(sb.st_mode))
151 /* Check if the HEAD reference can be opened. */
152 if (got_ref_open(&head_ref, repo, GOT_REF_HEAD) != NULL)
154 got_ref_close(head_ref);
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;
174 nelem = got_object_idcache_num_elements(cache->idcache);
175 if (nelem >= cache->size) {
176 err = got_object_idcache_remove_least_used((void **)&ce,
180 switch (cache->type) {
181 case GOT_OBJECT_CACHE_TYPE_OBJ:
182 got_object_close(ce->data.obj);
184 case GOT_OBJECT_CACHE_TYPE_TREE:
185 got_object_tree_close(ce->data.tree);
187 case GOT_OBJECT_CACHE_TYPE_COMMIT:
188 got_object_commit_close(ce->data.commit);
194 ce = calloc(1, sizeof(*ce));
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;
202 case GOT_OBJECT_CACHE_TYPE_TREE:
203 ce->data.tree = (struct got_tree_object *)item;
205 case GOT_OBJECT_CACHE_TYPE_COMMIT:
206 ce->data.commit = (struct got_commit_object *)item;
210 err = got_object_idcache_add(cache->idcache, id, ce);
212 if (err->code == GOT_ERR_OBJ_EXISTS) {
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);
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);
243 repo->objcache.cache_hit++;
247 repo->objcache.cache_miss++;
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);
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);
273 repo->treecache.cache_hit++;
274 return ce->data.tree;
277 repo->treecache.cache_miss++;
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);
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);
304 repo->commitcache.cache_hit++;
305 return ce->data.commit;
308 repo->commitcache.cache_miss++;
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();
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();
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();
339 if (is_git_repo(repo)) {
340 repo->path = strdup(path);
341 if (repo->path == NULL) {
342 err = got_error_from_errno();
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);
353 if (err->code == GOT_ERR_ERRNO && errno == ENOENT)
354 err = got_error(GOT_ERR_NOT_GIT_REPO);
357 repo->path_git_dir = strdup(worktree->repo_path);
358 if (repo->path_git_dir == NULL) {
359 err = got_error_from_errno();
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;
372 if (!is_git_repo(repo)) {
373 err = got_error(GOT_ERR_NOT_GIT_REPO);
376 repo->path = strdup(worktree->repo_path);
377 if (repo->path == NULL) {
378 err = got_error_from_errno();
382 repo->path = strdup(repo->path_git_dir);
383 if (repo->path == NULL) {
384 err = got_error_from_errno();
390 got_worktree_close(worktree);
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;
404 if (got_path_is_absolute(path))
405 abspath = strdup(path);
407 abspath = got_path_get_absolute(path);
409 return got_error(GOT_ERR_BAD_PATH);
411 repo = calloc(1, sizeof(*repo));
413 err = got_error_from_errno();
417 for (i = 0; i < nitems(repo->privsep_children); i++) {
418 memset(&repo->privsep_children[i], 0,
419 sizeof(repo->privsep_children[0]));
420 repo->privsep_children[i].imsg_fd = -1;
423 repo->objcache.type = GOT_OBJECT_CACHE_TYPE_OBJ;
424 repo->objcache.size = GOT_OBJECT_CACHE_SIZE_OBJ;
425 repo->objcache.idcache = got_object_idcache_alloc(repo->objcache.size);
426 if (repo->objcache.idcache == NULL) {
427 err = got_error_from_errno();
431 repo->treecache.type = GOT_OBJECT_CACHE_TYPE_TREE;
432 repo->treecache.size = GOT_OBJECT_CACHE_SIZE_TREE;
433 repo->treecache.idcache =
434 got_object_idcache_alloc(repo->treecache.size);
435 if (repo->treecache.idcache == NULL) {
436 err = got_error_from_errno();
440 repo->commitcache.type = GOT_OBJECT_CACHE_TYPE_COMMIT;
441 repo->commitcache.size = GOT_OBJECT_CACHE_SIZE_COMMIT;
442 repo->commitcache.idcache =
443 got_object_idcache_alloc(repo->commitcache.size);
444 if (repo->commitcache.idcache == NULL) {
445 err = got_error_from_errno();
449 normpath = got_path_normalize(abspath);
450 if (normpath == NULL) {
451 err = got_error(GOT_ERR_BAD_PATH);
457 err = open_repo(repo, path);
460 if (err->code != GOT_ERR_NOT_GIT_REPO)
462 if (path[0] == '/' && path[1] == '\0') {
464 err = got_error(GOT_ERR_NOT_GIT_REPO);
469 path = dirname(path);
471 err = got_error_from_errno();
475 err = got_repo_close(repo);
485 print_cache_stats(struct got_object_cache *cache, const char *name)
487 fprintf(stderr, "%s cache: %d elements, %d hits, %d missed\n",
488 name, got_object_idcache_num_elements(cache->idcache),
489 cache->cache_hit, cache->cache_miss);
492 void check_refcount(struct got_object_id *id, void *data, void *arg)
494 struct got_object_cache *cache = arg;
495 struct got_object_cache_entry *ce = data;
496 struct got_object *obj;
497 struct got_tree_object *tree;
498 struct got_commit_object *commit;
501 if (got_object_id_str(&id_str, id) != NULL)
504 switch (cache->type) {
505 case GOT_OBJECT_CACHE_TYPE_OBJ:
507 if (obj->refcnt == 1)
509 fprintf(stderr, "object %s has %d unclaimed references\n",
510 id_str, obj->refcnt - 1);
512 case GOT_OBJECT_CACHE_TYPE_TREE:
513 tree = ce->data.tree;
514 if (tree->refcnt == 1)
516 fprintf(stderr, "tree %s has %d unclaimed references\n",
517 id_str, tree->refcnt - 1);
519 case GOT_OBJECT_CACHE_TYPE_COMMIT:
520 commit = ce->data.commit;
521 if (commit->refcnt == 1)
523 fprintf(stderr, "commit %s has %d unclaimed references\n",
524 id_str, commit->refcnt);
531 static const struct got_error *
532 wait_for_child(pid_t pid)
536 waitpid(pid, &child_status, 0);
538 if (!WIFEXITED(child_status))
539 return got_error(GOT_ERR_PRIVSEP_DIED);
541 if (WEXITSTATUS(child_status) != 0)
542 return got_error(GOT_ERR_PRIVSEP_EXIT);
547 const struct got_error *
548 got_repo_close(struct got_repository *repo)
550 const struct got_error *err = NULL, *child_err;
553 for (i = 0; i < nitems(repo->packidx_cache); i++) {
554 if (repo->packidx_cache[i] == NULL)
556 got_packidx_close(repo->packidx_cache[i]);
559 for (i = 0; i < nitems(repo->packs); i++) {
560 if (repo->packs[i].path_packfile == NULL)
562 got_pack_close(&repo->packs[i]);
566 free(repo->path_git_dir);
569 print_cache_stats(&repo->objcache, "object");
570 print_cache_stats(&repo->treecache, "tree");
571 print_cache_stats(&repo->commitcache, "commit");
572 got_object_idcache_for_each(repo->objcache.idcache, check_refcount,
574 got_object_idcache_for_each(repo->treecache.idcache, check_refcount,
576 got_object_idcache_for_each(repo->commitcache.idcache, check_refcount,
580 if (repo->objcache.idcache)
581 got_object_idcache_free(repo->objcache.idcache);
582 if (repo->treecache.idcache)
583 got_object_idcache_free(repo->treecache.idcache);
584 if (repo->commitcache.idcache)
585 got_object_idcache_free(repo->commitcache.idcache);
587 for (i = 0; i < nitems(repo->privsep_children); i++) {
588 if (repo->privsep_children[i].imsg_fd == -1)
590 imsg_clear(repo->privsep_children[i].ibuf);
591 free(repo->privsep_children[i].ibuf);
592 err = got_privsep_send_stop(repo->privsep_children[i].imsg_fd);
593 child_err = wait_for_child(repo->privsep_children[i].pid);
594 if (child_err && err == NULL)
596 close(repo->privsep_children[i].imsg_fd);
603 const struct got_error *
604 got_repo_map_path(char **in_repo_path, struct got_repository *repo,
605 const char *input_path)
607 const struct got_error *err = NULL;
608 char *repo_abspath = NULL, *cwd = NULL;
610 size_t repolen, cwdlen, len;
611 char *canonpath, *path;
613 *in_repo_path = NULL;
615 cwd = getcwd(NULL, 0);
617 return got_error_from_errno();
619 canonpath = strdup(input_path);
620 if (canonpath == NULL) {
621 err = got_error_from_errno();
624 err = got_canonpath(input_path, canonpath, strlen(canonpath) + 1);
628 repo_abspath = got_repo_get_path(repo);
629 if (repo_abspath == NULL) {
630 err = got_error_from_errno();
634 /* TODO: Call "get in-repository path of work-tree node" API. */
636 if (lstat(canonpath, &sb) != 0) {
637 if (errno != ENOENT) {
638 err = got_error_from_errno();
642 * Path is not on disk.
643 * Assume it is already relative to repository root.
645 path = strdup(canonpath);
647 int is_repo_child = 0, is_cwd_child = 0;
649 path = realpath(canonpath, NULL);
651 err = got_error_from_errno();
655 repolen = strlen(repo_abspath);
656 cwdlen = strlen(cwd);
659 if (len > repolen && strncmp(path, repo_abspath, repolen) == 0)
661 if (len > cwdlen && strncmp(path, cwd, cwdlen) == 0)
664 if (strcmp(path, repo_abspath) == 0) {
668 err = got_error_from_errno();
671 } else if (is_repo_child && is_cwd_child) {
673 /* TODO: Is path inside a got worktree? */
674 /* Strip common prefix with repository path. */
675 err = got_path_skip_common_ancestor(&child,
681 } else if (is_repo_child) {
682 /* Matched an on-disk path inside repository. */
683 if (got_repo_is_bare(repo)) {
685 * Matched an on-disk path inside repository
686 * database. Treat as repository-relative.
690 /* Strip common prefix with repository path. */
691 err = got_path_skip_common_ancestor(&child,
698 } else if (is_cwd_child) {
700 /* TODO: Is path inside a got worktree? */
701 /* Strip common prefix with cwd. */
702 err = got_path_skip_common_ancestor(&child, cwd,
710 * Matched unrelated on-disk path.
711 * Treat it as repository-relative.
716 /* Make in-repository path absolute */
717 if (path[0] != '/') {
719 if (asprintf(&abspath, "/%s", path) == -1) {
720 err = got_error_from_errno();
734 *in_repo_path = path;