Commit f0cfc34105fd68af9eb6e2024459c40c45e7d3a0

Patrick Steinhardt 2015-10-21T13:53:18

worktree: implement `git_worktree_prune` Implement the `git_worktree_prune` function. This function can be used to delete working trees from a repository. According to the flags passed to it, it can either delete the working tree's gitdir only or both gitdir and the working directory.

diff --git a/include/git2/worktree.h b/include/git2/worktree.h
index 62b4b5e..594ff79 100644
--- a/include/git2/worktree.h
+++ b/include/git2/worktree.h
@@ -112,6 +112,32 @@ GIT_EXTERN(int) git_worktree_unlock(git_worktree *wt);
  */
 GIT_EXTERN(int) git_worktree_is_locked(git_buf *reason, const git_worktree *wt);
 
+/**
+ * Flags which can be passed to git_worktree_prune to alter its
+ * behavior.
+ */
+typedef enum {
+	/* Prune working tree even if working tree is valid */
+	GIT_WORKTREE_PRUNE_VALID = 1u << 0,
+	/* Prune working tree even if it is locked */
+	GIT_WORKTREE_PRUNE_LOCKED = 1u << 1,
+	/* Prune checked out working tree */
+	GIT_WORKTREE_PRUNE_WORKING_TREE = 1u << 2,
+} git_worktree_prune_t;
+
+/**
+ * Prune working tree
+ *
+ * Prune the working tree, that is remove the git data
+ * structures on disk. The repository will only be pruned of
+ * `git_worktree_is_prunable` succeeds.
+ *
+ * @param wt Worktree to prune
+ * @param flags git_worktree_prune_t flags
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_worktree_prune(git_worktree *wt, unsigned flags);
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/src/worktree.c b/src/worktree.c
index fa5a916..95a2757 100644
--- a/src/worktree.c
+++ b/src/worktree.c
@@ -356,3 +356,67 @@ out:
 
 	return ret;
 }
+
+int git_worktree_prune(git_worktree *wt, unsigned flags)
+{
+	git_buf reason = GIT_BUF_INIT, path = GIT_BUF_INIT;
+	char *wtpath;
+	int err;
+
+	if ((flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 &&
+		git_worktree_is_locked(&reason, wt))
+	{
+		if (!reason.size)
+			git_buf_attach_notowned(&reason, "no reason given", 15);
+		giterr_set(GITERR_WORKTREE, "Not pruning locked working tree: '%s'", reason.ptr);
+
+		err = -1;
+		goto out;
+	}
+
+	if ((flags & GIT_WORKTREE_PRUNE_VALID) == 0 &&
+		git_worktree_validate(wt) == 0)
+	{
+		giterr_set(GITERR_WORKTREE, "Not pruning valid working tree");
+		err = -1;
+		goto out;
+	}
+
+	/* Delete gitdir in parent repository */
+	if ((err = git_buf_printf(&path, "%s/worktrees/%s", wt->parent_path, wt->name)) < 0)
+		goto out;
+	if (!git_path_exists(path.ptr))
+	{
+		giterr_set(GITERR_WORKTREE, "Worktree gitdir '%s' does not exist", path.ptr);
+		err = -1;
+		goto out;
+	}
+	if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
+		goto out;
+
+	/* Skip deletion of the actual working tree if it does
+	 * not exist or deletion was not requested */
+	if ((flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 ||
+		!git_path_exists(wt->gitlink_path))
+	{
+		goto out;
+	}
+
+	if ((wtpath = git_path_dirname(wt->gitlink_path)) == NULL)
+		goto out;
+	git_buf_attach(&path, wtpath, 0);
+	if (!git_path_exists(path.ptr))
+	{
+		giterr_set(GITERR_WORKTREE, "Working tree '%s' does not exist", path.ptr);
+		err = -1;
+		goto out;
+	}
+	if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
+		goto out;
+
+out:
+	git_buf_free(&reason);
+	git_buf_free(&path);
+
+	return err;
+}
diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c
index 82b4ebc..7758b1b 100644
--- a/tests/worktree/worktree.c
+++ b/tests/worktree/worktree.c
@@ -395,3 +395,61 @@ void test_worktree_worktree__unlock_locked_worktree(void)
 
 	git_worktree_free(wt);
 }
+
+void test_worktree_worktree__prune_valid(void)
+{
+	git_worktree *wt;
+	git_repository *repo;
+
+	cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
+	cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
+
+	/* Assert the repository is not valid anymore */
+	cl_git_fail(git_repository_open_from_worktree(&repo, wt));
+
+	git_worktree_free(wt);
+	git_repository_free(repo);
+}
+
+void test_worktree_worktree__prune_locked(void)
+{
+	git_worktree *wt;
+	git_repository *repo;
+
+	cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
+	cl_git_pass(git_worktree_lock(wt, NULL));
+	cl_git_fail(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
+	cl_git_fail(git_worktree_prune(wt, ~GIT_WORKTREE_PRUNE_LOCKED));
+
+	/* Assert the repository is still valid */
+	cl_git_pass(git_repository_open_from_worktree(&repo, wt));
+
+	git_worktree_free(wt);
+	git_repository_free(repo);
+}
+
+void test_worktree_worktree__prune_gitdir(void)
+{
+	git_worktree *wt;
+
+	cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
+	cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
+
+	cl_assert(!git_path_exists(wt->gitdir_path));
+	cl_assert(git_path_exists(wt->gitlink_path));
+
+	git_worktree_free(wt);
+}
+
+void test_worktree_worktree__prune_both(void)
+{
+	git_worktree *wt;
+
+	cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
+	cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_WORKING_TREE | GIT_WORKTREE_PRUNE_VALID));
+
+	cl_assert(!git_path_exists(wt->gitdir_path));
+	cl_assert(!git_path_exists(wt->gitlink_path));
+
+	git_worktree_free(wt);
+}