Commit Diff


commit - 7cc76137bf43f435f12aa25c04316ca69c3b21cd
commit + 7d69d862a07866680ea64fcf8c30500f1f510243
blob - 4b9ae8539216e6a771ac12438cc2e7bb79a42ced
blob + 7e44f914b454fcd320fd03610f840109df3f7539
--- got/Makefile
+++ got/Makefile
@@ -7,7 +7,7 @@ SRCS=		got.c blame.c commit_graph.c delta.c diff.c \
 		diffreg.c error.c fileindex.c object.c object_cache.c \
 		object_idset.c object_parse.c opentemp.c path.c pack.c \
 		privsep.c reference.c repository.c sha1.c worktree.c \
-		inflate.c buf.c rcsutil.c diff3.c lockfile.c \
+		worktree_open.c inflate.c buf.c rcsutil.c diff3.c lockfile.c \
 		deflate.c object_create.c delta_cache.c fetch.c \
 		gotconfig.c diff_main.c diff_atomize_text.c \
 		diff_myers.c diff_output.c diff_output_plain.c \
blob - e40715b4c34c91855cc2dd46670ca0ba712541a6
blob + 47d9d105cc92076d9e33ac2abdf1adf89ab25dc9
--- gotadmin/Makefile
+++ gotadmin/Makefile
@@ -8,7 +8,7 @@ SRCS=		gotadmin.c \
 		inflate.c lockfile.c object.c object_cache.c object_create.c \
 		object_idset.c object_parse.c opentemp.c pack.c pack_create.c \
 		path.c privsep.c reference.c repository.c repository_admin.c \
-		sha1.c bloom.c murmurhash2.c
+		worktree_open.c sha1.c bloom.c murmurhash2.c
 MAN =		${PROG}.1
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
blob - 95a7aa49c8f72ab6c542ca943a37c5fc2cca1932
blob + f0ef0abde989951cff823fdaf07ad4a0528aca1f
--- gotadmin/gotadmin.1
+++ gotadmin/gotadmin.1
@@ -68,6 +68,9 @@ are as follows:
 Use the repository at the specified path.
 If not specified, assume the repository is located at or above the current
 working directory.
+If this directory is a
+.Xr got 1
+work tree, use the repository path associated with this work tree.
 .El
 .It Cm pack Oo Fl a Oc Oo Fl r Ar repository-path Oc Oo Fl x Ar reference Oc Op Ar reference ...
 Generate a new pack file and a corresponding pack file index.
@@ -101,6 +104,9 @@ Unless this option is specified, only loose objects wi
 Use the repository at the specified path.
 If not specified, assume the repository is located at or above the current
 working directory.
+If this directory is a
+.Xr got 1
+work tree, use the repository path associated with this work tree.
 .It Fl x Ar reference
 Exclude objects reachable via the specified
 .Ar reference
@@ -274,6 +280,9 @@ remove any files from disk.
 Use the repository at the specified path.
 If not specified, assume the repository is located at or above the current
 working directory.
+If this directory is a
+.Xr got 1
+work tree, use the repository path associated with this work tree.
 .It Fl q
 Suppress progress reporting and disk space summary output.
 .El
blob - b3c2e764d6748d711145b67ef6017d4d695e00b7
blob + 4a20c98ded30054a59e95d5cd5fc9f422d019da1
--- gotadmin/gotadmin.c
+++ gotadmin/gotadmin.c
@@ -42,6 +42,7 @@
 #include "got_path.h"
 #include "got_privsep.h"
 #include "got_opentemp.h"
+#include "got_worktree.h"
 
 #ifndef nitems
 #define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
@@ -232,10 +233,43 @@ usage_info(void)
 }
 
 static const struct got_error *
