Commit Diff


commit - dc2404d9e5211dd2c472c2bc600b720eb526a88e
commit + 9d31a1d8a43396fe66a94b1110e395d134bebf3f
blob - 282259e175723a16c57488d07a0e3a909a7d857c
blob + e8a9594dc6bfdc9057b7fabc1fcfcda43b0ea4af
--- include/got_error.h
+++ include/got_error.h
@@ -43,6 +43,8 @@
 #define GOT_ERR_WORKTREE_META	27
 #define GOT_ERR_WORKTREE_VERS	28
 #define GOT_ERR_WORKTREE_BUSY	29
+#define GOT_ERR_DIR_OBSTRUCTED	30
+#define GOT_ERR_FILE_OBSTRUCTED	31
 
 static const struct got_error {
 	int code;
blob - c872b92c3edc17bc19fb55ee253e8da7d74d40f0
blob + 3bfc19180c2887cfa564c03866b4f69599e3d7cc
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -23,4 +23,4 @@ void got_worktree_close(struct got_worktree *);
 char *got_worktree_get_repo_path(struct got_worktree *);
 char  *got_worktree_get_head_ref_name(struct got_worktree *);
 const struct got_error *got_worktree_checkout_files(struct got_worktree *,
-    struct got_repository *);
+    struct got_reference *, struct got_repository *);
blob - 6430af102b5eb0b9f230bf8989fd1606ad27ca06
blob + 85fdbb17fe65945c8f9d5c7e0ae4996976cd62b6
--- lib/fileindex.c
+++ lib/fileindex.c
@@ -75,3 +75,44 @@ got_fileindex_entry_close(struct got_fileindex_entry *
 	free(entry->path);
 	free(entry);
 }
+
+const struct got_error *
+got_fileindex_entry_add(struct got_fileindex *fileindex,
+    struct got_fileindex_entry *entry)
+{
+	/* TODO keep entries sorted by name */
+	TAILQ_INSERT_TAIL(&fileindex->entries, entry, entry);
+	fileindex->nentries++;
+	return NULL;
+}
+
+struct got_fileindex *
+got_fileindex_open(void)
+{
+	struct got_fileindex *fileindex;
+
+	fileindex = calloc(1, sizeof(*fileindex));
+	if (fileindex)
+		TAILQ_INIT(&fileindex->entries);
+	return fileindex;
+}
+
+void
+got_fileindex_close(struct got_fileindex *fileindex)
+{
+	struct got_fileindex_entry *entry;
+
+	while (!TAILQ_EMPTY(&fileindex->entries)) {
+		entry = TAILQ_FIRST(&fileindex->entries);
+		TAILQ_REMOVE(&fileindex->entries, entry, entry);
+		got_fileindex_entry_close(entry);
+		fileindex->nentries--;
+	}
+	free(fileindex);
+}
+
+const struct got_error *
+got_fileindex_write(struct got_fileindex *fileindex, FILE *outfile)
+{
+	return NULL;
+}
blob - d7cf180ed2d58a007aaf610c8d2445e337a2c687
blob + 546f7ddd279eba8e6792b2ea8e126f4bc0d3d40b
--- lib/got_fileindex_lib.h
+++ lib/got_fileindex_lib.h
@@ -84,3 +84,8 @@ struct got_fileindex_hdr {
 const struct got_error *got_fileindex_entry_open(struct got_fileindex_entry **,
     const char *, uint8_t *);
 void got_fileindex_entry_close(struct got_fileindex_entry *);
+struct got_fileindex *got_fileindex_open(void);
+void got_fileindex_close(struct got_fileindex *);
+const struct got_error *got_fileindex_write(struct got_fileindex *, FILE *);
+const struct got_error *got_fileindex_entry_add(struct got_fileindex *,
+    struct got_fileindex_entry *);
blob - 977e6fa24ee0d199aed7f2654832d0b74fc28e31
blob + 53b63eff8f31d9fb8abb59dc9c33972ed6d43060
--- lib/got_path_lib.h
+++ lib/got_path_lib.h
@@ -37,3 +37,11 @@ char *got_path_normalize(const char *);
 /* Open a new temporary file for writing.
  * The file is not visible in the filesystem. */
 FILE *got_opentemp(void);
+
+/* Open a new temporary file for writing.
+ * The file is visible in the filesystem. */
+const struct got_error *got_opentemp_named(char **, FILE **, const char *);
+
+/* Count the number of path segments separated by '/'. */
+const struct got_error *
+got_path_segment_count(int *count, const char *path);
blob - 021720b277109368e9c0ec2b3f66fafaed364f0e
blob + 9f0d7007ce89105d2222d28c50ae1edf94c03821
--- lib/path.c
+++ lib/path.c
@@ -20,6 +20,8 @@
 #include <stdio.h>
 #include <string.h>
 
+#include "got_error.h"
+
 #include "got_path_lib.h"
 
 int
@@ -61,6 +63,26 @@ got_path_normalize(const char *path)
 	return resolved;
 }
 
