]> git.ipfire.org Git - thirdparty/git.git/blame - builtin/worktree.c
tests: worktree: retrofit "checkout --to" tests for "worktree add"
[thirdparty/git.git] / builtin / worktree.c
CommitLineData
df0b6cfb
NTND
1#include "cache.h"
2#include "builtin.h"
3#include "dir.h"
4#include "parse-options.h"
fc56361f
ES
5#include "argv-array.h"
6#include "run-command.h"
df0b6cfb
NTND
7
8static const char * const worktree_usage[] = {
f4325444 9 N_("git worktree add [<options>] <path> <branch>"),
df0b6cfb
NTND
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
fc56361f
ES
125static int add(int ac, const char **av, const char *prefix)
126{
127 struct child_process c;
39ecb274 128 int force = 0, detach = 0;
cbdf60fa 129 const char *new_branch = NULL, *new_branch_force = NULL;
fc56361f
ES
130 const char *path, *branch;
131 struct argv_array cmd = ARGV_ARRAY_INIT;
132 struct option options[] = {
f4325444 133 OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")),
cbdf60fa
ES
134 OPT_STRING('b', NULL, &new_branch, N_("branch"),
135 N_("create a new branch")),
136 OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
137 N_("create or reset a branch")),
39ecb274 138 OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")),
fc56361f
ES
139 OPT_END()
140 };
141
142 ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
cbdf60fa
ES
143 if (new_branch && new_branch_force)
144 die(_("-b and -B are mutually exclusive"));
fc56361f
ES
145 if (ac != 2)
146 usage_with_options(worktree_usage, options);
147
148 path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
149 branch = av[1];
150
151 argv_array_push(&cmd, "checkout");
152 argv_array_pushl(&cmd, "--to", path, NULL);
f4325444
ES
153 if (force)
154 argv_array_push(&cmd, "--ignore-other-worktrees");
cbdf60fa
ES
155 if (new_branch)
156 argv_array_pushl(&cmd, "-b", new_branch, NULL);
157 if (new_branch_force)
158 argv_array_pushl(&cmd, "-B", new_branch_force, NULL);
39ecb274
ES
159 if (detach)
160 argv_array_push(&cmd, "--detach");
fc56361f
ES
161 argv_array_push(&cmd, branch);
162
163 memset(&c, 0, sizeof(c));
164 c.git_cmd = 1;
165 c.argv = cmd.argv;
166 return run_command(&c);
167}
168
df0b6cfb
NTND
169int cmd_worktree(int ac, const char **av, const char *prefix)
170{
171 struct option options[] = {
172 OPT_END()
173 };
174
175 if (ac < 2)
176 usage_with_options(worktree_usage, options);
fc56361f
ES
177 if (!strcmp(av[1], "add"))
178 return add(ac - 1, av + 1, prefix);
df0b6cfb
NTND
179 if (!strcmp(av[1], "prune"))
180 return prune(ac - 1, av + 1, prefix);
181 usage_with_options(worktree_usage, options);
182}