+get_repo_path(char **repo_path)
+{
+	const struct got_error *err = NULL;
+	struct got_worktree *worktree = NULL;
+	char *cwd;
+
+	*repo_path = NULL;
+
+	cwd = getcwd(NULL, 0);
+	if (cwd == NULL)
+		return got_error_from_errno("getcwd");
+
+	err = got_worktree_open(&worktree, cwd);
+	if (err) {
+		if (err->code != GOT_ERR_NOT_WORKTREE)
+			goto done;
+		err = NULL;
+	}
+
+	if (worktree)
+		*repo_path = strdup(got_worktree_get_repo_path(worktree));
+	else
+		*repo_path = strdup(cwd);
+	if (*repo_path == NULL)
+		err = got_error_from_errno("strdup");
+done:
+	if (worktree)
+		got_worktree_close(worktree);
+	free(cwd);
+	return err;
+}
+
+static const struct got_error *
 cmd_info(int argc, char *argv[])
 {
 	const struct got_error *error = NULL;
-	char *cwd = NULL, *repo_path = NULL;
+	char *repo_path = NULL;
 	struct got_repository *repo = NULL;
 	const struct got_gotconfig *gotconfig = NULL;
 	int ch, npackfiles, npackedobj, nobj;
@@ -265,13 +299,12 @@ cmd_info(int argc, char *argv[])
 	    NULL) == -1)
 		err(1, "pledge");
 #endif
-	cwd = getcwd(NULL, 0);
-	if (cwd == NULL) {
-		error = got_error_from_errno("getcwd");
-		goto done;
+	if (repo_path == NULL) {
+		error = get_repo_path(&repo_path);
+		if (error)
+			goto done;
 	}
-
-	error = got_repo_open(&repo, repo_path ? repo_path : cwd, NULL);
+	error = got_repo_open(&repo, repo_path, NULL);
 	if (error)
 		goto done;
 
@@ -333,7 +366,7 @@ cmd_info(int argc, char *argv[])
 done:
 	if (repo)
 		got_repo_close(repo);
-	free(cwd);
+	free(repo_path);
 	return error;
 }
 
@@ -521,7 +554,7 @@ static const struct got_error *
 cmd_pack(int argc, char *argv[])
 {
 	const struct got_error *error = NULL;
-	char *cwd = NULL, *repo_path = NULL;
+	char *repo_path = NULL;
 	struct got_repository *repo = NULL;
 	int ch, i, loose_obj_only = 1;
 	struct got_object_id *pack_hash = NULL;
@@ -571,13 +604,12 @@ cmd_pack(int argc, char *argv[])
 	    NULL) == -1)
 		err(1, "pledge");
 #endif
-	cwd = getcwd(NULL, 0);
-	if (cwd == NULL) {
-		error = got_error_from_errno("getcwd");
-		goto done;
+	if (repo_path == NULL) {
+		error = get_repo_path(&repo_path);
+		if (error)
+			goto done;
 	}
-
-	error = got_repo_open(&repo, repo_path ? repo_path : cwd, NULL);
+	error = got_repo_open(&repo, repo_path, NULL);
 	if (error)
 		goto done;
 
@@ -651,7 +683,7 @@ done:
 	got_ref_list_free(&include_refs);
 	free(id_str);
 	free(pack_hash);
-	free(cwd);
+	free(repo_path);
 	return error;
 }
 
@@ -1002,7 +1034,7 @@ static const struct got_error *
 cmd_cleanup(int argc, char *argv[])
 {
 	const struct got_error *error = NULL;
-	char *cwd = NULL, *repo_path = NULL;
+	char *repo_path = NULL;
 	struct got_repository *repo = NULL;
 	int ch, dry_run = 0, npacked = 0, verbosity = 0;
 	int remove_lonely_packidx = 0, ignore_mtime = 0;
@@ -1050,13 +1082,12 @@ cmd_cleanup(int argc, char *argv[])
 	    NULL) == -1)
 		err(1, "pledge");
 #endif
-	cwd = getcwd(NULL, 0);
-	if (cwd == NULL) {
-		error = got_error_from_errno("getcwd");
-		goto done;
+	if (repo_path == NULL) {
+		error = get_repo_path(&repo_path);
+		if (error)
+			goto done;
 	}
-
-	error = got_repo_open(&repo, repo_path ? repo_path : cwd, NULL);
+	error = got_repo_open(&repo, repo_path, NULL);
 	if (error)
 		goto done;
 
@@ -1121,6 +1152,6 @@ cmd_cleanup(int argc, char *argv[])
 done:
 	if (repo)
 		got_repo_close(repo);
-	free(cwd);
+	free(repo_path);
 	return error;
 }