+const struct got_error *
+got_path_segment_count(int *count, const char *path)
+{
+	int n = 0;
+	char *s = strdup(path), *p;
+
+	*count = 0;
+
+	if (s == NULL)
+		return got_error(GOT_ERR_NO_MEM);
+
+	do {
+		p = strsep(&s, "/");
+		if (s && *s != '/')
+			(*count)++;
+	} while (p);
+
+	return NULL;
+}
+
 FILE *
 got_opentemp(void)
 {
@@ -84,3 +106,32 @@ got_opentemp(void)
 
 	return f;
 }
+
+const struct got_error *
+got_opentemp_named(char **path, FILE **outfile, const char *basepath)
+{
+	const struct got_error *err = NULL;
+	int fd, ret;
+
+	if (asprintf(path, "%s-XXXXXX", basepath) == -1) {
+		*path = NULL;
+		return got_error(GOT_ERR_NO_MEM);
+	}
+
+	fd = mkstemp(*path);
+	if (fd == -1) {
+		err = got_error_from_errno();
+		free(*path);
+		*path = NULL;
+		return err;
+	}
+
+	*outfile = fdopen(fd, "w+");
+	if (*outfile == NULL) {
+		err = got_error_from_errno();
+		free(*path);
+		*path = NULL;
+	}
+
+	return err;
+}
blob - b7998d0623d8b7f3a44f6d0962e19ba8064c496c
blob + c8140c77f4fb12393cc353436d2ef004276aae3c
--- lib/worktree.c
+++ lib/worktree.c
@@ -16,6 +16,7 @@
 
 #include <sys/stat.h>
 #include <sys/limits.h>
+#include <sys/queue.h>
 
 #include <string.h>
 #include <stdio.h>
@@ -23,15 +24,27 @@
 #include <fcntl.h>
 #include <errno.h>
 #include <unistd.h>
+#include <sha1.h>
+#include <zlib.h>
+#include <fnmatch.h>
 
 #include "got_error.h"
 #include "got_repository.h"
 #include "got_refs.h"
+#include "got_object.h"
 #include "got_worktree.h"
 
 #include "got_worktree_lib.h"
 #include "got_path_lib.h"
 #include "got_sha1_lib.h"
+#include "got_fileindex_lib.h"
+#include "got_zbuf_lib.h"
+#include "got_delta_lib.h"
+#include "got_object_lib.h"
+
+#ifndef MIN
+#define	MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
+#endif
 
 static const struct got_error *
 create_meta_file(const char *path_got, const char *name, const char *content)
@@ -340,9 +353,313 @@ got_worktree_get_head_ref_name(struct got_worktree *wo
 	return strdup(worktree->head_ref);
 }
 
-const struct got_error *
-got_worktree_checkout_files(struct got_worktree *worktree,
-    struct got_repository *repo)
+static const struct got_error *
+lock_worktree(struct got_worktree *worktree, int operation)
 {
+	if (flock(worktree->lockfd, operation | LOCK_NB) == -1)
+		return (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY)
+		    : got_error_from_errno());
 	return NULL;
 }
