8c35023a46c5aa010ef307c81a1567561ef0a567
   1#include "cache.h"
   2#include "builtin.h"
   3#include "dir.h"
   4#include "parse-options.h"
   5#include "argv-array.h"
   6#include "run-command.h"
   7
   8static const char * const worktree_usage[] = {
   9        N_("git worktree add [<options>] <path> <branch>"),
  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
 125static int add(int ac, const char **av, const char *prefix)
 126{
 127        struct child_process c;
 128        int force = 0;
 129        const char *path, *branch;
 130        struct argv_array cmd = ARGV_ARRAY_INIT;
 131        struct option options[] = {
 132                OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")),
 133                OPT_END()
 134        };
 135
 136        ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
 137        if (ac != 2)
 138                usage_with_options(worktree_usage, options);
 139
 140        path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
 141        branch = av[1];
 142
 143        argv_array_push(&cmd, "checkout");
 144        argv_array_pushl(&cmd, "--to", path, NULL);
 145        if (force)
 146                argv_array_push(&cmd, "--ignore-other-worktrees");
 147        argv_array_push(&cmd, branch);
 148
 149        memset(&c, 0, sizeof(c));
 150        c.git_cmd = 1;
 151        c.argv = cmd.argv;
 152        return run_command(&c);
 153}
 154
 155int cmd_worktree(int ac, const char **av, const char *prefix)
 156{
 157        struct option options[] = {
 158                OPT_END()
 159        };
 160
 161        if (ac < 2)
 162                usage_with_options(worktree_usage, options);
 163        if (!strcmp(av[1], "add"))
 164                return add(ac - 1, av + 1, prefix);
 165        if (!strcmp(av[1], "prune"))
 166                return prune(ac - 1, av + 1, prefix);
 167        usage_with_options(worktree_usage, options);
 168}