Commit 0cd1c46a9e4b3e3a7c7f2947c3eded6fb5439516

Stefan Sperling 2019-03-11T18:07:33

create references to base commits of got worktrees

diff --git a/got/got.c b/got/got.c
index 0e10c1f..ef9b7d3 100644
--- a/got/got.c
+++ b/got/got.c
@@ -364,7 +364,7 @@ cmd_checkout(int argc, char *argv[])
 	} else
 		usage_checkout();
 
-	error = apply_unveil(repo_path, 1, worktree_path);
+	error = apply_unveil(repo_path, 0, worktree_path);
 	if (error)
 		goto done;
 
diff --git a/lib/got_lib_path.h b/lib/got_lib_path.h
index 1d766a0..f28495f 100644
--- a/lib/got_lib_path.h
+++ b/lib/got_lib_path.h
@@ -86,3 +86,6 @@ const struct got_error *got_pathlist_insert(struct got_pathlist_entry **,
 
 /* Free resources allocated for a path list. */
 void got_pathlist_free(struct got_pathlist_head *);
+
+/* Attempt to create a directory at a given path. */
+const struct got_error *got_path_mkdir(const char *);
diff --git a/lib/got_lib_worktree.h b/lib/got_lib_worktree.h
index a8227e2..44e57df 100644
--- a/lib/got_lib_worktree.h
+++ b/lib/got_lib_worktree.h
@@ -47,3 +47,5 @@ struct got_worktree {
 
 #define GOT_WORKTREE_FORMAT_VERSION	1
 #define GOT_WORKTREE_INVALID_COMMIT_ID	GOT_SHA1_STRING_ZERO
+
+#define GOT_WORKTREE_BASE_REF_PREFIX "got/worktree-base"
diff --git a/lib/path.c b/lib/path.c
index 8deccf4..e4c1921 100644
--- a/lib/path.c
+++ b/lib/path.c
@@ -16,8 +16,11 @@
  */
 
 #include <sys/queue.h>
+#include <sys/stat.h>
 
+#include <errno.h>
 #include <limits.h>
+#include <libgen.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <stdio.h>
@@ -265,3 +268,61 @@ got_pathlist_free(struct got_pathlist_head *pathlist)
 		free(pe);
 	}
 }
+
+static const struct got_error *
+make_parent_dirs(const char *abspath)
+{
+	const struct got_error *err = NULL;
+
+	char *parent = dirname(abspath);
+	if (parent == NULL)
+		return NULL;
+
+	if (mkdir(parent, GOT_DEFAULT_DIR_MODE) == -1) {
+		if (errno == ENOENT) {
+			err = make_parent_dirs(parent);
+			if (err)
+				return err;
+			if (mkdir(parent, GOT_DEFAULT_DIR_MODE) == -1)
+				return got_error_from_errno();
+		} else
+			err = got_error_from_errno();
+	}
+
+	return err;
+}
+
+const struct got_error *
+got_path_mkdir(const char *abspath)
+{
+	const struct got_error *err = NULL;
+
+	if (mkdir(abspath, GOT_DEFAULT_DIR_MODE) == -1) {
+		struct stat sb;
+
+		if (errno == EEXIST) {
+			if (lstat(abspath, &sb) == -1) {
+				err = got_error_from_errno();
+				goto done;
+			}
+
+			if (!S_ISDIR(sb.st_mode)) {
+				/* TODO directory is obstructed; do something */
+				err = got_error(GOT_ERR_FILE_OBSTRUCTED);
+				goto done;
+			}
+
+			return NULL;
+		} else if (errno == ENOENT) {
+			err = make_parent_dirs(abspath);
+			if (err)
+				goto done;
+			if (mkdir(abspath, GOT_DEFAULT_DIR_MODE) == -1)
+				err = got_error_from_errno();
+		} else
+			err = got_error_from_errno();
+	}
+
+done:
+	return err;
+}
diff --git a/lib/reference.c b/lib/reference.c
index f66bd47..0dd31b2 100644
--- a/lib/reference.c
+++ b/lib/reference.c
@@ -28,6 +28,7 @@
 #include <util.h>
 #include <zlib.h>
 #include <time.h>
