{"thread":{"id":"39744","subject":"[PATCH] worktree: new place for \"git prune --worktrees\"","startedAt":"2015-06-29T12:51:18Z","lastAt":"2015-07-01T10:38:49Z","messageCount":3,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano","Duy Nguyen"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"265120","messageId":"1435582278-31158-1-git-send-email-pclouds@gmail.com","threadId":"39744","inReplyTo":null,"subject":"[PATCH] worktree: new place for \"git prune --worktrees\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2015-06-29T12:51:18Z","receivedAt":"2015-06-29T12:51:18Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Commit 23af91d (prune: strategies for linked checkouts - 2014-11-30)\nadds \"--worktrees\" to \"git prune\" without realizing that \"git prune\" is\nfor object database only. This patch moves the same functionality to a\nnew command \"git worktree\".\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n In future I probably move the big block of text in git-checkout.txt to\n git-worktree.txt and add \"git worktree list\". But let's start with\n something small and simple before \"git prune --worktrees\" is shipped\n out.\n\n .gitignore                           |   1 +\n Documentation/git-prune.txt          |   3 -\n Documentation/git-worktree.txt (new) |  48 +++++++++++++\n Makefile                             |   1 +\n builtin.h                            |   1 +\n builtin/gc.c                         |   2 +-\n builtin/prune.c                      |  99 --------------------------\n builtin/worktree.c (new)             | 133 +++++++++++++++++++++++++++++++++++\n command-list.txt                     |   1 +\n git.c                                |   1 +\n t/t2026-prune-linked-checkouts.sh    |  22 +++---\n 11 files changed, 198 insertions(+), 114 deletions(-)\n create mode 100644 Documentation/git-worktree.txt\n create mode 100644 builtin/worktree.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 422c538..a685ec1 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -171,6 +171,7 @@\n /git-verify-tag\n /git-web--browse\n /git-whatchanged\n+/git-worktree\n /git-write-tree\n /git-core-*/?*\n /gitweb/GITWEB-BUILD-OPTIONS\ndiff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt\nindex 1cf3bed..7a493c8 100644\n--- a/Documentation/git-prune.txt\n+++ b/Documentation/git-prune.txt\n@@ -48,9 +48,6 @@ OPTIONS\n --expire <time>::\n \tOnly expire loose objects older than <time>.\n \n---worktrees::\n-\tPrune dead working tree information in $GIT_DIR/worktrees.\n-\n <head>...::\n \tIn addition to objects\n \treachable from any of our references, keep objects\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nnew file mode 100644\nindex 0000000..b9072e0\n--- /dev/null\n+++ b/Documentation/git-worktree.txt\n@@ -0,0 +1,48 @@\n+git-worktree(1)\n+===============\n+\n+NAME\n+----\n+git-worktree - Manage multiple worktrees\n+\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git worktree prune' [-n] [-v] [--expire <expire>]\n+\n+DESCRIPTION\n+-----------\n+\n+Manage multiple worktrees attached to the same repository. These are\n+created by the command `git checkout --to`.\n+\n+COMMANDS\n+--------\n+prune::\n+\n+Prune working tree information in $GIT_DIR/worktrees.\n+\n+OPTIONS\n+-------\n+\n+-n::\n+--dry-run::\n+\tDo not remove anything; just report what it would\n+\tremove.\n+\n+-v::\n+--verbose::\n+\tReport all removals.\n+\n+--expire <time>::\n+\tOnly expire unused worktrees older than <time>.\n+\n+SEE ALSO\n+--------\n+\n+linkgit:git-checkout[1]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 149f1c7..89785f6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -909,6 +909,7 @@ BUILTIN_OBJS += builtin/var.o\n BUILTIN_OBJS += builtin/verify-commit.o\n BUILTIN_OBJS += builtin/verify-pack.o\n BUILTIN_OBJS += builtin/verify-tag.o\n+BUILTIN_OBJS += builtin/worktree.o\n BUILTIN_OBJS += builtin/write-tree.o\n \n GITLIBS = $(LIB_FILE) $(XDIFF_LIB)\ndiff --git a/builtin.h b/builtin.h\nindex b87df70..9e04f97 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -133,6 +133,7 @@ extern int cmd_verify_commit(int argc, const char **argv, const char *prefix);\n extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);\n extern int cmd_version(int argc, const char **argv, const char *prefix);\n extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);\n+extern int cmd_worktree(int argc, const char **argv, const char *prefix);\n extern int cmd_write_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);\n extern int cmd_show_ref(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 36fe333..4957c39 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -293,7 +293,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)\n \targv_array_pushl(&reflog, \"reflog\", \"expire\", \"--all\", NULL);\n \targv_array_pushl(&repack, \"repack\", \"-d\", \"-l\", NULL);\n \targv_array_pushl(&prune, \"prune\", \"--expire\", NULL);\n-\targv_array_pushl(&prune_worktrees, \"prune\", \"--worktrees\", \"--expire\", NULL);\n+\targv_array_pushl(&prune_worktrees, \"worktree\", \"prune\", \"--expire\", NULL);\n \targv_array_pushl(&rerere, \"rerere\", \"gc\", NULL);\n \n \tgc_config();\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 0c73246..10b03d3 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -6,7 +6,6 @@\n #include \"reachable.h\"\n #include \"parse-options.h\"\n #include \"progress.h\"\n-#include \"dir.h\"\n \n static const char * const prune_usage[] = {\n \tN_(\"git prune [-n] [-v] [--expire <time>] [--] [<head>...]\"),\n@@ -76,95 +75,6 @@ static int prune_subdir(int nr, const char *path, void *data)\n \treturn 0;\n }\n \n-static int prune_worktree(const char *id, struct strbuf *reason)\n-{\n-\tstruct stat st;\n-\tchar *path;\n-\tint fd, len;\n-\n-\tif (!is_directory(git_path(\"worktrees/%s\", id))) {\n-\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: not a valid directory\"), id);\n-\t\treturn 1;\n-\t}\n-\tif (file_exists(git_path(\"worktrees/%s/locked\", id)))\n-\t\treturn 0;\n-\tif (stat(git_path(\"worktrees/%s/gitdir\", id), &st)) {\n-\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: gitdir file does not exist\"), id);\n-\t\treturn 1;\n-\t}\n-\tfd = open(git_path(\"worktrees/%s/gitdir\", id), O_RDONLY);\n-\tif (fd < 0) {\n-\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: unable to read gitdir file (%s)\"),\n-\t\t\t    id, strerror(errno));\n-\t\treturn 1;\n-\t}\n-\tlen = st.st_size;\n-\tpath = xmalloc(len + 1);\n-\tread_in_full(fd, path, len);\n-\tclose(fd);\n-\twhile (len && (path[len - 1] == '\\n' || path[len - 1] == '\\r'))\n-\t\tlen--;\n-\tif (!len) {\n-\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: invalid gitdir file\"), id);\n-\t\tfree(path);\n-\t\treturn 1;\n-\t}\n-\tpath[len] = '\\0';\n-\tif (!file_exists(path)) {\n-\t\tstruct stat st_link;\n-\t\tfree(path);\n-\t\t/*\n-\t\t * the repo is moved manually and has not been\n-\t\t * accessed since?\n-\t\t */\n-\t\tif (!stat(git_path(\"worktrees/%s/link\", id), &st_link) &&\n-\t\t    st_link.st_nlink > 1)\n-\t\t\treturn 0;\n-\t\tif (st.st_mtime <= expire) {\n-\t\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: gitdir file points to non-existent location\"), id);\n-\t\t\treturn 1;\n-\t\t} else {\n-\t\t\treturn 0;\n-\t\t}\n-\t}\n-\tfree(path);\n-\treturn 0;\n-}\n-\n-static void prune_worktrees(void)\n-{\n-\tstruct strbuf reason = STRBUF_INIT;\n-\tstruct strbuf path = STRBUF_INIT;\n-\tDIR *dir = opendir(git_path(\"worktrees\"));\n-\tstruct dirent *d;\n-\tint ret;\n-\tif (!dir)\n-\t\treturn;\n-\twhile ((d = readdir(dir)) != NULL) {\n-\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n-\t\t\tcontinue;\n-\t\tstrbuf_reset(&reason);\n-\t\tif (!prune_worktree(d->d_name, &reason))\n-\t\t\tcontinue;\n-\t\tif (show_only || verbose)\n-\t\t\tprintf(\"%s\\n\", reason.buf);\n-\t\tif (show_only)\n-\t\t\tcontinue;\n-\t\tstrbuf_reset(&path);\n-\t\tstrbuf_addstr(&path, git_path(\"worktrees/%s\", d->d_name));\n-\t\tret = remove_dir_recursively(&path, 0);\n-\t\tif (ret < 0 && errno == ENOTDIR)\n-\t\t\tret = unlink(path.buf);\n-\t\tif (ret)\n-\t\t\terror(_(\"failed to remove: %s\"), strerror(errno));\n-\t}\n-\tclosedir(dir);\n-\tif (!show_only)\n-\t\trmdir(git_path(\"worktrees\"));\n-\tstrbuf_release(&reason);\n-\tstrbuf_release(&path);\n-}\n-\n /*\n  * Write errors (particularly out of space) can result in\n  * failed temporary packs (and more rarely indexes and other\n@@ -191,12 +101,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n {\n \tstruct rev_info revs;\n \tstruct progress *progress = NULL;\n-\tint do_prune_worktrees = 0;\n \tconst struct option options[] = {\n \t\tOPT__DRY_RUN(&show_only, N_(\"do not remove, show only\")),\n \t\tOPT__VERBOSE(&verbose, N_(\"report pruned objects\")),\n \t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n-\t\tOPT_BOOL(0, \"worktrees\", &do_prune_worktrees, N_(\"prune .git/worktrees\")),\n \t\tOPT_EXPIRY_DATE(0, \"expire\", &expire,\n \t\t\t\tN_(\"expire objects older than <time>\")),\n \t\tOPT_END()\n@@ -211,13 +119,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, prune_usage, 0);\n \n-\tif (do_prune_worktrees) {\n-\t\tif (argc)\n-\t\t\tdie(_(\"--worktrees does not take extra arguments\"));\n-\t\tprune_worktrees();\n-\t\treturn 0;\n-\t}\n-\n \twhile (argc--) {\n \t\tunsigned char sha1[20];\n \t\tconst char *name = *argv++;\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nnew file mode 100644\nindex 0000000..2a729c6\n--- /dev/null\n+++ b/builtin/worktree.c\n@@ -0,0 +1,133 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"dir.h\"\n+#include \"parse-options.h\"\n+\n+static const char * const worktree_usage[] = {\n+\tN_(\"git worktree prune [<options>]\"),\n+\tNULL\n+};\n+\n+static int show_only;\n+static int verbose;\n+static unsigned long expire;\n+\n+static int prune_worktree(const char *id, struct strbuf *reason)\n+{\n+\tstruct stat st;\n+\tchar *path;\n+\tint fd, len;\n+\n+\tif (!is_directory(git_path(\"worktrees/%s\", id))) {\n+\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: not a valid directory\"), id);\n+\t\treturn 1;\n+\t}\n+\tif (file_exists(git_path(\"worktrees/%s/locked\", id)))\n+\t\treturn 0;\n+\tif (stat(git_path(\"worktrees/%s/gitdir\", id), &st)) {\n+\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: gitdir file does not exist\"), id);\n+\t\treturn 1;\n+\t}\n+\tfd = open(git_path(\"worktrees/%s/gitdir\", id), O_RDONLY);\n+\tif (fd < 0) {\n+\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: unable to read gitdir file (%s)\"),\n+\t\t\t    id, strerror(errno));\n+\t\treturn 1;\n+\t}\n+\tlen = st.st_size;\n+\tpath = xmalloc(len + 1);\n+\tread_in_full(fd, path, len);\n+\tclose(fd);\n+\twhile (len && (path[len - 1] == '\\n' || path[len - 1] == '\\r'))\n+\t\tlen--;\n+\tif (!len) {\n+\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: invalid gitdir file\"), id);\n+\t\tfree(path);\n+\t\treturn 1;\n+\t}\n+\tpath[len] = '\\0';\n+\tif (!file_exists(path)) {\n+\t\tstruct stat st_link;\n+\t\tfree(path);\n+\t\t/*\n+\t\t * the repo is moved manually and has not been\n+\t\t * accessed since?\n+\t\t */\n+\t\tif (!stat(git_path(\"worktrees/%s/link\", id), &st_link) &&\n+\t\t    st_link.st_nlink > 1)\n+\t\t\treturn 0;\n+\t\tif (st.st_mtime <= expire) {\n+\t\t\tstrbuf_addf(reason, _(\"Removing worktrees/%s: gitdir file points to non-existent location\"), id);\n+\t\t\treturn 1;\n+\t\t} else {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\tfree(path);\n+\treturn 0;\n+}\n+\n+static void prune_worktrees(void)\n+{\n+\tstruct strbuf reason = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tDIR *dir = opendir(git_path(\"worktrees\"));\n+\tstruct dirent *d;\n+\tint ret;\n+\tif (!dir)\n+\t\treturn;\n+\twhile ((d = readdir(dir)) != NULL) {\n+\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\tcontinue;\n+\t\tstrbuf_reset(&reason);\n+\t\tif (!prune_worktree(d->d_name, &reason))\n+\t\t\tcontinue;\n+\t\tif (show_only || verbose)\n+\t\t\tprintf(\"%s\\n\", reason.buf);\n+\t\tif (show_only)\n+\t\t\tcontinue;\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addstr(&path, git_path(\"worktrees/%s\", d->d_name));\n+\t\tret = remove_dir_recursively(&path, 0);\n+\t\tif (ret < 0 && errno == ENOTDIR)\n+\t\t\tret = unlink(path.buf);\n+\t\tif (ret)\n+\t\t\terror(_(\"failed to remove: %s\"), strerror(errno));\n+\t}\n+\tclosedir(dir);\n+\tif (!show_only)\n+\t\trmdir(git_path(\"worktrees\"));\n+\tstrbuf_release(&reason);\n+\tstrbuf_release(&path);\n+}\n+\n+static int prune(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT__DRY_RUN(&show_only, N_(\"do not remove, show only\")),\n+\t\tOPT__VERBOSE(&verbose, N_(\"report pruned objects\")),\n+\t\tOPT_EXPIRY_DATE(0, \"expire\", &expire,\n+\t\t\t\tN_(\"expire objects older than <time>\")),\n+\t\tOPT_END()\n+\t};\n+\n+\texpire = ULONG_MAX;\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac)\n+\t\tusage_with_options(worktree_usage, options);\n+\tprune_worktrees();\n+\treturn 0;\n+}\n+\n+int cmd_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\n+\tif (ac < 2)\n+\t\tusage_with_options(worktree_usage, options);\n+\tif (!strcmp(av[1], \"prune\"))\n+\t\treturn prune(ac - 1, av + 1, prefix);\n+\tusage_with_options(worktree_usage, options);\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex b17c011..2a94137 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -148,4 +148,5 @@ git-verify-pack                         plumbinginterrogators\n git-verify-tag                          ancillaryinterrogators\n gitweb                                  ancillaryinterrogators\n git-whatchanged                         ancillaryinterrogators\n+git-worktree                            mainporcelain\n git-write-tree                          plumbingmanipulators\ndiff --git a/git.c b/git.c\nindex 44374b1..fa77bc9 100644\n--- a/git.c\n+++ b/git.c\n@@ -483,6 +483,7 @@ static struct cmd_struct commands[] = {\n \t{ \"verify-tag\", cmd_verify_tag, RUN_SETUP },\n \t{ \"version\", cmd_version },\n \t{ \"whatchanged\", cmd_whatchanged, RUN_SETUP },\n+\t{ \"worktree\", cmd_worktree, RUN_SETUP },\n \t{ \"write-tree\", cmd_write_tree, RUN_SETUP },\n };\n \ndiff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-prune-linked-checkouts.sh\nindex 1821a48..e872f02 100755\n--- a/t/t2026-prune-linked-checkouts.sh\n+++ b/t/t2026-prune-linked-checkouts.sh\n@@ -8,15 +8,15 @@ test_expect_success initialize '\n \tgit commit --allow-empty -m init\n '\n \n-test_expect_success 'prune --worktrees on normal repo' '\n-\tgit prune --worktrees &&\n-\ttest_must_fail git prune --worktrees abc\n+test_expect_success 'worktree prune on normal repo' '\n+\tgit worktree prune &&\n+\ttest_must_fail git worktree prune abc\n '\n \n test_expect_success 'prune files inside $GIT_DIR/worktrees' '\n \tmkdir .git/worktrees &&\n \t: >.git/worktrees/abc &&\n-\tgit prune --worktrees --verbose >actual &&\n+\tgit worktree prune --verbose >actual &&\n \tcat >expect <<EOF &&\n Removing worktrees/abc: not a valid directory\n EOF\n@@ -31,7 +31,7 @@ test_expect_success 'prune directories without gitdir' '\n \tcat >expect <<EOF &&\n Removing worktrees/def: gitdir file does not exist\n EOF\n-\tgit prune --worktrees --verbose >actual &&\n+\tgit worktree prune --verbose >actual &&\n \ttest_i18ncmp expect actual &&\n \t! test -d .git/worktrees/def &&\n \t! test -d .git/worktrees\n@@ -42,7 +42,7 @@ test_expect_success SANITY 'prune directories with unreadable gitdir' '\n \t: >.git/worktrees/def/def &&\n \t: >.git/worktrees/def/gitdir &&\n \tchmod u-r .git/worktrees/def/gitdir &&\n-\tgit prune --worktrees --verbose >actual &&\n+\tgit worktree prune --verbose >actual &&\n \ttest_i18ngrep \"Removing worktrees/def: unable to read gitdir file\" actual &&\n \t! test -d .git/worktrees/def &&\n \t! test -d .git/worktrees\n@@ -52,7 +52,7 @@ test_expect_success 'prune directories with invalid gitdir' '\n \tmkdir -p .git/worktrees/def/abc &&\n \t: >.git/worktrees/def/def &&\n \t: >.git/worktrees/def/gitdir &&\n-\tgit prune --worktrees --verbose >actual &&\n+\tgit worktree prune --verbose >actual &&\n \ttest_i18ngrep \"Removing worktrees/def: invalid gitdir file\" actual &&\n \t! test -d .git/worktrees/def &&\n \t! test -d .git/worktrees\n@@ -62,7 +62,7 @@ test_expect_success 'prune directories with gitdir pointing to nowhere' '\n \tmkdir -p .git/worktrees/def/abc &&\n \t: >.git/worktrees/def/def &&\n \techo \"$(pwd)\"/nowhere >.git/worktrees/def/gitdir &&\n-\tgit prune --worktrees --verbose >actual &&\n+\tgit worktree prune --verbose >actual &&\n \ttest_i18ngrep \"Removing worktrees/def: gitdir file points to non-existent location\" actual &&\n \t! test -d .git/worktrees/def &&\n \t! test -d .git/worktrees\n@@ -72,7 +72,7 @@ test_expect_success 'not prune locked checkout' '\n \ttest_when_finished rm -r .git/worktrees &&\n \tmkdir -p .git/worktrees/ghi &&\n \t: >.git/worktrees/ghi/locked &&\n-\tgit prune --worktrees &&\n+\tgit worktree prune &&\n \ttest -d .git/worktrees/ghi\n '\n \n@@ -82,14 +82,14 @@ test_expect_success 'not prune recent checkouts' '\n \tmkdir -p .git/worktrees/jlm &&\n \techo \"$(pwd)\"/zz >.git/worktrees/jlm/gitdir &&\n \trmdir zz &&\n-\tgit prune --worktrees --verbose --expire=2.days.ago &&\n+\tgit worktree prune --verbose --expire=2.days.ago &&\n \ttest -d .git/worktrees/jlm\n '\n \n test_expect_success 'not prune proper checkouts' '\n \ttest_when_finished rm -r .git/worktrees &&\n \tgit checkout \"--to=$PWD/nop\" --detach master &&\n-\tgit prune --worktrees &&\n+\tgit worktree prune &&\n \ttest -d .git/worktrees/nop\n '\n \n-- \n2.3.0.rc1.137.g477eb31\n"},{"id":"265138","messageId":"xmqqh9pqcx1q.fsf@gitster.dls.corp.google.com","threadId":"39744","inReplyTo":"1435582278-31158-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH] worktree: new place for \"git prune --worktrees\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-06-29T16:13:37Z","receivedAt":"2015-06-29T16:13:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> Commit 23af91d (prune: strategies for linked checkouts - 2014-11-30)\n> adds \"--worktrees\" to \"git prune\" without realizing that \"git prune\" is\n> for object database only. This patch moves the same functionality to a\n> new command \"git worktree\".\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  In future I probably move the big block of text in git-checkout.txt to\n>  git-worktree.txt and add \"git worktree list\". But let's start with\n>  something small and simple before \"git prune --worktrees\" is shipped\n>  out.\n\nThanks.  I notice that after applying this, builtin/prune.c does not\nrevert to the original before 23af91d.  It stops including \"dir.h\"\nand it adds an extra blank line.  We've been including a header that\nis not necessary even in v2.4.0, it seems.  We used to walk\n$GIT_DIR/objects ourselves to find loose object files and \"dir.h\"\nwas needed for is_dot_or_dotdot(); for_each_loose_file_in_objdir()\nis what we use these days to hide the implementation details these\ndays; 27e1e22 forgot to remove the inclusion when it did this.\n\nThe C code part is mostly just \\C-x \\C-v and I found nothing\nquestionable.\n\n> diff --git a/command-list.txt b/command-list.txt\n> index b17c011..2a94137 100644\n> --- a/command-list.txt\n> +++ b/command-list.txt\n> @@ -148,4 +148,5 @@ git-verify-pack                         plumbinginterrogators\n>  git-verify-tag                          ancillaryinterrogators\n>  gitweb                                  ancillaryinterrogators\n>  git-whatchanged                         ancillaryinterrogators\n> +git-worktree                            mainporcelain\n\nI doubt that a helper that is primarily spawned from \"gc\" as its\nimplementation detail is more mainporcelain than \"git config\" is:\n\n   git-config                              ancillarymanipulators\n\nI also wonder if \"git worktree\" command should have a mode that\nworks in a way similar to how new-workdir (in contrib/workdir) does,\ninstead of an option \"checkout --to\" that looks just out of place as\n\"worktree prune\" was out of place in \"prune\".  The feature is doing\na lot more than what \"checkout\" normally does (somewhere in between\n\"checkout\" and \"clone\", I would say), and it may be cleaner to use\nan independent command \"git worktree\" to manage a separate worktree.\n\nAnd when that happens, the command should definitely be classified\nas a mainporcelain.\n\n> diff --git a/git.c b/git.c\n> index 44374b1..fa77bc9 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -483,6 +483,7 @@ static struct cmd_struct commands[] = {\n>  \t{ \"verify-tag\", cmd_verify_tag, RUN_SETUP },\n>  \t{ \"version\", cmd_version },\n>  \t{ \"whatchanged\", cmd_whatchanged, RUN_SETUP },\n> +\t{ \"worktree\", cmd_worktree, RUN_SETUP },\n\nWe do not NEED_WORK_TREE because we can create a new worktree\noff of a bare repository?\n"},{"id":"265317","messageId":"CACsJy8ASdbmHuJn_og_jHxHn_h0gZ-30ieD=vwMJ6O-j=1hGGQ@mail.gmail.com","threadId":"39744","inReplyTo":"xmqqh9pqcx1q.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH] worktree: new place for \"git prune --worktrees\"","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2015-07-01T10:38:49Z","receivedAt":"2015-07-01T10:38:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, Jun 29, 2015 at 11:13 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> Commit 23af91d (prune: strategies for linked checkouts - 2014-11-30)\n>> adds \"--worktrees\" to \"git prune\" without realizing that \"git prune\" is\n>> for object database only. This patch moves the same functionality to a\n>> new command \"git worktree\".\n>>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>>  In future I probably move the big block of text in git-checkout.txt to\n>>  git-worktree.txt and add \"git worktree list\". But let's start with\n>>  something small and simple before \"git prune --worktrees\" is shipped\n>>  out.\n>\n> Thanks.  I notice that after applying this, builtin/prune.c does not\n> revert to the original before 23af91d.  It stops including \"dir.h\"\n> and it adds an extra blank line.  We've been including a header that\n> is not necessary even in v2.4.0, it seems.  We used to walk\n> $GIT_DIR/objects ourselves to find loose object files and \"dir.h\"\n> was needed for is_dot_or_dotdot(); for_each_loose_file_in_objdir()\n> is what we use these days to hide the implementation details these\n> days; 27e1e22 forgot to remove the inclusion when it did this.\n>\n> The C code part is mostly just \\C-x \\C-v and I found nothing\n> questionable.\n\nI was about to prepare v2, but the only change in the end was removing\nthis blank line, not worth another round.\n\n>> diff --git a/command-list.txt b/command-list.txt\n>> index b17c011..2a94137 100644\n>> --- a/command-list.txt\n>> +++ b/command-list.txt\n>> @@ -148,4 +148,5 @@ git-verify-pack                         plumbinginterrogators\n>>  git-verify-tag                          ancillaryinterrogators\n>>  gitweb                                  ancillaryinterrogators\n>>  git-whatchanged                         ancillaryinterrogators\n>> +git-worktree                            mainporcelain\n>\n> I doubt that a helper that is primarily spawned from \"gc\" as its\n> implementation detail is more mainporcelain than \"git config\" is:\n>\n>    git-config                              ancillarymanipulators\n>\n> I also wonder if \"git worktree\" command should have a mode that\n> works in a way similar to how new-workdir (in contrib/workdir) does,\n> instead of an option \"checkout --to\" that looks just out of place as\n> \"worktree prune\" was out of place in \"prune\".  The feature is doing\n> a lot more than what \"checkout\" normally does (somewhere in between\n> \"checkout\" and \"clone\", I would say), and it may be cleaner to use\n> an independent command \"git worktree\" to manage a separate worktree.\n>\n> And when that happens, the command should definitely be classified\n> as a mainporcelain.\n\nAnd because \"git worktree add\" is coming, I guess it's ok to stick to\nmainporcelain here..\n\n>> diff --git a/git.c b/git.c\n>> index 44374b1..fa77bc9 100644\n>> --- a/git.c\n>> +++ b/git.c\n>> @@ -483,6 +483,7 @@ static struct cmd_struct commands[] = {\n>>       { \"verify-tag\", cmd_verify_tag, RUN_SETUP },\n>>       { \"version\", cmd_version },\n>>       { \"whatchanged\", cmd_whatchanged, RUN_SETUP },\n>> +     { \"worktree\", cmd_worktree, RUN_SETUP },\n>\n> We do not NEED_WORK_TREE because we can create a new worktree\n> off of a bare repository?\n\nYes.\n-- \nDuy\n"}]}