1#include "cache.h"
2#include "builtin.h"
3#include "dir.h"
4#include "parse-options.h"
5#include "argv-array.h"
6#include "run-command.h"
7
8static const char * const worktree_usage[] = {
9 N_("git worktree add [<options>] <path> <branch>"),
10 N_("git worktree prune [<options>]"),
11 NULL
12};
13
14static int show_only;
15static int verbose;
16static unsigned long expire;
17
18static int prune_worktree(const char *id, struct strbuf *reason)
19{
20 struct stat st;
21 char *path;
22 int fd, len;
23
24 if (!is_directory(git_path("worktrees/%s", id))) {
25 strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
26 return 1;
27 }
28 if (file_exists(git_path("worktrees/%s/locked", id)))
29 return 0;
30 if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
31 strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
32 return 1;
33 }
34 fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
35 if (fd < 0) {
36 strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
37 id, strerror(errno));
38 return 1;
39 }
40 len = st.st_size;
41 path = xmalloc(len + 1);
42 read_in_full(fd, path, len);
43 close(fd);
44 while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
45 len--;
46 if (!len) {
47 strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
48 free(path);
49 return 1;
50 }
51 path[len] = '\0';
52 if (!file_exists(path)) {
53 struct stat st_link;
54 free(path);
55 /*
56 * the repo is moved manually and has not been
57 * accessed since?
58 */
59 if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
60 st_link.st_nlink > 1)
61 return 0;
62 if (st.st_mtime <= expire) {
63 strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
64 return 1;
65 } else {
66 return 0;
67 }
68 }
69 free(path);
70 return 0;
71}
72
73static void prune_worktrees(void)
74{
75 struct strbuf reason = STRBUF_INIT;
76 struct strbuf path = STRBUF_INIT;
77 DIR *dir = opendir(git_path("worktrees"));
78 struct dirent *d;
79 int ret;
80 if (!dir)
81 return;
82 while ((d = readdir(dir)) != NULL) {
83 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
84 continue;
85 strbuf_reset(&reason);
86 if (!prune_worktree(d->d_name, &reason))
87 continue;
88 if (show_only || verbose)
89 printf("%s\n", reason.buf);
90 if (show_only)
91 continue;
92 strbuf_reset(&path);
93 strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
94 ret = remove_dir_recursively(&path, 0);
95 if (ret < 0 && errno == ENOTDIR)
96 ret = unlink(path.buf);
97 if (ret)
98 error(_("failed to remove: %s"), strerror(errno));
99 }
100 closedir(dir);
101 if (!show_only)
102 rmdir(git_path("worktrees"));
103 strbuf_release(&reason);
104 strbuf_release(&path);
105}
106
107static int prune(int ac, const char **av, const char *prefix)
108{
109 struct option options[] = {
110 OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
111 OPT__VERBOSE(&verbose, N_("report pruned objects")),
112 OPT_EXPIRY_DATE(0, "expire", &expire,
113 N_("expire objects older than <time>")),
114 OPT_END()
115 };
116
117 expire = ULONG_MAX;
118 ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
119 if (ac)
120 usage_with_options(worktree_usage, options);
121 prune_worktrees();
122 return 0;
123}
124
125static int add(int ac, const char **av, const char *prefix)
126{
127 struct child_process c;
128 int force = 0, detach = 0;
129 const char *path, *branch;
130 struct argv_array cmd = ARGV_ARRAY_INIT;
131 struct option options[] = {
132 OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")),
133 OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")),
134 OPT_END()
135 };
136
137 ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
138 if (ac != 2)
139 usage_with_options(worktree_usage, options);
140
141 path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
142 branch = av[1];
143
144 argv_array_push(&cmd, "checkout");
145 argv_array_pushl(&cmd, "--to", path, NULL);
146 if (force)
147 argv_array_push(&cmd, "--ignore-other-worktrees");
148 if (detach)
149 argv_array_push(&cmd, "--detach");
150 argv_array_push(&cmd, branch);
151
152 memset(&c, 0, sizeof(c));
153 c.git_cmd = 1;
154 c.argv = cmd.argv;
155 return run_command(&c);
156}
157
158int cmd_worktree(int ac, const char **av, const char *prefix)
159{
160 struct option options[] = {
161 OPT_END()
162 };
163
164 if (ac < 2)
165 usage_with_options(worktree_usage, options);
166 if (!strcmp(av[1], "add"))
167 return add(ac - 1, av + 1, prefix);
168 if (!strcmp(av[1], "prune"))
169 return prune(ac - 1, av + 1, prefix);
170 usage_with_options(worktree_usage, options);
171}