1#include "cache.h"
2#include "builtin.h"
3#include "dir.h"
4#include "parse-options.h"
56
static const char * const worktree_usage[] = {
7N_("git worktree prune [<options>]"),
8NULL
9};
1011
static int show_only;
12static int verbose;
13static unsigned long expire;
1415
static int prune_worktree(const char *id, struct strbuf *reason)
16{
17struct stat st;
18char *path;
19int fd, len;
2021
if (!is_directory(git_path("worktrees/%s", id))) {
22strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
23return 1;
24}
25if (file_exists(git_path("worktrees/%s/locked", id)))
26return 0;
27if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
28strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
29return 1;
30}
31fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
32if (fd < 0) {
33strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
34id, strerror(errno));
35return 1;
36}
37len = st.st_size;
38path = xmalloc(len + 1);
39read_in_full(fd, path, len);
40close(fd);
41while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
42len--;
43if (!len) {
44strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
45free(path);
46return 1;
47}
48path[len] = '\0';
49if (!file_exists(path)) {
50struct stat st_link;
51free(path);
52/*
53* the repo is moved manually and has not been
54* accessed since?
55*/
56if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
57st_link.st_nlink > 1)
58return 0;
59if (st.st_mtime <= expire) {
60strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
61return 1;
62} else {
63return 0;
64}
65}
66free(path);
67return 0;
68}
6970
static void prune_worktrees(void)
71{
72struct strbuf reason = STRBUF_INIT;
73struct strbuf path = STRBUF_INIT;
74DIR *dir = opendir(git_path("worktrees"));
75struct dirent *d;
76int ret;
77if (!dir)
78return;
79while ((d = readdir(dir)) != NULL) {
80if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
81continue;
82strbuf_reset(&reason);
83if (!prune_worktree(d->d_name, &reason))
84continue;
85if (show_only || verbose)
86printf("%s\n", reason.buf);
87if (show_only)
88continue;
89strbuf_reset(&path);
90strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
91ret = remove_dir_recursively(&path, 0);
92if (ret < 0 && errno == ENOTDIR)
93ret = unlink(path.buf);
94if (ret)
95error(_("failed to remove: %s"), strerror(errno));
96}
97closedir(dir);
98if (!show_only)
99rmdir(git_path("worktrees"));
100strbuf_release(&reason);
101strbuf_release(&path);
102}
103104
static int prune(int ac, const char **av, const char *prefix)
105{
106struct option options[] = {
107OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
108OPT__VERBOSE(&verbose, N_("report pruned objects")),
109OPT_EXPIRY_DATE(0, "expire", &expire,
110N_("expire objects older than <time>")),
111OPT_END()
112};
113114
expire = ULONG_MAX;
115ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
116if (ac)
117usage_with_options(worktree_usage, options);
118prune_worktrees();
119return 0;
120}
121122
int cmd_worktree(int ac, const char **av, const char *prefix)
123{
124struct option options[] = {
125OPT_END()
126};
127128
if (ac < 2)
129usage_with_options(worktree_usage, options);
130if (!strcmp(av[1], "prune"))
131return prune(ac - 1, av + 1, prefix);
132usage_with_options(worktree_usage, options);
133}