blob - 8b445649ab78fb4dde26c6c0d79295a825c10599
blob + 31c6214885278c6568978931a3f60491140afca9
--- lib/worktree.c
+++ lib/worktree.c
@@ -114,70 +114,6 @@ done:
 	if (fclose(tmpfile) == EOF && err == NULL)
 		err = got_error_from_errno2("fclose", tmppath);
 	free(tmppath);
-	return err;
-}
-
-static const struct got_error *
-read_meta_file(char **content, const char *path_got, const char *name)
-{
-	const struct got_error *err = NULL;
-	char *path;
-	int fd = -1;
-	ssize_t n;
-	struct stat sb;
-
-	*content = NULL;
-
-	if (asprintf(&path, "%s/%s", path_got, name) == -1) {
-		err = got_error_from_errno("asprintf");
-		path = NULL;
-		goto done;
-	}
-
-	fd = open(path, O_RDONLY | O_NOFOLLOW);
-	if (fd == -1) {
-		if (errno == ENOENT)
-			err = got_error_path(path, GOT_ERR_WORKTREE_META);
-		else
-			err = got_error_from_errno2("open", path);
-		goto done;
-	}
-	if (flock(fd, LOCK_SH | LOCK_NB) == -1) {
-		err = (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY)
-		    : got_error_from_errno2("flock", path));
-		goto done;
-	}
-
-	if (fstat(fd, &sb) != 0) {
-		err = got_error_from_errno2("fstat", path);
-		goto done;
-	}
-	*content = calloc(1, sb.st_size);
-	if (*content == NULL) {
-		err = got_error_from_errno("calloc");
-		goto done;
-	}
-
-	n = read(fd, *content, sb.st_size);
-	if (n != sb.st_size) {
-		err = (n == -1 ? got_error_from_errno2("read", path) :
-		    got_error_path(path, GOT_ERR_WORKTREE_META));
-		goto done;
-	}
-	if ((*content)[sb.st_size - 1] != '\n') {
-		err = got_error_path(path, GOT_ERR_WORKTREE_META);
-		goto done;
-	}
-	(*content)[sb.st_size - 1] = '\0';
-
-done:
-	if (fd != -1 && close(fd) == -1 && err == NULL)
-		err = got_error_from_errno2("close", path_got);
-	free(path);
-	if (err) {
-		free(*content);
-		*content = NULL;
-	}
 	return err;
 }
 
@@ -320,226 +256,7 @@ done:
 	return err;
 }
 
