1#include "cache.h"2#include "builtin.h"3#include "dir.h"4#include "parse-options.h"56static const char * const worktree_usage[] = {7N_("git worktree prune [<options>]"),8NULL9};1011static int show_only;12static int verbose;13static unsigned long expire;1415static int prune_worktree(const char *id, struct strbuf *reason)16{17struct stat st;18char *path;19int fd, len;2021if (!is_directory(git_path("worktrees/%s", id))) {22strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);23return 1;24}25if (file_exists(git_path("worktrees/%s/locked", id)))26return 0;27if (stat(git_path("worktrees/%s/gitdir", id), &st)) {28strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);29return 1;30}31fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);32if (fd < 0) {33strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),34id, strerror(errno));35return 1;36}37len = st.st_size;38path = xmalloc(len + 1);39read_in_full(fd, path, len);40close(fd);41while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))42len--;43if (!len) {44strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);45free(path);46return 1;47}48path[len] = '\0';49if (!file_exists(path)) {50struct stat st_link;51free(path);52/*53* the repo is moved manually and has not been54* accessed since?55*/56if (!stat(git_path("worktrees/%s/link", id), &st_link) &&57st_link.st_nlink > 1)58return 0;59if (st.st_mtime <= expire) {60strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);61return 1;62} else {63return 0;64}65}66free(path);67return 0;68}6970static void prune_worktrees(void)71{72struct strbuf reason = STRBUF_INIT;73struct strbuf path = STRBUF_INIT;74DIR *dir = opendir(git_path("worktrees"));75struct dirent *d;76int ret;77if (!dir)78return;79while ((d = readdir(dir)) != NULL) {80if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))81continue;82strbuf_reset(&reason);83if (!prune_worktree(d->d_name, &reason))84continue;85if (show_only || verbose)86printf("%s\n", reason.buf);87if (show_only)88continue;89strbuf_reset(&path);90strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));91ret = remove_dir_recursively(&path, 0);92if (ret < 0 && errno == ENOTDIR)93ret = unlink(path.buf);94if (ret)95error(_("failed to remove: %s"), strerror(errno));96}97closedir(dir);98if (!show_only)99rmdir(git_path("worktrees"));100strbuf_release(&reason);101strbuf_release(&path);102}103104static int prune(int ac, const char **av, const char *prefix)105{106struct option options[] = {107OPT__DRY_RUN(&show_only, N_("do not remove, show only")),108OPT__VERBOSE(&verbose, N_("report pruned objects")),109OPT_EXPIRY_DATE(0, "expire", &expire,110N_("expire objects older than <time>")),111OPT_END()112};113114expire = ULONG_MAX;115ac = parse_options(ac, av, prefix, options, worktree_usage, 0);116if (ac)117usage_with_options(worktree_usage, options);118prune_worktrees();119return 0;120}121122int cmd_worktree(int ac, const char **av, const char *prefix)123{124struct option options[] = {125OPT_END()126};127128if (ac < 2)129usage_with_options(worktree_usage, options);130if (!strcmp(av[1], "prune"))131return prune(ac - 1, av + 1, prefix);132usage_with_options(worktree_usage, options);133}