+
+static const char *
+apply_path_prefix(struct got_worktree *worktree, const char *path)
+{
+	const char *p = path;
+	p += strlen(worktree->path_prefix);
+	if (*p == '/')
+		p++;
+	return p;
+}
+
+static const struct got_error *
+add_file_on_disk(struct got_worktree *worktree, struct got_fileindex *fileindex,
+   const char *path, struct got_blob_object *blob, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	char *abspath;
+	int fd;
+	size_t len, hdrlen;
+	struct got_fileindex_entry *entry;
+
+	if (asprintf(&abspath, "%s/%s", worktree->root_path,
+	    apply_path_prefix(worktree, path)) == -1)
+		return got_error(GOT_ERR_NO_MEM);
+
+	fd = open(abspath, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
+	    GOT_DEFAULT_FILE_MODE);
+	if (fd == -1) {
+		err = got_error_from_errno();
+		if (errno == EEXIST) {
+			struct stat sb;
+			if (lstat(abspath, &sb) == -1) {
+				err = got_error_from_errno();
+			} else if (!S_ISREG(sb.st_mode)) {
+				/* TODO file is obstructed; do something */
+				err = got_error(GOT_ERR_FILE_OBSTRUCTED);
+			}
+		}
+		return err;
+	}
+
+	hdrlen = got_object_blob_get_hdrlen(blob);
+	do {
+		const uint8_t *buf = got_object_blob_get_read_buf(blob);
+		err = got_object_blob_read_block(&len, blob);
+		if (err)
+			break;
+		if (len > 0) {
+			/* Skip blob object header first time around. */
+			ssize_t outlen = write(fd, buf + hdrlen, len - hdrlen);
+			hdrlen = 0;
+			if (outlen == -1) {
+				err = got_error_from_errno();
+				break;
+			} else if (outlen != len) {
+				err = got_error(GOT_ERR_IO);
+				break;
+			}
+		}
+	} while (len != 0);
+
+	fsync(fd);
+
+	err = got_fileindex_entry_open(&entry, abspath, blob->id.sha1);
+	if (err)
+		goto done;
+
+	err = got_fileindex_entry_add(fileindex, entry);
+	if (err)
+		goto done;
+done:
+	close(fd);
+	free(abspath);
+	return err;
+}
+
+static const struct got_error *
+add_dir_on_disk(struct got_worktree *worktree, const char *path)
+{
+	const struct got_error *err = NULL;
+	char *abspath;
+	size_t len;
+
+	if (asprintf(&abspath, "%s/%s", worktree->root_path,
+	    apply_path_prefix(worktree, path)) == -1)
+		return got_error(GOT_ERR_NO_MEM);
+
+	/* XXX queue work rather than editing disk directly? */
+	if (mkdir(abspath, GOT_DEFAULT_DIR_MODE) == -1) {
+		struct stat sb;
+
+		if (errno != EEXIST) {
+			err = got_error_from_errno();
+			goto done;
+		}
+
+		if (lstat(abspath, &sb) == -1) {
+			err = got_error_from_errno();
+			goto done;
+		}
+
+		if (!S_ISDIR(sb.st_mode)) {
+			/* TODO directory is obstructed; do something */
+			return got_error(GOT_ERR_FILE_OBSTRUCTED);
+		}
+	}
+
+done:
+	free(abspath);
+	return err;
+}
+
+static const struct got_error *
+tree_checkout(struct got_worktree *, struct got_fileindex *,
+    struct got_tree_object *, const char *, struct got_repository *);
+
+static const struct got_error *
+tree_checkout_entry(struct got_worktree *worktree,
+    struct got_fileindex *fileindex, struct got_tree_entry *te,
+    const char *parent, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct got_object *obj = NULL;
+	struct got_blob_object *blob = NULL;
+	struct got_tree_object *tree = NULL;
+	char *path = NULL;
+	size_t len;
+
+	if (parent[0] == '/' && parent[1] == '\0')
+		parent = "";
+	if (asprintf(&path, "%s/%s", parent, te->name) == -1)
+		return got_error(GOT_ERR_NO_MEM);
+
+	/* Skip this entry if it is outside of our path prefix. */
+	len = MIN(strlen(worktree->path_prefix), strlen(path));
+	if (strncmp(path, worktree->path_prefix, len) != 0) {
+		free(path);
+		return NULL;
+	}
+
+	err = got_object_open(&obj, repo, te->id);
+	if (err)
+		goto done;
+
+	switch (got_object_get_type(obj)) {
+	case GOT_OBJ_TYPE_BLOB:
+		if (strlen(worktree->path_prefix) >= strlen(path))
+			break;
+		err = got_object_blob_open(&blob, repo, obj, 8192);
+		if (err)
+			goto done;
+		err = add_file_on_disk(worktree, fileindex, path, blob, repo);
+		break;
+	case GOT_OBJ_TYPE_TREE:
+		err = got_object_tree_open(&tree, repo, obj);
+		if (err)
+			goto done;
+		if (strlen(worktree->path_prefix) < strlen(path)) {
+			err = add_dir_on_disk(worktree, path);
+			if (err)
+				break;
+		}
+		err = tree_checkout(worktree, fileindex, tree, path, repo);
+		break;
+	default:
+		break;
+	}
+
+done:
+	if (blob)
+		got_object_blob_close(blob);
+	if (tree)
+		got_object_tree_close(tree);
+	if (obj)
+		got_object_close(obj);
+	free(path);
+	return err;
+}
+
+static const struct got_error *
+tree_checkout(struct got_worktree *worktree,
+    struct got_fileindex *fileindex, struct got_tree_object *tree,
+    const char *path, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct got_tree_entry *te;
+	size_t len;
+
+	/* Skip this tree if it is outside of our path prefix. */
+	len = MIN(strlen(worktree->path_prefix), strlen(path));
+	if (strncmp(path, worktree->path_prefix, len) != 0)
+		return NULL;
+
+	SIMPLEQ_FOREACH(te, &tree->entries, entry) {
+		err = tree_checkout_entry(worktree, fileindex, te, path, repo);
+		if (err)
+			break;
+	}
+
+	return err;
+}
+
+const struct got_error *
+got_worktree_checkout_files(struct got_worktree *worktree,
+    struct got_reference *head_ref, struct got_repository *repo)
+{
+	const struct got_error *err = NULL, *unlockerr;
+	struct got_object_id *commit_id = NULL;
+	struct got_object *obj = NULL;
+	struct got_commit_object *commit = NULL;
+	struct got_tree_object *tree = NULL;
+	char *fileindex_path = NULL, *new_fileindex_path = NULL;
+	struct got_fileindex *fileindex = NULL;
+	FILE *findex = NULL;
+
+	err = lock_worktree(worktree, LOCK_EX);
+	if (err)
+		return err;
+
+	fileindex = got_fileindex_open();
+	if (fileindex == NULL) {
+		err = got_error(GOT_ERR_NO_MEM);
+		goto done;
+	}
+
+	err = got_opentemp_named(&new_fileindex_path, &findex, fileindex_path);
+	if (err)
+		goto done;
+
+
+	if (asprintf(&fileindex_path, "%s/%s/%s", worktree->root_path,
+	    GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_FILE_INDEX) == -1) {
+		err = got_error(GOT_ERR_NO_MEM);
+		fileindex_path = NULL;
+		goto done;
+	}
+
+	err = got_ref_resolve(&commit_id, repo, head_ref);
+	if (err)
+		goto done;
+
+	err = got_object_open(&obj, repo, commit_id);
+	if (err)
+		goto done;
+
+	if (got_object_get_type(obj) != GOT_OBJ_TYPE_COMMIT) {
+		err = got_error(GOT_ERR_OBJ_TYPE);
+		goto done;
+	}
+
+	err = got_object_commit_open(&commit, repo, obj);
+	if (err)
+		goto done;
+
+	got_object_close(obj);
+	err = got_object_open(&obj, repo, commit->tree_id);
+	if (err)
+		goto done;
+
+	if (got_object_get_type(obj) != GOT_OBJ_TYPE_TREE) {
+		err = got_error(GOT_ERR_OBJ_TYPE);
+		goto done;
+	}
+
+	err = got_object_tree_open(&tree, repo, obj);
+	if (err)
+		goto done;
+
+	err = tree_checkout(worktree, fileindex, tree, "/", repo);
+	if (err)
+		goto done;
+
+	err = got_fileindex_write(fileindex, findex);
+	if (err)
+		goto done;
+
+	if (rename(new_fileindex_path, fileindex_path) != 0) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	free(new_fileindex_path);
+	new_fileindex_path = NULL;
+
+done:
+	if (commit)
+		got_object_commit_close(commit);
+	if (obj)
+		got_object_close(obj);
+	free(commit_id);
+	if (new_fileindex_path)
+		unlink(new_fileindex_path);
+	if (findex)
+		fclose(findex);
+	free(new_fileindex_path);
+	free(fileindex_path);
+	got_fileindex_close(fileindex);
+	unlockerr = lock_worktree(worktree, LOCK_SH);
+	if (unlockerr && err == NULL)
+		err = unlockerr;
+	return err;
+}
blob - 1bde89a4abe6550a5bbfc2ae3171877410b724ab
blob + ecc33b7be943945e294ef8d5d41fdc426d07ed4c
--- regress/worktree/worktree_test.c
+++ regress/worktree/worktree_test.c
@@ -303,7 +303,75 @@ done:
 		remove_worktree(worktree_path);
 	return (ok == 7);
 }