-static const struct got_error *
-open_worktree(struct got_worktree **worktree, const char *path)
-{
-	const struct got_error *err = NULL;
-	char *path_got;
-	char *formatstr = NULL;
-	char *uuidstr = NULL;
-	char *path_lock = NULL;
-	char *base_commit_id_str = NULL;
-	int version, fd = -1;
-	const char *errstr;
-	struct got_repository *repo = NULL;
-	uint32_t uuid_status;
-
-	*worktree = NULL;
-
-	if (asprintf(&path_got, "%s/%s", path, GOT_WORKTREE_GOT_DIR) == -1) {
-		err = got_error_from_errno("asprintf");
-		path_got = NULL;
-		goto done;
-	}
-
-	if (asprintf(&path_lock, "%s/%s", path_got, GOT_WORKTREE_LOCK) == -1) {
-		err = got_error_from_errno("asprintf");
-		path_lock = NULL;
-		goto done;
-	}
-
-	fd = open(path_lock, O_RDWR | O_EXLOCK | O_NONBLOCK);
-	if (fd == -1) {
-		err = (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY)
-		    : got_error_from_errno2("open", path_lock));
-		goto done;
-	}
-
-	err = read_meta_file(&formatstr, path_got, GOT_WORKTREE_FORMAT);
-	if (err)
-		goto done;
-
-	version = strtonum(formatstr, 1, INT_MAX, &errstr);
-	if (errstr) {
-		err = got_error_msg(GOT_ERR_WORKTREE_META,
-		    "could not parse work tree format version number");
-		goto done;
-	}
-	if (version != GOT_WORKTREE_FORMAT_VERSION) {
-		err = got_error(GOT_ERR_WORKTREE_VERS);
-		goto done;
-	}
-
-	*worktree = calloc(1, sizeof(**worktree));
-	if (*worktree == NULL) {
-		err = got_error_from_errno("calloc");
-		goto done;
-	}
-	(*worktree)->lockfd = -1;
-
-	(*worktree)->root_path = realpath(path, NULL);
-	if ((*worktree)->root_path == NULL) {
-		err = got_error_from_errno2("realpath", path);
-		goto done;
-	}
-	err = read_meta_file(&(*worktree)->repo_path, path_got,
-	    GOT_WORKTREE_REPOSITORY);
-	if (err)
-		goto done;
-
-	err = read_meta_file(&(*worktree)->path_prefix, path_got,
-	    GOT_WORKTREE_PATH_PREFIX);
-	if (err)
-		goto done;
-
-	err = read_meta_file(&base_commit_id_str, path_got,
-	    GOT_WORKTREE_BASE_COMMIT);
-	if (err)
-		goto done;
-
-	err = read_meta_file(&uuidstr, path_got, GOT_WORKTREE_UUID);
-	if (err)
-		goto done;
-	uuid_from_string(uuidstr, &(*worktree)->uuid, &uuid_status);
-	if (uuid_status != uuid_s_ok) {
-		err = got_error_uuid(uuid_status, "uuid_from_string");
-		goto done;
-	}
-
-	err = got_repo_open(&repo, (*worktree)->repo_path, NULL);
-	if (err)
-		goto done;
-
-	err = got_object_resolve_id_str(&(*worktree)->base_commit_id, repo,
-	    base_commit_id_str);
-	if (err)
-		goto done;
-
-	err = read_meta_file(&(*worktree)->head_ref_name, path_got,
-	    GOT_WORKTREE_HEAD_REF);
-	if (err)
-		goto done;
-
-	if (asprintf(&(*worktree)->gotconfig_path, "%s/%s/%s",
-	    (*worktree)->root_path,
-	    GOT_WORKTREE_GOT_DIR, GOT_GOTCONFIG_FILENAME) == -1) {
-		err = got_error_from_errno("asprintf");
-		goto done;
-	}
-
-	err = got_gotconfig_read(&(*worktree)->gotconfig,
-	    (*worktree)->gotconfig_path);
-
-	(*worktree)->root_fd = open((*worktree)->root_path, O_DIRECTORY);
-	if ((*worktree)->root_fd == -1) {
-		err = got_error_from_errno2("open", (*worktree)->root_path);
-		goto done;
-	}
-done:
-	if (repo) {
-		const struct got_error *close_err = got_repo_close(repo);
-		if (err == NULL)
-			err = close_err;
-	}
-	free(path_got);
-	free(path_lock);
-	free(base_commit_id_str);
-	free(uuidstr);
-	free(formatstr);
-	if (err) {
-		if (fd != -1)
-			close(fd);
-		if (*worktree != NULL)
-			got_worktree_close(*worktree);
-		*worktree = NULL;
-	} else
-		(*worktree)->lockfd = fd;
-
-	return err;
-}
-
 const struct got_error *
