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