+
+static int
+worktree_checkout(const char *repo_path)
+{
+	const struct got_error *err;
+	struct got_repository *repo = NULL;
+	struct got_reference *head_ref = NULL;
+	struct got_worktree *worktree = NULL;
+	char *makefile_path = NULL, *cfile_path = NULL;
+	char worktree_path[PATH_MAX];
+	int ok = 0;
+	struct stat sb;
+
+	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, "/regress/worktree",
+	    repo);
+	if (err != NULL)
+		goto done;
+
+	err = got_worktree_open(&worktree, worktree_path);
+	if (err != NULL)
+		goto done;
+
+	err = got_worktree_checkout_files(worktree, head_ref, repo);
+	if (err != NULL)
+		goto done;
+
+	test_printf("checked out %s\n", worktree_path);
+
+	/* The work tree should contain a Makefile and worktree_test.c. */
+	if (asprintf(&makefile_path, "%s/Makefile", worktree_path) == -1)
+		goto done;
+	if (stat(makefile_path, &sb) != 0)
+		goto done;
+	else
+		unlink(makefile_path);
+	if (asprintf(&cfile_path, "%s/worktree_test.c", worktree_path) == -1)
+		goto done;
+	if (stat(cfile_path, &sb) != 0)
+		goto done;
+	else
+		unlink(cfile_path);
+
+	if (!remove_worktree(worktree_path))
+		goto done;
+
+	ok = 1;
+done:
+	if (worktree)
+		got_worktree_close(worktree);
+	if (head_ref)
+		got_ref_close(head_ref);
+	if (repo)
+		got_repo_close(repo);
+	free(makefile_path);
+	free(cfile_path);
+	return ok;
+}
+
 #define RUN_TEST(expr, name) \
 	{ test_ok = (expr);  \
 	printf("test %s %s\n", (name), test_ok ? "ok" : "failed"); \
@@ -348,6 +416,7 @@ main(int argc, char *argv[])
 
 	RUN_TEST(worktree_init(repo_path), "init");
 	RUN_TEST(worktree_init_exists(repo_path), "init exists");
+	RUN_TEST(worktree_checkout(repo_path), "checkout");
 
 	return failure ? 1 : 0;
 }