-got_worktree_open(struct got_worktree **worktree, const char *path)
-{
-	const struct got_error *err = NULL;
-	char *worktree_path;
-
-	worktree_path = strdup(path);
-	if (worktree_path == NULL)
-		return got_error_from_errno("strdup");
-
-	for (;;) {
-		char *parent_path;
-
-		err = open_worktree(worktree, worktree_path);
-		if (err && !(err->code == GOT_ERR_ERRNO && errno == ENOENT)) {
-			free(worktree_path);
-			return err;
-		}
-		if (*worktree) {
-			free(worktree_path);
-			return NULL;
-		}
-		if (worktree_path[0] == '/' && worktree_path[1] == '\0')
-			break;
-		err = got_path_dirname(&parent_path, worktree_path);
-		if (err) {
-			if (err->code != GOT_ERR_BAD_PATH) {
-				free(worktree_path);
-				return err;
-			}
-			break;
-		}
-		free(worktree_path);
-		worktree_path = parent_path;
-	}
-
-	free(worktree_path);
-	return got_error(GOT_ERR_NOT_WORKTREE);
-}
-
-const struct got_error *
-got_worktree_close(struct got_worktree *worktree)
-{
-	const struct got_error *err = NULL;
-
-	if (worktree->lockfd != -1) {
-		if (close(worktree->lockfd) == -1)
-			err = got_error_from_errno2("close",
-			    got_worktree_get_root_path(worktree));
-	}
-	if (close(worktree->root_fd) == -1 && err == NULL)
-		err = got_error_from_errno2("close",
-		    got_worktree_get_root_path(worktree));
-	free(worktree->repo_path);
-	free(worktree->path_prefix);
-	free(worktree->base_commit_id);
-	free(worktree->head_ref_name);
-	free(worktree->root_path);
-	free(worktree->gotconfig_path);
-	got_gotconfig_free(worktree->gotconfig);
-	free(worktree);
-	return err;
-}
-
-const char *
-got_worktree_get_root_path(struct got_worktree *worktree)
-{
-	return worktree->root_path;
-}
-
-const char *
-got_worktree_get_repo_path(struct got_worktree *worktree)
-{
-	return worktree->repo_path;
-}
-const char *
-got_worktree_get_path_prefix(struct got_worktree *worktree)
-{
-	return worktree->path_prefix;
-}
-
-const struct got_error *
 got_worktree_match_path_prefix(int *match, struct got_worktree *worktree,
     const char *path_prefix)
 {
blob - /dev/null
blob + 4a589cf5ece62d780a9e4fac1215b2df6a5ea5cc (mode 644)
--- /dev/null
+++ lib/worktree_open.c
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2018, 2019, 2020 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/stat.h>
+#include <sys/queue.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <uuid.h>
+
+#include "got_cancel.h"
+#include "got_error.h"
+#include "got_reference.h"
+#include "got_path.h"
+#include "got_worktree.h"
+#include "got_repository.h"
+#include "got_gotconfig.h"
+#include "got_object.h"
+
+#include "got_lib_worktree.h"
+#include "got_lib_gotconfig.h"
+
+static const struct got_error *
+read_meta_file(char **content, const char *path_got, const char *name)
+{
+	const struct got_error *err = NULL;
+	char *path;
+	int fd = -1;
+	ssize_t n;
+	struct stat sb;
+
+	*content = NULL;
+
+	if (asprintf(&path, "%s/%s", path_got, name) == -1) {
+		err = got_error_from_errno("asprintf");
+		path = NULL;
+		goto done;
+	}
+
+	fd = open(path, O_RDONLY | O_NOFOLLOW);
+	if (fd == -1) {
+		if (errno == ENOENT)
+			err = got_error_path(path, GOT_ERR_WORKTREE_META);
+		else
+			err = got_error_from_errno2("open", path);
+		goto done;
+	}
+	if (flock(fd, LOCK_SH | LOCK_NB) == -1) {
+		err = (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY)
+		    : got_error_from_errno2("flock", path));
+		goto done;
+	}
+
+	if (fstat(fd, &sb) != 0) {
+		err = got_error_from_errno2("fstat", path);
+		goto done;
+	}
+	*content = calloc(1, sb.st_size);
+	if (*content == NULL) {
+		err = got_error_from_errno("calloc");
+		goto done;
+	}
+
+	n = read(fd, *content, sb.st_size);
+	if (n != sb.st_size) {
+		err = (n == -1 ? got_error_from_errno2("read", path) :
+		    got_error_path(path, GOT_ERR_WORKTREE_META));
+		goto done;
+	}
+	if ((*content)[sb.st_size - 1] != '\n') {
+		err = got_error_path(path, GOT_ERR_WORKTREE_META);
+		goto done;
+	}
+	(*content)[sb.st_size - 1] = '\0';
+
+done:
+	if (fd != -1 && close(fd) == -1 && err == NULL)
+		err = got_error_from_errno2("close", path_got);
+	free(path);
+	if (err) {
+		free(*content);
+		*content = NULL;
+	}
+	return err;
+}
+
+static const struct got_error *
+open_worktree(struct got_worktree **worktree, const char *path)
+{
+	const struct got_error *err = NULL;
+	char *path_got;
+	char *formatstr = NULL;
+	char *uuidstr = NULL;
+	char *path_lock = NULL;
+	char *base_commit_id_str = NULL;
+	int version, fd = -1;
+	const char *errstr;
+	struct got_repository *repo = NULL;
+	uint32_t uuid_status;
+
+	*worktree = NULL;
+
+	if (asprintf(&path_got, "%s/%s", path, GOT_WORKTREE_GOT_DIR) == -1) {
+		err = got_error_from_errno("asprintf");
+		path_got = NULL;
+		goto done;
+	}
+
+	if (asprintf(&path_lock, "%s/%s", path_got, GOT_WORKTREE_LOCK) == -1) {
+		err = got_error_from_errno("asprintf");
+		path_lock = NULL;
+		goto done;
+	}
+
+	fd = open(path_lock, O_RDWR | O_EXLOCK | O_NONBLOCK);
+	if (fd == -1) {
+		err = (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY)
+		    : got_error_from_errno2("open", path_lock));
+		goto done;
+	}
+
+	err = read_meta_file(&formatstr, path_got, GOT_WORKTREE_FORMAT);
+	if (err)
+		goto done;
+
+	version = strtonum(formatstr, 1, INT_MAX, &errstr);
+	if (errstr) {
+		err = got_error_msg(GOT_ERR_WORKTREE_META,
+		    "could not parse work tree format version number");
+		goto done;
+	}
+	if (version != GOT_WORKTREE_FORMAT_VERSION) {
+		err = got_error(GOT_ERR_WORKTREE_VERS);
+		goto done;
+	}
+
+	*worktree = calloc(1, sizeof(**worktree));
+	if (*worktree == NULL) {
+		err = got_error_from_errno("calloc");
+		goto done;
+	}
+	(*worktree)->lockfd = -1;
+
+	(*worktree)->root_path = realpath(path, NULL);
+	if ((*worktree)->root_path == NULL) {
+		err = got_error_from_errno2("realpath", path);
+		goto done;
+	}
+	err = read_meta_file(&(*worktree)->repo_path, path_got,
+	    GOT_WORKTREE_REPOSITORY);
+	if (err)
+		goto done;
+
+	err = read_meta_file(&(*worktree)->path_prefix, path_got,
+	    GOT_WORKTREE_PATH_PREFIX);
+	if (err)
+		goto done;
+
+	err = read_meta_file(&base_commit_id_str, path_got,
+	    GOT_WORKTREE_BASE_COMMIT);
+	if (err)
+		goto done;
+
+	err = read_meta_file(&uuidstr, path_got, GOT_WORKTREE_UUID);
+	if (err)
+		goto done;
+	uuid_from_string(uuidstr, &(*worktree)->uuid, &uuid_status);
+	if (uuid_status != uuid_s_ok) {
+		err = got_error_uuid(uuid_status, "uuid_from_string");
+		goto done;
+	}
+
+	err = got_repo_open(&repo, (*worktree)->repo_path, NULL);
+	if (err)
+		goto done;
+
+	err = got_object_resolve_id_str(&(*worktree)->base_commit_id, repo,
+	    base_commit_id_str);
+	if (err)
+		goto done;
+
+	err = read_meta_file(&(*worktree)->head_ref_name, path_got,
+	    GOT_WORKTREE_HEAD_REF);
+	if (err)
+		goto done;
+
+	if (asprintf(&(*worktree)->gotconfig_path, "%s/%s/%s",
+	    (*worktree)->root_path,
+	    GOT_WORKTREE_GOT_DIR, GOT_GOTCONFIG_FILENAME) == -1) {
+		err = got_error_from_errno("asprintf");
+		goto done;
+	}
+
+	err = got_gotconfig_read(&(*worktree)->gotconfig,
+	    (*worktree)->gotconfig_path);
+
+	(*worktree)->root_fd = open((*worktree)->root_path, O_DIRECTORY);
+	if ((*worktree)->root_fd == -1) {
+		err = got_error_from_errno2("open", (*worktree)->root_path);
+		goto done;
+	}
+done:
+	if (repo) {
+		const struct got_error *close_err = got_repo_close(repo);
+		if (err == NULL)
+			err = close_err;
+	}
+	free(path_got);
+	free(path_lock);
+	free(base_commit_id_str);
+	free(uuidstr);
+	free(formatstr);
+	if (err) {
+		if (fd != -1)
+			close(fd);
+		if (*worktree != NULL)
+			got_worktree_close(*worktree);
+		*worktree = NULL;
+	} else
+		(*worktree)->lockfd = fd;
+
+	return err;
+}
+
+const struct got_error *
+got_worktree_open(struct got_worktree **worktree, const char *path)
+{
+	const struct got_error *err = NULL;
+	char *worktree_path;
+
+	worktree_path = strdup(path);
+	if (worktree_path == NULL)
+		return got_error_from_errno("strdup");
+
+	for (;;) {
+		char *parent_path;
+
+		err = open_worktree(worktree, worktree_path);
+		if (err && !(err->code == GOT_ERR_ERRNO && errno == ENOENT)) {
+			free(worktree_path);
+			return err;
+		}
+		if (*worktree) {
+			free(worktree_path);
+			return NULL;
+		}
+		if (worktree_path[0] == '/' && worktree_path[1] == '\0')
+			break;
+		err = got_path_dirname(&parent_path, worktree_path);
+		if (err) {
+			if (err->code != GOT_ERR_BAD_PATH) {
+				free(worktree_path);
+				return err;
+			}
+			break;
+		}
+		free(worktree_path);
+		worktree_path = parent_path;
+	}
+
+	free(worktree_path);
+	return got_error(GOT_ERR_NOT_WORKTREE);
+}
+
+const struct got_error *
+got_worktree_close(struct got_worktree *worktree)
+{
+	const struct got_error *err = NULL;
+
+	if (worktree->lockfd != -1) {
+		if (close(worktree->lockfd) == -1)
+			err = got_error_from_errno2("close",
+			    got_worktree_get_root_path(worktree));
+	}
+	if (close(worktree->root_fd) == -1 && err == NULL)
+		err = got_error_from_errno2("close",
+		    got_worktree_get_root_path(worktree));
+	free(worktree->repo_path);
+	free(worktree->path_prefix);
+	free(worktree->base_commit_id);
+	free(worktree->head_ref_name);
+	free(worktree->root_path);
+	free(worktree->gotconfig_path);
+	got_gotconfig_free(worktree->gotconfig);
+	free(worktree);
+	return err;
+}
+
+const char *
+got_worktree_get_root_path(struct got_worktree *worktree)
+{
+	return worktree->root_path;
+}
+
+const char *
+got_worktree_get_repo_path(struct got_worktree *worktree)
+{
+	return worktree->repo_path;
+}
+
+const char *
+got_worktree_get_path_prefix(struct got_worktree *worktree)
+{
+	return worktree->path_prefix;
+}
blob - f7072d7a4b02817f653b795674c32ae7feabcc0b
blob + ba79d5e787ada9939dea4f62aae062cea501f845
--- tog/Makefile
+++ tog/Makefile
@@ -7,7 +7,7 @@ SRCS=		tog.c blame.c commit_graph.c delta.c diff.c \
 		diffreg.c error.c fileindex.c object.c object_cache.c \
 		object_idset.c object_parse.c opentemp.c path.c pack.c \
 		privsep.c reference.c repository.c sha1.c worktree.c \
-		utf8.c inflate.c buf.c rcsutil.c diff3.c \
+		worktree_open.c utf8.c inflate.c buf.c rcsutil.c diff3.c \
 		lockfile.c deflate.c object_create.c delta_cache.c \
 		gotconfig.c diff_main.c diff_atomize_text.c \
 		diff_myers.c diff_output.c diff_output_plain.c \