+#include <libgen.h>
 
 #include "got_error.h"
 #include "got_object.h"
@@ -741,8 +742,24 @@ got_ref_write(struct got_reference *ref, struct got_repository *repo)
 
 	err = got_opentemp_named(&tmppath, &f, path);
 	if (f == NULL) {
-		err = got_error_from_errno();
-		goto done;
+		char *parent;
+		if (errno != ENOENT) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		parent = dirname(path);
+		if (parent == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		err = got_path_mkdir(parent);
+		if (err)
+			goto done;
+		err = got_opentemp_named(&tmppath, &f, path);
+		if (f == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
 	}
 
 	if (ref->flags & GOT_REF_IS_SYMBOLIC) {
@@ -771,9 +788,12 @@ got_ref_write(struct got_reference *ref, struct got_repository *repo)
 
 	/* XXX: check if old content matches our expectations? */
 
-	if (stat(path, &sb) != 0 && errno != ENOENT) {
-		err = got_error_from_errno();
-		goto done;
+	if (stat(path, &sb) != 0) {
+		if (errno != ENOENT) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		sb.st_mode = GOT_DEFAULT_FILE_MODE;
 	}
 
 	if (rename(tmppath, path) != 0) {
diff --git a/lib/worktree.c b/lib/worktree.c
index c31c617..b42d708 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -369,7 +369,7 @@ open_worktree(struct got_worktree **worktree, const char *path)
 	}
 	(*worktree)->lockfd = -1;
 
-	(*worktree)->root_path = strdup(path);
+	(*worktree)->root_path = realpath(path, NULL);
 	if ((*worktree)->root_path == NULL) {
 		err = got_error_from_errno();
 		goto done;
@@ -578,29 +578,6 @@ lock_worktree(struct got_worktree *worktree, int operation)
 }
 
 static const struct got_error *
-make_parent_dirs(const char *abspath)
-{
-	const struct got_error *err = NULL;
-
-	char *parent = dirname(abspath);
-	if (parent == NULL)
-		return NULL;
-
-	if (mkdir(parent, GOT_DEFAULT_DIR_MODE) == -1) {
-		if (errno == ENOENT) {
-			err = make_parent_dirs(parent);
-			if (err)
-				return err;
-			if (mkdir(parent, GOT_DEFAULT_DIR_MODE) == -1)
-				return got_error_from_errno();
-		} else
-			err = got_error_from_errno();
-	}
-
-	return err;
-}
-
-static const struct got_error *
 add_dir_on_disk(struct got_worktree *worktree, const char *path)
 {
 	const struct got_error *err = NULL;
@@ -609,34 +586,7 @@ add_dir_on_disk(struct got_worktree *worktree, const char *path)
 	if (asprintf(&abspath, "%s/%s", worktree->root_path, path) == -1)
 		return got_error_from_errno();
 
-	/* XXX queue work rather than editing disk directly? */
-	if (mkdir(abspath, GOT_DEFAULT_DIR_MODE) == -1) {
-		struct stat sb;
-
-		if (errno == EEXIST) {
-			if (lstat(abspath, &sb) == -1) {
-				err = got_error_from_errno();
-				goto done;
-			}
-
-			if (!S_ISDIR(sb.st_mode)) {
-				/* TODO directory is obstructed; do something */
-				err = got_error(GOT_ERR_FILE_OBSTRUCTED);
-				goto done;
-			}
-
-			return NULL;
-		} else if (errno == ENOENT) {
-			err = make_parent_dirs(abspath);
-			if (err)
-				goto done;
-			if (mkdir(abspath, GOT_DEFAULT_DIR_MODE) == -1)
-				err = got_error_from_errno();
-		} else
-			err = got_error_from_errno();
-	}
-
-done:
+	err = got_path_mkdir(abspath);
 	free(abspath);
 	return err;
 }
@@ -1221,6 +1171,54 @@ diff_new(void *arg, struct got_tree_entry *te, const char *parent_path)
 	return err;
 }
 
+/*
+ * Prevent Git's garbage collector from deleting our base commit by
+ * setting a reference to our base commit's ID.
+ */
+static const struct got_error *
+ref_base_commit(struct got_worktree *worktree, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct got_reference *ref = NULL;
+	const char *root_path;
+	char *refname = NULL, *uuidstr = NULL, *s;
+	uint32_t uuid_status;
+
+	uuid_to_string(&worktree->uuid, &uuidstr, &uuid_status);
+	if (uuid_status != uuid_s_ok)
+		return got_error_uuid(uuid_status);
+
+	root_path = got_worktree_get_root_path(worktree);
+	while (root_path[0] == '/')
+		root_path++;
+	if (asprintf(&refname, "%s-%s-%s", GOT_WORKTREE_BASE_REF_PREFIX,
+	    root_path, uuidstr) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	/* Replace slashes from worktree's on-disk path with dashes. */
+	s = refname + sizeof(GOT_WORKTREE_BASE_REF_PREFIX) - 1;
+	while (*s) {
+		if (*s == '/')
+			*s = '-';
+		s++;
+	}
+
+	err = got_ref_alloc(&ref, refname, worktree->base_commit_id);
+	if (err)
+		goto done;
+
+	err = got_ref_write(ref, repo);
+done:
+	free(uuidstr);
+	free(refname);
+	if (ref)
+		got_ref_close(ref);
+	return err;
+}
+
+
 const struct got_error *
 got_worktree_checkout_files(struct got_worktree *worktree,
     struct got_repository *repo, got_worktree_checkout_cb progress_cb,
@@ -1276,6 +1274,10 @@ got_worktree_checkout_files(struct got_worktree *worktree,
 	if (err)
 		goto done;
 
+	err = ref_base_commit(worktree, repo);
+	if (err)
+		goto done;
+
 	err = got_object_open_as_commit(&commit, repo,
 	   worktree->base_commit_id);
 	if (err)
diff --git a/regress/worktree/worktree_test.c b/regress/worktree/worktree_test.c
index b33bfa3..7c5d377 100644
--- a/regress/worktree/worktree_test.c
+++ b/regress/worktree/worktree_test.c
@@ -83,6 +83,48 @@ remove_meta_file(const char *worktree_path, const char *name)
 	return 1;
 }
 
+static const struct got_error *
+remove_worktree_base_ref(struct got_worktree *worktree,
+    struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	const char *root_path;
+	struct got_reference *base_ref;
+	char *refname = NULL, *uuidstr = NULL, *s;
+	uint32_t uuid_status;
+
+	uuid_to_string(&worktree->uuid, &uuidstr, &uuid_status);
+	if (uuid_status != uuid_s_ok)
+		return got_error_uuid(uuid_status);
+	root_path = got_worktree_get_root_path(worktree);
+	while (*root_path == '/')
+		root_path++;
+	if (asprintf(&refname, "refs/%s-%s-%s", GOT_WORKTREE_BASE_REF_PREFIX,
+	    root_path, uuidstr) == -1)
+		return got_error_from_errno();
+
+	/* Replace slashes from worktree's on-disk path with dashes. */
+	s = refname + sizeof(GOT_WORKTREE_BASE_REF_PREFIX) - 1;
+	while (*s) {
+		if (*s == '/')
+			*s = '-';
+		s++;
+	}
+
+	err = got_ref_open(&base_ref, repo, refname);
+	if (err)
+		goto done;
+
+	err = got_ref_delete(base_ref, repo);
+done:
+	if (base_ref)
+		got_ref_close(base_ref);
+	free(uuidstr);
+	free(refname);
+	return err;
+
+}
+
 static int
 remove_worktree(const char *worktree_path)
 {
@@ -370,6 +412,9 @@ worktree_checkout(const char *repo_path)
 	else
 		unlink(cfile_path);
 
+	err = remove_worktree_base_ref(worktree, repo);
+	if (err)
+		goto done;
 	if (!remove_worktree(worktree_path))
 		goto done;
 
@@ -444,7 +489,7 @@ main(int argc, char *argv[])
 	if (unveil("/tmp", "rwc") != 0)
 		err(1, "unveil");
 
-	if (unveil(repo_path, "r") != 0)
+	if (unveil(repo_path, "rwc") != 0)
 		err(1, "unveil");
 
 	if (got_privsep_unveil_exec_helpers() != NULL)