76
return 0;
77
}
78
79
+static int prune_worktree(const char *id, struct strbuf *reason)
80
+{
81
+ struct stat st;
82
+ char *path;
83
+ int fd, len;
84
+
85
+ if (!is_directory(git_path("worktrees/%s", id))) {
86
+ strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
87
+ return 1;
88
+ }
89
+ if (file_exists(git_path("worktrees/%s/locked", id)))
90
+ return 0;
91
+ if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
92
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
93
+ return 1;
94
+ }
95
+ fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
96
+ if (fd < 0) {
97
+ strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
98
+ id, strerror(errno));
99
+ return 1;
100
+ }
101
+ len = st.st_size;
102
+ path = xmalloc(len + 1);
103
+ read_in_full(fd, path, len);
104
+ close(fd);
105
+ while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
106
+ len--;
107
+ if (!len) {
108
+ strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
109
+ free(path);
110
+ return 1;
111
+ }
112
+ path[len] = '\0';
113
+ if (!file_exists(path)) {
114
+ struct stat st_link;
115
+ free(path);
116
+ /*
117
+ * the repo is moved manually and has not been
118
+ * accessed since?
119
+ */
120
+ if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
121
+ st_link.st_nlink > 1)
122
+ return 0;
123
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
124
+ return 1;
125
+ }
126
+ free(path);
127
+ return st.st_mtime <= expire;
128
+}
129
+
130
+static void prune_worktrees(void)
131
+{
132
+ struct strbuf reason = STRBUF_INIT;
133
+ struct strbuf path = STRBUF_INIT;
134
+ DIR *dir = opendir(git_path("worktrees"));
135
+ struct dirent *d;
136
+ int ret;
137
+ if (!dir)
138
+ return;
139
+ while ((d = readdir(dir)) != NULL) {
140
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
141
+ continue;
142
+ strbuf_reset(&reason);
143
+ if (!prune_worktree(d->d_name, &reason))
144
+ continue;
145
+ if (show_only || verbose)
146
+ printf("%s\n", reason.buf);
147
+ if (show_only)
148
+ continue;
149
+ strbuf_reset(&path);
150
+ strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
151
+ ret = remove_dir_recursively(&path, 0);
152
+ if (ret < 0 && errno == ENOTDIR)
153
+ ret = unlink(path.buf);
154
+ if (ret)
155
+ error(_("failed to remove: %s"), strerror(errno));
156
+ }
157
+ closedir(dir);
158
+ if (!show_only)
159
+ rmdir(git_path("worktrees"));
160
+ strbuf_release(&reason);
161
+ strbuf_release(&path);
162
+}
163
+
164
/*
165
* Write errors (particularly out of space) can result in
166
* failed temporary packs (and more rarely indexes and other
187
{
188
struct rev_info revs;
189
struct progress *progress = NULL;
190
+ int do_prune_worktrees = 0;
191
const struct option options[] = {
192
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
193
OPT__VERBOSE(&verbose, N_("report pruned objects")),
194
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
195
+ OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")),
196
OPT_EXPIRY_DATE(0, "expire", &expire,
197
N_("expire objects older than <time>")),
198
OPT_END()
205
init_revisions(&revs, prefix);
206
207
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
208
+
209
+ if (do_prune_worktrees) {
210
+ if (argc)
211
+ die(_("--worktrees does not take extra arguments"));
212
+ prune_worktrees();
213
+ return 0;
214
+ }
215
+
216
while (argc--) {
217
unsigned char sha1[20];
218
const char *name = *argv++;