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

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

Download raw body.

Thread
I am getting tired of typing the repository path whenever I need
to use a gotadmin command. With this patch gotadmin can be invoked
in a work tree and will use the corresponding repository, for each
command which accepts a "-r repository-path" option.

gotweb benefits a little as well by shedding some weight, as in
linking less unused worktree code into the binary.

ok?

-----------------------------------------------
commit b4020b016f3f0cced61db41a813cf53578cb23fe (gotadmin-worktree)
from: Stefan Sperling <stsp@stsp.name>
date: Sun Nov 14 10:12:24 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 and gotweb.
 
diff 8eec8e75ff7e815be1085681f6f4d76076fdc421 2ada9a0a2bf43de3d3a40b9adf107cba13ed4b0a
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 - b68c94d43465ffea4905be005ad51f1b0cd194a8
blob + 74070cda17d24e88383bdb4beaa59b87fd5f67ba
--- gotweb/Makefile
+++ gotweb/Makefile
@@ -9,7 +9,7 @@ PROG =		gotweb
 SRCS =		gotweb.c parse.y 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 \
+		privsep.c reference.c repository.c sha1.c worktree_open.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 \
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 \