Commit Diff


commit - 8fd174549d6af728ab316c727a527717d9fec756
commit + 86c3caaf12dbc42db6a36284eddfbbb5d243dc8f
blob - 63ff8ec7e3a9660476e5885e90590b8b63bc52bc
blob + 128e60e57bcaaebc470eebccc4ddd8948f7044d4
--- include/got_error.h
+++ include/got_error.h
@@ -39,6 +39,7 @@
 #define GOT_ERR_BAD_DELTA	21
 #define GOT_ERR_COMPRESSION	22
 #define GOT_ERR_BAD_OBJ_ID_STR	23
+#define GOT_ERR_WORKTREE_EXISTS	26
 
 static const struct got_error {
 	int code;
@@ -68,6 +69,7 @@ static const struct got_error {
 	{ GOT_ERR_BAD_DELTA,	"bad delta" },
 	{ GOT_ERR_COMPRESSION,	"compression failed" },
 	{ GOT_ERR_BAD_OBJ_ID_STR,"bad object id string" },
+	{ GOT_ERR_WORKTREE_EXISTS,"worktree already exists" },
 };
 
 const struct got_error * got_error(int code);
blob - e3f715f2cb0eed090fe9c7e42d360a3a6a75b4c2
blob + 9c30c84496b4423c3a5d3cb3282db636b246c4d3
--- include/got_refs.h
+++ include/got_refs.h
@@ -32,3 +32,4 @@ void got_ref_close(struct got_reference *);
 struct got_reference *got_ref_dup(struct got_reference *);
 const struct got_error *got_ref_resolve(struct got_object_id **,
     struct got_repository *, struct got_reference *);
+char *got_ref_to_str(struct got_reference *);
blob - 3ca9626ba044cf3a00cb587c9d36e9c37ee0f39e
blob + 96f1b71b451b24b056f5bb16b7cf978bfc6dc5e1
--- include/got_repository.h
+++ include/got_repository.h
@@ -20,6 +20,7 @@ struct got_repository;
 const struct got_error *got_repo_open(struct got_repository**, const char *);
 void got_repo_close(struct got_repository*);
 
+char *got_repo_get_path(struct got_repository *);
 char *got_repo_get_path_git_dir(struct got_repository *);
 char *got_repo_get_path_objects(struct got_repository *);
 char *got_repo_get_path_objects_pack(struct got_repository *);
blob - /dev/null
blob + efcffc833f19d3b45d8b089c5ce124a3e89c9fd8 (mode 644)
--- /dev/null
+++ include/got_worktree.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018 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.
+ */
+
+struct got_worktree;
+
+const struct got_error *got_worktree_init(const char *, struct got_reference *,
+    struct got_repository *);
+const struct got_error *got_worktree_open(struct got_worktree **, const char *);
+void got_worktree_close(struct got_worktree *);
+char *got_worktree_get_repo_path(struct got_worktree *);
+struct got_reference *got_worktree_get_head(struct got_worktree *);
+const struct got_error *got_worktree_set_head(struct got_worktree *,
+    struct got_reference *, struct got_repository *);
+const struct got_error *got_worktree_checkout_files(struct got_worktree *,
+    struct got_repository *);
blob - 3bca95b3c18a88dff7ba4f0da9738a1e898be44b
blob + 977e6fa24ee0d199aed7f2654832d0b74fc28e31
--- lib/got_path_priv.h
+++ lib/got_path_priv.h
@@ -16,6 +16,9 @@
 
 /* Utilities for dealing with filesystem paths. */
 
+#define GOT_DEFAULT_FILE_MODE	(S_IRUSR|S_IWUSR | S_IRGRP | S_IROTH)
+#define GOT_DEFAULT_DIR_MODE	(S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH)
+
 /* Determine whether a path is an absolute path. */
 int got_path_is_absolute(const char *);
 
blob - /dev/null
blob + 97fb6c888a7d319c81a0c1bc3f9c8158bed69ada (mode 644)
--- /dev/null
+++ lib/got_worktree_priv.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2018 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.
+ */
+
+struct got_worktree {
+	char *path_worktree_root;
+	char *path_repo;
+};
+
+#define GOT_WORKTREE_GOT_DIR	".got"
+#define GOT_WORKTREE_FILE_INDEX	"fileindex"
+#define GOT_WORKTREE_REPOSITORY	"repository"
blob - 718cf6189afb20d006aa4347bc2035355ab67e9c
blob + fe5dfd7dd99058d3effb1cd88e52dc14a2767ebb
--- lib/refs.c
+++ lib/refs.c
@@ -275,4 +275,22 @@ got_ref_resolve(struct got_object_id **id, struct got_
 		return got_error(GOT_ERR_NO_MEM);
 	memcpy((*id)->sha1, ref->ref.ref.sha1, SHA1_DIGEST_LENGTH);
 	return NULL;
+}
+
+char *
+got_ref_to_str(struct got_reference *ref)
+{
+	char *str;
+	if (ref->flags & GOT_REF_IS_SYMBOLIC) {
+		if (asprintf(&str, "ref: %s", ref->ref.symref.ref) == -1)
+			return NULL;
+	} else {
+		str = calloc(1, SHA1_DIGEST_STRING_LENGTH);
+		if (str == NULL)
+			return NULL;
+		str = got_sha1_digest_to_str(ref->ref.ref.sha1, str,
+		    SHA1_DIGEST_STRING_LENGTH);
+	}
+
+	return str;
 }
blob - 9037504a410bed587331d101a6cb0a65968e356a
blob + ac7b356c73e492aea73ce81178afb0ddd399972d
--- lib/repository.c
+++ lib/repository.c
@@ -51,6 +51,12 @@
 #define GOT_OBJECTS_PACK_DIR	"objects/pack"
 
 char *
+got_repo_get_path(struct got_repository *repo)
+{
+	return strdup(repo->path);
+}
+
+char *
 got_repo_get_path_git_dir(struct got_repository *repo)
 {
 	char *path_git;
blob - /dev/null
blob + 0a8cb93ba6e69957b761f1acb9c87b4aeb315c61 (mode 644)
--- /dev/null
+++ lib/worktree.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2018 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 <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "got_error.h"
+#include "got_repository.h"
+#include "got_refs.h"
+#include "got_worktree.h"
+
+#include "got_worktree_priv.h"
+#include "got_path_priv.h"
+
+const struct got_error *
+got_worktree_init(const char *path, struct got_reference *head_ref,
+    struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	char *abspath = NULL;
+	char *normpath = NULL;
+	char *gotpath = NULL;
+	char *indexpath = NULL;
+	char *headpath = NULL;
+	char *repopath = NULL;
+	char *refstr = NULL;
+	char *path_repos = NULL;
+	char buf[4];
+	ssize_t n;
+	int fd;
+
+	if (got_path_is_absolute(path)) {
+		abspath = strdup(path);
+		if (abspath == NULL)
+			return got_error(GOT_ERR_NO_MEM);
+	} else {
+		abspath = got_path_get_absolute(path);
+		if (abspath == NULL)
+			return got_error(GOT_ERR_BAD_PATH);
+	}
+
+	/* Create top-level directory (may already exist). */
+	normpath = got_path_normalize(abspath);
+	if (normpath == NULL) {
+		err = got_error(GOT_ERR_BAD_PATH);
+		goto done;
+	}
+	if (mkdir(normpath, GOT_DEFAULT_DIR_MODE) == -1 && errno != EEXIST) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	/* Create .got directory (may already exist). */
+	if (asprintf(&gotpath, "%s/%s", normpath, GOT_WORKTREE_GOT_DIR) == -1) {
+		err = got_error(GOT_ERR_NO_MEM);
+		goto done;
+	}
+	if (mkdir(gotpath, GOT_DEFAULT_DIR_MODE) == -1 && errno != EEXIST) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	/* Create an empty file index. */
+	if (asprintf(&indexpath, "%s/%s", gotpath, GOT_WORKTREE_FILE_INDEX)
+	    == -1) {
+		err = got_error(GOT_ERR_NO_MEM);
+		goto done;
+	}
+	fd = open(indexpath, O_RDWR | O_CREAT | O_EXCL | O_EXLOCK | O_NOFOLLOW,
+	    GOT_DEFAULT_FILE_MODE);
+	if (fd == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	n = read(fd, buf, sizeof(buf));
+	if (n != 0) {
+		err = (n == -1 ? got_error_from_errno() :
+		    got_error(GOT_ERR_WORKTREE_EXISTS));
+		close(fd);
+		goto done;
+	}
+	if (close(fd) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	/* Write the HEAD reference. */
+	refstr = got_ref_to_str(head_ref);
+	if (refstr == NULL) {
+		err = got_error(GOT_ERR_NO_MEM);
+		goto done;
+	}
+	if (asprintf(&headpath, "%s/%s", gotpath, GOT_REF_HEAD) == -1) {
+		err = got_error(GOT_ERR_NO_MEM);
+		goto done;
+	}
+	fd = open(headpath, O_RDWR | O_CREAT | O_EXCL | O_EXLOCK | O_NOFOLLOW,
+	    GOT_DEFAULT_FILE_MODE);
+	if (fd == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	n = read(fd, buf, sizeof(buf));
+	if (n != 0) {
+		err = (n == -1 ? got_error_from_errno() :
+		    got_error(GOT_ERR_WORKTREE_EXISTS));
+		close(fd);
+		goto done;
+	}
+	n = write(fd, refstr, strlen(refstr));
+	if (n != strlen(refstr)) {
+		err = got_error_from_errno();
+		close(fd);
+		goto done;
+	}
+	n = write(fd, "\n", 1);
+	if (n != 1) {
+		err = got_error_from_errno();
+		close(fd);
+		goto done;
+	}
+	if (close(fd) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	/* Store path to repository. */ 
+	if (asprintf(&repopath, "%s/%s", gotpath, GOT_WORKTREE_REPOSITORY) == -1) {
+		err = got_error(GOT_ERR_NO_MEM);
+		goto done;
+	}
+	fd = open(repopath, O_RDWR | O_CREAT | O_EXCL | O_EXLOCK | O_NOFOLLOW,
+	    GOT_DEFAULT_FILE_MODE);
+	if (fd == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	n = read(fd, buf, sizeof(buf));
+	if (n != 0) {
+		err = (n == -1 ? got_error_from_errno() :
+		    got_error(GOT_ERR_WORKTREE_EXISTS));
+		close(fd);
+		goto done;
+	}
+	path_repos = got_repo_get_path(repo);
+	if (path_repos == NULL) {
+		err = got_error(GOT_ERR_NO_MEM);
+		goto done;
+	}
+	n = write(fd, path_repos, strlen(path_repos));
+	if (n != strlen(path_repos)) {
+		err = got_error_from_errno();
+		close(fd);
+		goto done;
+	}
+	n = write(fd, "\n", 1);
+	if (n != 1) {
+		err = got_error_from_errno();
+		close(fd);
+		goto done;
+	}
+	if (close(fd) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+done:
+	free(abspath);
+	free(normpath);
+	free(gotpath);
+	free(indexpath);
+	free(headpath);
+	free(repopath);
+	free(refstr);
+	free(path_repos);
+	return err;
+}
+
+const struct got_error *
+got_worktree_open(struct got_worktree **worktree, const char *path)
+{
+	return NULL;
+}
+
+void
+got_worktree_close(struct got_worktree *worktree)
+{
+}
+
+char *
+got_worktree_get_repo_path(struct got_worktree *worktree)
+{
+	return strdup(worktree->path_repo);
+}
+
+struct got_reference *
+got_worktree_get_head(struct got_worktree *worktree)
+{
+	return NULL;
+}
+
+const struct got_error *
+got_worktree_set_head(struct got_worktree *worktree, struct got_reference *head,
+    struct got_repository *repo)
+{
+	return NULL;
+}
+
+const struct got_error *
+got_worktree_checkout_files(struct got_worktree *worktree,
+    struct got_repository *repo)
+{
+	return NULL;
+}
blob - /dev/null
blob + e10b20fba2f9e06be8895d91675f8fc91f3ff522 (mode 644)
--- /dev/null
+++ regress/worktree/Makefile
@@ -0,0 +1,14 @@
+.PATH:${.CURDIR}/../../lib
+
+PROG = worktree_test
+SRCS = worktree.c repository.c object.c path.c error.c refs.c sha1.c pack.c \
+	delta.c zb.c worktree_test.c
+
+CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
+LDADD = -lutil -lz
+DEBUG = -O0 -g
+CFLAGS += -Werror
+
+NOMAN = yes
+
+.include <bsd.regress.mk>
blob - /dev/null
blob + 252f9c9aa2f1eac333b88865699f7a17ad70aae3 (mode 644)
--- /dev/null
+++ regress/worktree/worktree_test.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2018 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/param.h>
+#include <sys/queue.h>
+#include <sys/limits.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "got_error.h"
+#include "got_object.h"
+#include "got_refs.h"
+#include "got_repository.h"
+#include "got_worktree.h"
+
+#include "got_worktree_priv.h"
+
+#define GOT_REPO_PATH "../../../"
+
+static int verbose;
+
+void
+test_printf(char *fmt, ...)
+{
+	va_list ap;
+
+	if (!verbose)
+		return;
+
+	va_start(ap, fmt);
+	vprintf(fmt, ap);
+	va_end(ap);
+}
+
+static int
+check_meta_file_exists(const char *worktree_path, const char *name)
+{
+	FILE *f;
+	char *path;
+
+	if (asprintf(&path, "%s/%s/%s", worktree_path, GOT_WORKTREE_GOT_DIR,
+	    name) == -1)
+		return 0;
+	f = fopen(path, "r");
+	if (f == NULL)
+		return 0;
+	fclose(f);
+	return 1;
+}
+
+static int
+worktree_init(const char *repo_path)
+{
+	const struct got_error *err;
+	struct got_repository *repo = NULL;
+	struct got_reference *head_ref = NULL;
+	char worktree_path[PATH_MAX];
+	int ok = 0;
+
+	err = got_repo_open(&repo, repo_path);
+	if (err != NULL || repo == NULL)
+		goto done;
+	err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
+	if (err != NULL || head_ref == NULL)
+		goto done;
+
+	strlcpy(worktree_path, "worktree-XXXXXX", sizeof(worktree_path));
+	if (mkdtemp(worktree_path) == NULL)
+		goto done;
+
+	err = got_worktree_init(worktree_path, head_ref, repo);
+	if (err != NULL)
+		goto done;
+
+	/* Ensure required files were created. */
+	if (!check_meta_file_exists(worktree_path, GOT_REF_HEAD))
+		goto done;
+	if (!check_meta_file_exists(worktree_path, GOT_WORKTREE_FILE_INDEX))
+		goto done;
+	if (!check_meta_file_exists(worktree_path, GOT_WORKTREE_REPOSITORY))
+		goto done;
+	ok = 1;
+done:
+	if (head_ref)
+		got_ref_close(head_ref);
+	if (repo)
+		got_repo_close(repo);
+	return ok;
+}
+
+#define RUN_TEST(expr, name) \
+	{ test_ok = (expr);  \
+	printf("test %s %s\n", (name), test_ok ? "ok" : "failed"); \
+	failure = (failure || !test_ok); }
+
+
+void
+usage(void)
+{
+	fprintf(stderr, "usage: worktree_test [-v] [REPO_PATH]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+	int test_ok = 0, failure = 0;
+	const char *repo_path;
+	int ch;
+	int vflag = 0;
+
+	while ((ch = getopt(argc, argv, "v")) != -1) {
+		switch (ch) {
+		case 'v':
+			verbose = 1;
+			break;
+		default:
+			usage();
+			return 1;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc == 0)
+		repo_path = GOT_REPO_PATH;
+	else if (argc == 1)
+		repo_path = argv[0];
+	else {
+		usage();
+		return 1;
+	}
+
+	RUN_TEST(worktree_init(repo_path), "init");
+
+	return failure ? 1 : 0;
+}