worktree: new place for "git prune --worktrees"
[git] / builtin / worktree.c
1 #include "cache.h"
2 #include "builtin.h"
3 #include "dir.h"
4 #include "parse-options.h"
5
6 static const char * const worktree_usage[] = {
7         N_("git worktree prune [<options>]"),
8         NULL
9 };
10
11 static int show_only;
12 static int verbose;
13 static unsigned long expire;
14
15 static int prune_worktree(const char *id, struct strbuf *reason)
16 {
17         struct stat st;
18         char *path;
19         int fd, len;
20
21         if (!is_directory(git_path("worktrees/%s", id))) {
22                 strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
23                 return 1;
24         }
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;
30         }
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;
36         }
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;
47         }
48         path[len] = '\0';
49         if (!file_exists(path)) {
50                 struct stat st_link;
51                 free(path);
52                 /*
53                  * the repo is moved manually and has not been
54                  * accessed since?
55                  */
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;
64                 }
65         }
66         free(path);
67         return 0;
68 }
69
70 static void prune_worktrees(void)
71 {
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));
96         }
97         closedir(dir);
98         if (!show_only)
99                 rmdir(git_path("worktrees"));
100         strbuf_release(&reason);
101         strbuf_release(&path);
102 }
103
104 static int prune(int ac, const char **av, const char *prefix)
105 {
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()
112         };
113
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;
120 }
121
122 int cmd_worktree(int ac, const char **av, const char *prefix)
123 {
124         struct option options[] = {
125                 OPT_END()
126         };
127
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);
133 }