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 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();
428 goto done;
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();
437 goto done;
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();
446 goto done;
449 normpath = got_path_normalize(abspath);
450 if (normpath == NULL) {
451 err = got_error(GOT_ERR_BAD_PATH);
452 goto done;
455 path = normpath;
456 do {
457 err = open_repo(repo, path);
458 if (err == NULL)
459 break;
460 if (err->code != GOT_ERR_NOT_GIT_REPO)
461 break;
462 if (path[0] == '/' && path[1] == '\0') {
463 if (tried_root) {
464 err = got_error(GOT_ERR_NOT_GIT_REPO);
465 break;
467 tried_root = 1;
469 path = dirname(path);
470 if (path == NULL)
471 err = got_error_from_errno();
472 } while (path);
473 done:
474 if (err)
475 err = got_repo_close(repo);
476 else
477 *repop = repo;
478 free(abspath);
479 free(normpath);
480 return err;
483 #if 0
484 static void
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;
499 char *id_str;
501 if (got_object_id_str(&id_str, id) != NULL)
502 return;
504 switch (cache->type) {
505 case GOT_OBJECT_CACHE_TYPE_OBJ:
506 obj = ce->data.obj;
507 if (obj->refcnt == 1)
508 break;
509 fprintf(stderr, "object %s has %d unclaimed references\n",
510 id_str, obj->refcnt - 1);
511 break;
512 case GOT_OBJECT_CACHE_TYPE_TREE:
513 tree = ce->data.tree;
514 if (tree->refcnt == 1)
515 break;
516 fprintf(stderr, "tree %s has %d unclaimed references\n",
517 id_str, tree->refcnt - 1);
518 break;
519 case GOT_OBJECT_CACHE_TYPE_COMMIT:
520 commit = ce->data.commit;
521 if (commit->refcnt == 1)
522 break;
523 fprintf(stderr, "commit %s has %d unclaimed references\n",
524 id_str, commit->refcnt);
525 break;
527 free(id_str);
529 #endif
531 static const struct got_error *
532 wait_for_child(pid_t pid)
534 int child_status;
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);
544 return NULL;
547 const struct got_error *
548 got_repo_close(struct got_repository *repo)
550 const struct got_error *err = NULL, *child_err;
551 int i;
553 for (i = 0; i < nitems(repo->packidx_cache); i++) {
554 if (repo->packidx_cache[i] == NULL)
555 break;
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)
561 break;
562 got_pack_close(&repo->packs[i]);
565 free(repo->path);
566 free(repo->path_git_dir);
568 #if 0
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,
573 &repo->objcache);
574 got_object_idcache_for_each(repo->treecache.idcache, check_refcount,
575 &repo->treecache);
576 got_object_idcache_for_each(repo->commitcache.idcache, check_refcount,
577 &repo->commitcache);
578 #endif
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)
589 continue;
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)
595 err = child_err;
596 close(repo->privsep_children[i].imsg_fd);
598 free(repo);
600 return err;
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;
609 struct stat sb;
610 size_t repolen, cwdlen, len;
611 char *canonpath, *path;
613 *in_repo_path = NULL;
615 cwd = getcwd(NULL, 0);
616 if (cwd == NULL)
617 return got_error_from_errno();
619 canonpath = strdup(input_path);
620 if (canonpath == NULL) {
621 err = got_error_from_errno();
622 goto done;
624 err = got_canonpath(input_path, canonpath, strlen(canonpath) + 1);
625 if (err)
626 goto done;
628 repo_abspath = got_repo_get_path(repo);
629 if (repo_abspath == NULL) {
630 err = got_error_from_errno();
631 goto done;
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();
639 goto done;
641 /*
642 * Path is not on disk.
643 * Assume it is already relative to repository root.
644 */
645 path = strdup(canonpath);
646 } else {
647 int is_repo_child = 0, is_cwd_child = 0;
649 path = realpath(canonpath, NULL);
650 if (path == NULL) {
651 err = got_error_from_errno();
652 goto done;
655 repolen = strlen(repo_abspath);
656 cwdlen = strlen(cwd);
657 len = strlen(path);
659 if (len > repolen && strncmp(path, repo_abspath, repolen) == 0)
660 is_repo_child = 1;
661 if (len > cwdlen && strncmp(path, cwd, cwdlen) == 0)
662 is_cwd_child = 1;
664 if (strcmp(path, repo_abspath) == 0) {
665 free(path);
666 path = strdup("");
667 if (path == NULL) {
668 err = got_error_from_errno();
669 goto done;
671 } else if (is_repo_child && is_cwd_child) {
672 char *child;
673 /* TODO: Is path inside a got worktree? */
674 /* Strip common prefix with repository path. */
675 err = got_path_skip_common_ancestor(&child,
676 repo_abspath, path);
677 if (err)
678 goto done;
679 free(path);
680 path = child;
681 } else if (is_repo_child) {
682 /* Matched an on-disk path inside repository. */
683 if (got_repo_is_bare(repo)) {
684 /*
685 * Matched an on-disk path inside repository
686 * database. Treat as repository-relative.
687 */
688 } else {
689 char *child;
690 /* Strip common prefix with repository path. */
691 err = got_path_skip_common_ancestor(&child,
692 repo_abspath, path);
693 if (err)
694 goto done;
695 free(path);
696 path = child;
698 } else if (is_cwd_child) {
699 char *child;
700 /* TODO: Is path inside a got worktree? */
701 /* Strip common prefix with cwd. */
702 err = got_path_skip_common_ancestor(&child, cwd,
703 path);
704 if (err)
705 goto done;
706 free(path);
707 path = child;
708 } else {
709 /*
710 * Matched unrelated on-disk path.
711 * Treat it as repository-relative.
712 */
716 /* Make in-repository path absolute */
717 if (path[0] != '/') {
718 char *abspath;
719 if (asprintf(&abspath, "/%s", path) == -1) {
720 err = got_error_from_errno();
721 goto done;
723 free(path);
724 path = abspath;
727 done:
728 free(repo_abspath);
729 free(cwd);
730 free(canonpath);
731 if (err)
732 free(path);
733 else
734 *in_repo_path = path;
735 return err;