Skip to content

Commit f0cfc34

Browse files
committed
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 parent 2a50348 commit f0cfc34

File tree

3 files changed

+148
-0
lines changed

3 files changed

+148
-0
lines changed

include/git2/worktree.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,32 @@ GIT_EXTERN(int) git_worktree_unlock(git_worktree *wt);
112112
*/
113113
GIT_EXTERN(int) git_worktree_is_locked(git_buf *reason, const git_worktree *wt);
114114

115+
/**
116+
* Flags which can be passed to git_worktree_prune to alter its
117+
* behavior.
118+
*/
119+
typedef enum {
120+
/* Prune working tree even if working tree is valid */
121+
GIT_WORKTREE_PRUNE_VALID = 1u << 0,
122+
/* Prune working tree even if it is locked */
123+
GIT_WORKTREE_PRUNE_LOCKED = 1u << 1,
124+
/* Prune checked out working tree */
125+
GIT_WORKTREE_PRUNE_WORKING_TREE = 1u << 2,
126+
} git_worktree_prune_t;
127+
128+
/**
129+
* Prune working tree
130+
*
131+
* Prune the working tree, that is remove the git data
132+
* structures on disk. The repository will only be pruned of
133+
* `git_worktree_is_prunable` succeeds.
134+
*
135+
* @param wt Worktree to prune
136+
* @param flags git_worktree_prune_t flags
137+
* @return 0 or an error code
138+
*/
139+
GIT_EXTERN(int) git_worktree_prune(git_worktree *wt, unsigned flags);
140+
115141
/** @} */
116142
GIT_END_DECL
117143
#endif

src/worktree.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,3 +356,67 @@ int git_worktree_is_locked(git_buf *reason, const git_worktree *wt)
356356

357357
return ret;
358358
}
359+
360+
int git_worktree_prune(git_worktree *wt, unsigned flags)
361+
{
362+
git_buf reason = GIT_BUF_INIT, path = GIT_BUF_INIT;
363+
char *wtpath;
364+
int err;
365+
366+
if ((flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 &&
367+
git_worktree_is_locked(&reason, wt))
368+
{
369+
if (!reason.size)
370+
git_buf_attach_notowned(&reason, "no reason given", 15);
371+
giterr_set(GITERR_WORKTREE, "Not pruning locked working tree: '%s'", reason.ptr);
372+
373+
err = -1;
374+
goto out;
375+
}
376+
377+
if ((flags & GIT_WORKTREE_PRUNE_VALID) == 0 &&
378+
git_worktree_validate(wt) == 0)
379+
{
380+
giterr_set(GITERR_WORKTREE, "Not pruning valid working tree");
381+
err = -1;
382+
goto out;
383+
}
384+
385+
/* Delete gitdir in parent repository */
386+
if ((err = git_buf_printf(&path, "%s/worktrees/%s", wt->parent_path, wt->name)) < 0)
387+
goto out;
388+
if (!git_path_exists(path.ptr))
389+
{
390+
giterr_set(GITERR_WORKTREE, "Worktree gitdir '%s' does not exist", path.ptr);
391+
err = -1;
392+
goto out;
393+
}
394+
if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
395+
goto out;
396+
397+
/* Skip deletion of the actual working tree if it does
398+
* not exist or deletion was not requested */
399+
if ((flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 ||
400+
!git_path_exists(wt->gitlink_path))
401+
{
402+
goto out;
403+
}
404+
405+
if ((wtpath = git_path_dirname(wt->gitlink_path)) == NULL)
406+
goto out;
407+
git_buf_attach(&path, wtpath, 0);
408+
if (!git_path_exists(path.ptr))
409+
{
410+
giterr_set(GITERR_WORKTREE, "Working tree '%s' does not exist", path.ptr);
411+
err = -1;
412+
goto out;
413+
}
414+
if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
415+
goto out;
416+
417+
out:
418+
git_buf_free(&reason);
419+
git_buf_free(&path);
420+
421+
return err;
422+
}

tests/worktree/worktree.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,61 @@ void test_worktree_worktree__unlock_locked_worktree(void)
395395

396396
git_worktree_free(wt);
397397
}
398+
399+
void test_worktree_worktree__prune_valid(void)
400+
{
401+
git_worktree *wt;
402+
git_repository *repo;
403+
404+
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
405+
cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
406+
407+
/* Assert the repository is not valid anymore */
408+
cl_git_fail(git_repository_open_from_worktree(&repo, wt));
409+
410+
git_worktree_free(wt);
411+
git_repository_free(repo);
412+
}
413+
414+
void test_worktree_worktree__prune_locked(void)
415+
{
416+
git_worktree *wt;
417+
git_repository *repo;
418+
419+
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
420+
cl_git_pass(git_worktree_lock(wt, NULL));
421+
cl_git_fail(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
422+
cl_git_fail(git_worktree_prune(wt, ~GIT_WORKTREE_PRUNE_LOCKED));
423+
424+
/* Assert the repository is still valid */
425+
cl_git_pass(git_repository_open_from_worktree(&repo, wt));
426+
427+
git_worktree_free(wt);
428+
git_repository_free(repo);
429+
}
430+
431+
void test_worktree_worktree__prune_gitdir(void)
432+
{
433+
git_worktree *wt;
434+
435+
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
436+
cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
437+
438+
cl_assert(!git_path_exists(wt->gitdir_path));
439+
cl_assert(git_path_exists(wt->gitlink_path));
440+
441+
git_worktree_free(wt);
442+
}
443+
444+
void test_worktree_worktree__prune_both(void)
445+
{
446+
git_worktree *wt;
447+
448+
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
449+
cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_WORKING_TREE | GIT_WORKTREE_PRUNE_VALID));
450+
451+
cl_assert(!git_path_exists(wt->gitdir_path));
452+
cl_assert(!git_path_exists(wt->gitlink_path));
453+
454+
git_worktree_free(wt);
455+
}

0 commit comments

Comments
 (0)