worktree: new place for "git prune --worktrees"
authorNguyễn Thái Ngọc Duy <pclouds@gmail.com>
Mon, 29 Jun 2015 12:51:18 +0000 (19:51 +0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 29 Jun 2015 15:48:44 +0000 (08:48 -0700)
Commit 23af91d (prune: strategies for linked checkouts - 2014-11-30)
adds "--worktrees" to "git prune" without realizing that "git prune" is
for object database only. This patch moves the same functionality to a
new command "git worktree".

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
.gitignore
Documentation/git-prune.txt
Documentation/git-worktree.txt [new file with mode: 0644]
Makefile
builtin.h
builtin/gc.c
builtin/prune.c
builtin/worktree.c [new file with mode: 0644]
command-list.txt
git.c
t/t2026-prune-linked-checkouts.sh
index a05241916c9c9a3760a6e98670a7f6427d553d77..b527248ff0e7390750bd4a4e864baf6928ee84ae 100644 (file)
 /git-verify-tag
 /git-web--browse
 /git-whatchanged
+/git-worktree
 /git-write-tree
 /git-core-*/?*
 /gitweb/GITWEB-BUILD-OPTIONS
index 1cf3bed4ab7ab19364c6801fbc73920170dfd1e4..7a493c80f776092265abd4fb2575639e683bcf5c 100644 (file)
@@ -48,9 +48,6 @@ OPTIONS
 --expire <time>::
        Only expire loose objects older than <time>.
 
---worktrees::
-       Prune dead working tree information in $GIT_DIR/worktrees.
-
 <head>...::
        In addition to objects
        reachable from any of our references, keep objects
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
new file mode 100644 (file)
index 0000000..41103e5
--- /dev/null
@@ -0,0 +1,48 @@
+git-worktree(1)
+===============
+
+NAME
+----
+git-worktree - Manage multiple worktrees
+
+
+SYNOPSIS
+--------
+[verse]
+'git worktree prune' [-n] [-v] [--expire <expire>]
+
+DESCRIPTION
+-----------
+
+Manage multiple worktrees attached to the same repository. These are
+created by the command `git checkout --to`.
+
+COMMANDS
+--------
+prune::
+
+Prune working tree information in $GIT_DIR/worktrees.
+
+OPTIONS
+-------
+
+-n::
+--dry-run::
+       Do not remove anything; just report what it would
+       remove.
+
+-v::
+--verbose::
+       Report all removals.
+
+--expire <time>::
+       Only expire unused worktrees older than <time>.
+
+SEE ALSO
+--------
+
+linkgit:git-checkout[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 827006ba611c7e8be0ae4d92eaeaa321d7b9feb2..bfdc90877169fbfc95d38159496636b6e4aec302 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -886,6 +886,7 @@ BUILTIN_OBJS += builtin/var.o
 BUILTIN_OBJS += builtin/verify-commit.o
 BUILTIN_OBJS += builtin/verify-pack.o
 BUILTIN_OBJS += builtin/verify-tag.o
+BUILTIN_OBJS += builtin/worktree.o
 BUILTIN_OBJS += builtin/write-tree.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
index b87df70f96d0f43e4b838f34d7492d116d4d97db..9e04f979bc6ff251bcd18efd55db986d77b1c6aa 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -133,6 +133,7 @@ extern int cmd_verify_commit(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_version(int argc, const char **argv, const char *prefix);
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
+extern int cmd_worktree(int argc, const char **argv, const char *prefix);
 extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_show_ref(int argc, const char **argv, const char *prefix);
index fa87ad355ccebfe22f8e5dc5f90586b482020f0e..44b206acad36393a4158c5e440b1ec5ca4924bc1 100644 (file)
@@ -293,7 +293,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
        argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
        argv_array_pushl(&prune, "prune", "--expire", NULL);
-       argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL);
+       argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
        argv_array_pushl(&rerere, "rerere", "gc", NULL);
 
        gc_config();
index 86282b244758d7c248dc2829291c8b6b05a4708e..75f3b83aa1dc45d75cebc3e81824dad9607bf62a 100644 (file)
@@ -6,7 +6,6 @@
 #include "reachable.h"
 #include "parse-options.h"
 #include "progress.h"
-#include "dir.h"
 
 static const char * const prune_usage[] = {
        N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
@@ -76,95 +75,6 @@ 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
@@ -191,12 +101,10 @@ 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()
@@ -210,13 +118,6 @@ int cmd_prune(int argc, const char **argv, const char *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++;
diff --git a/builtin/worktree.c b/builtin/worktree.c
new file mode 100644 (file)
index 0000000..2a729c6
--- /dev/null
@@ -0,0 +1,133 @@
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "parse-options.h"
+
+static const char * const worktree_usage[] = {
+       N_("git worktree prune [<options>]"),
+       NULL
+};
+
+static int show_only;
+static int verbose;
+static unsigned long expire;
+
+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);
+}
+
+static int prune(int ac, const char **av, const char *prefix)
+{
+       struct option options[] = {
+               OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
+               OPT__VERBOSE(&verbose, N_("report pruned objects")),
+               OPT_EXPIRY_DATE(0, "expire", &expire,
+                               N_("expire objects older than <time>")),
+               OPT_END()
+       };
+
+       expire = ULONG_MAX;
+       ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+       if (ac)
+               usage_with_options(worktree_usage, options);
+       prune_worktrees();
+       return 0;
+}
+
+int cmd_worktree(int ac, const char **av, const char *prefix)
+{
+       struct option options[] = {
+               OPT_END()
+       };
+
+       if (ac < 2)
+               usage_with_options(worktree_usage, options);
+       if (!strcmp(av[1], "prune"))
+               return prune(ac - 1, av + 1, prefix);
+       usage_with_options(worktree_usage, options);
+}
index f1eae0810de9d7e405759190221742534c293745..5fc74b94b8addbf9703543b96eb67e5a8c01bd49 100644 (file)
@@ -138,4 +138,5 @@ git-verify-pack                         plumbinginterrogators
 git-verify-tag                          ancillaryinterrogators
 gitweb                                  ancillaryinterrogators
 git-whatchanged                         ancillaryinterrogators
+git-worktree                            mainporcelain
 git-write-tree                          plumbingmanipulators
diff --git a/git.c b/git.c
index 160896a9439007b1ca110b56bac327d3b0e6af3d..f22783894c161850b8e36fa5426bf61f4fc98a51 100644 (file)
--- a/git.c
+++ b/git.c
@@ -484,6 +484,7 @@ static struct cmd_struct commands[] = {
        { "verify-tag", cmd_verify_tag, RUN_SETUP },
        { "version", cmd_version },
        { "whatchanged", cmd_whatchanged, RUN_SETUP },
+       { "worktree", cmd_worktree, RUN_SETUP },
        { "write-tree", cmd_write_tree, RUN_SETUP },
 };
 
index 1821a480c5ce6fd31416201e3a3dcb0f17b1de91..e872f02dac37638a64918d1e3f585aacd2f94516 100755 (executable)
@@ -8,15 +8,15 @@ test_expect_success initialize '
        git commit --allow-empty -m init
 '
 
-test_expect_success 'prune --worktrees on normal repo' '
-       git prune --worktrees &&
-       test_must_fail git prune --worktrees abc
+test_expect_success 'worktree prune on normal repo' '
+       git worktree prune &&
+       test_must_fail git worktree prune abc
 '
 
 test_expect_success 'prune files inside $GIT_DIR/worktrees' '
        mkdir .git/worktrees &&
        : >.git/worktrees/abc &&
-       git prune --worktrees --verbose >actual &&
+       git worktree prune --verbose >actual &&
        cat >expect <<EOF &&
 Removing worktrees/abc: not a valid directory
 EOF
@@ -31,7 +31,7 @@ test_expect_success 'prune directories without gitdir' '
        cat >expect <<EOF &&
 Removing worktrees/def: gitdir file does not exist
 EOF
-       git prune --worktrees --verbose >actual &&
+       git worktree prune --verbose >actual &&
        test_i18ncmp expect actual &&
        ! test -d .git/worktrees/def &&
        ! test -d .git/worktrees
@@ -42,7 +42,7 @@ test_expect_success SANITY 'prune directories with unreadable gitdir' '
        : >.git/worktrees/def/def &&
        : >.git/worktrees/def/gitdir &&
        chmod u-r .git/worktrees/def/gitdir &&
-       git prune --worktrees --verbose >actual &&
+       git worktree prune --verbose >actual &&
        test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual &&
        ! test -d .git/worktrees/def &&
        ! test -d .git/worktrees
@@ -52,7 +52,7 @@ test_expect_success 'prune directories with invalid gitdir' '
        mkdir -p .git/worktrees/def/abc &&
        : >.git/worktrees/def/def &&
        : >.git/worktrees/def/gitdir &&
-       git prune --worktrees --verbose >actual &&
+       git worktree prune --verbose >actual &&
        test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual &&
        ! test -d .git/worktrees/def &&
        ! test -d .git/worktrees
@@ -62,7 +62,7 @@ test_expect_success 'prune directories with gitdir pointing to nowhere' '
        mkdir -p .git/worktrees/def/abc &&
        : >.git/worktrees/def/def &&
        echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
-       git prune --worktrees --verbose >actual &&
+       git worktree prune --verbose >actual &&
        test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
        ! test -d .git/worktrees/def &&
        ! test -d .git/worktrees
@@ -72,7 +72,7 @@ test_expect_success 'not prune locked checkout' '
        test_when_finished rm -r .git/worktrees &&
        mkdir -p .git/worktrees/ghi &&
        : >.git/worktrees/ghi/locked &&
-       git prune --worktrees &&
+       git worktree prune &&
        test -d .git/worktrees/ghi
 '
 
@@ -82,14 +82,14 @@ test_expect_success 'not prune recent checkouts' '
        mkdir -p .git/worktrees/jlm &&
        echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir &&
        rmdir zz &&
-       git prune --worktrees --verbose --expire=2.days.ago &&
+       git worktree prune --verbose --expire=2.days.ago &&
        test -d .git/worktrees/jlm
 '
 
 test_expect_success 'not prune proper checkouts' '
        test_when_finished rm -r .git/worktrees &&
        git checkout "--to=$PWD/nop" --detach master &&
-       git prune --worktrees &&
+       git worktree prune &&
        test -d .git/worktrees/nop
 '