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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
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);
+}