summaryrefslogtreecommitdiff
path: root/builtin/prune.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/prune.c')
-rw-r--r--builtin/prune.c99
1 files changed, 99 insertions, 0 deletions
diff --git a/builtin/prune.c b/builtin/prune.c
index 17094ad954..0c73246c72 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -76,6 +76,95 @@ static int prune_subdir(int nr, const char *path, void *data)
return 0;
}
+static int prune_worktree(const char *id, struct strbuf *reason)
+{
+ struct stat st;
+ char *path;
+ int fd, len;
+
+ if (!is_directory(git_path("worktrees/%s", id))) {
+ strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
+ return 1;
+ }
+ if (file_exists(git_path("worktrees/%s/locked", id)))
+ return 0;
+ if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
+ return 1;
+ }
+ fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
+ if (fd < 0) {
+ strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
+ id, strerror(errno));
+ return 1;
+ }
+ len = st.st_size;
+ path = xmalloc(len + 1);
+ read_in_full(fd, path, len);
+ close(fd);
+ while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
+ len--;
+ if (!len) {
+ strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
+ free(path);
+ return 1;
+ }
+ path[len] = '\0';
+ if (!file_exists(path)) {
+ struct stat st_link;
+ free(path);
+ /*
+ * the repo is moved manually and has not been
+ * accessed since?
+ */
+ if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
+ st_link.st_nlink > 1)
+ return 0;
+ if (st.st_mtime <= expire) {
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ free(path);
+ return 0;
+}
+
+static void prune_worktrees(void)
+{
+ struct strbuf reason = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ DIR *dir = opendir(git_path("worktrees"));
+ struct dirent *d;
+ int ret;
+ if (!dir)
+ return;
+ while ((d = readdir(dir)) != NULL) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+ strbuf_reset(&reason);
+ if (!prune_worktree(d->d_name, &reason))
+ continue;
+ if (show_only || verbose)
+ printf("%s\n", reason.buf);
+ if (show_only)
+ continue;
+ strbuf_reset(&path);
+ strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
+ ret = remove_dir_recursively(&path, 0);
+ if (ret < 0 && errno == ENOTDIR)
+ ret = unlink(path.buf);
+ if (ret)
+ error(_("failed to remove: %s"), strerror(errno));
+ }
+ closedir(dir);
+ if (!show_only)
+ rmdir(git_path("worktrees"));
+ strbuf_release(&reason);
+ strbuf_release(&path);
+}
+
/*
* Write errors (particularly out of space) can result in
* failed temporary packs (and more rarely indexes and other
@@ -102,10 +191,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
struct progress *progress = NULL;
+ int do_prune_worktrees = 0;
const struct option options[] = {
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
OPT__VERBOSE(&verbose, N_("report pruned objects")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+ OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")),
OPT_EXPIRY_DATE(0, "expire", &expire,
N_("expire objects older than <time>")),
OPT_END()
@@ -119,6 +210,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
init_revisions(&revs, prefix);
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
+
+ if (do_prune_worktrees) {
+ if (argc)
+ die(_("--worktrees does not take extra arguments"));
+ prune_worktrees();
+ return 0;
+ }
+
while (argc--) {
unsigned char sha1[20];
const char *name = *argv++;