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