"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
Re: make gotadmin run in a work tree
To:
gameoftrees@openbsd.org
Date:
Sun, 14 Nov 2021 11:37:31 +0100

Download raw body.

Thread
On Sun, Nov 14, 2021 at 11:23:59AM +0100, Stefan Sperling wrote:
> gotweb benefits a little as well by shedding some weight, as in
> linking less unused worktree code into the binary.

Actually, gotweb doesn't need any worktree.c code at all. I have
removed worktree.c from its list of source files on the main branch.

Here is a rebased patch which ignores the gotweb side of things.

-----------------------------------------------
commit 1813a7c09b4ab5f28fa7c139625db5f4362ce61c (gotadmin-worktree)
from: Stefan Sperling <stsp@stsp.name>
date: Sun Nov 14 10:35:31 2021 UTC
 
 let gotadmin find the repository automatically if invoked in a work tree
 
 Move a small amount of code from worktree.c to a new file worktree_open.c,
 which contains everything required to open and close a work tree and inspect
 some of its basic parameters. This can be used by gotadmin.
 
diff d34cb66cb9f25f0f06b250b99d9aa3de97c3cb7d 9e8331df1736d9c4835ad8d2419d1fe9c3642215
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
@@ -118,70 +118,6 @@ done:
 }
 
 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 *
 write_head_ref(const char *path_got, struct got_reference *head_ref)
 {
 	const struct got_error *err = NULL;
@@ -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 \