{"thread":{"id":"40039","subject":"[PATCH v2] worktree: list operation","startedAt":"2015-08-09T00:19:58Z","lastAt":"2015-08-09T07:45:43Z","messageCount":4,"participants":["Michael Rappazzo","Andreas Schwab","Eric Sunshine"],"isPatch":true,"patchVersion":2,"patchTotal":null},"messages":[{"id":"267662","messageId":"1439079599-87904-1-git-send-email-rappazzo@gmail.com","threadId":"40039","inReplyTo":null,"subject":"[PATCH v2] worktree: list operation","fromName":"Michael Rappazzo","fromEmail":"rappazzo@gmail.com","sentAt":"2015-08-09T00:19:58Z","receivedAt":"2015-08-09T00:19:58Z","isPatch":true,"sender":{"key":"rappazzo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/525287?v=4"},"body":"I am attempting to add the 'git worktree list' command.  \n\nI don't have a lot of c experience, so please double check that I am \nnot missing something important.\n\nSorry about publishing the first version too soon.\n\nMichael Rappazzo (1):\n  worktree:  list operation\n\n Documentation/git-worktree.txt |  9 ++++-\n builtin/worktree.c             | 80 ++++++++++++++++++++++++++++++++++++++----\n t/t2027-worktree-list.sh       | 68 +++++++++++++++++++++++++++++++++++\n 3 files changed, 150 insertions(+), 7 deletions(-)\n create mode 100755 t/t2027-worktree-list.sh\n\n-- \n2.5.0\n"},{"id":"267663","messageId":"1439079599-87904-2-git-send-email-rappazzo@gmail.com","threadId":"40039","inReplyTo":"1439079599-87904-1-git-send-email-rappazzo@gmail.com","subject":"[PATCH v2] worktree: list operation","fromName":"Michael Rappazzo","fromEmail":"rappazzo@gmail.com","sentAt":"2015-08-09T00:19:59Z","receivedAt":"2015-08-09T00:19:59Z","isPatch":true,"sender":{"key":"rappazzo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/525287?v=4"},"body":"'git worktree list' will list the main worktree followed by any linked\nworktrees which were created using 'git worktree add'.  The option\n'--main-only' will restrict the list to only the main worktree.\n---\n Documentation/git-worktree.txt |  9 ++++-\n builtin/worktree.c             | 84 ++++++++++++++++++++++++++++++++++++++----\n t/t2027-worktree-list.sh       | 68 ++++++++++++++++++++++++++++++++++\n 3 files changed, 152 insertions(+), 9 deletions(-)\n create mode 100755 t/t2027-worktree-list.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 3387e2f..2b6b543 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n [verse]\n 'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n+'git worktree list' [--main-only]\n \n DESCRIPTION\n -----------\n@@ -59,6 +60,10 @@ prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n \n+list::\n+\n+List the main worktree followed by all of the linked worktrees.\n+\n OPTIONS\n -------\n \n@@ -86,6 +91,9 @@ OPTIONS\n \tWith `prune`, do not remove anything; just report what it would\n \tremove.\n \n+--main-only::\n+\tWith `list`, only list the main worktree.\n+\n -v::\n --verbose::\n \tWith `prune`, report all removals.\n@@ -167,7 +175,6 @@ performed manually, such as:\n - `remove` to remove a linked worktree and its administrative files (and\n   warn if the worktree is dirty)\n - `mv` to move or rename a worktree and update its administrative files\n-- `list` to list linked worktrees\n - `lock` to prevent automatic pruning of administrative files (for instance,\n   for a worktree on a portable device)\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 6a264ee..8c4a82a 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -10,6 +10,7 @@\n static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> <branch>\"),\n \tN_(\"git worktree prune [<options>]\"),\n+\tN_(\"git worktree list [<options>]\"),\n \tNULL\n };\n \n@@ -36,7 +37,7 @@ static int prune_worktree(const char *id, struct strbuf *reason)\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\t\tid, strerror(errno));\n \t\treturn 1;\n \t}\n \tlen = st.st_size;\n@@ -59,7 +60,7 @@ static int prune_worktree(const char *id, struct strbuf *reason)\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\tst_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@@ -187,11 +188,11 @@ static int add_worktree(const char *path, const char **child_argv)\n \n \tname = worktree_basename(path, &len);\n \tstrbuf_addstr(&sb_repo,\n-\t\t      git_path(\"worktrees/%.*s\", (int)(path + len - name), name));\n+\t\t\t\tgit_path(\"worktrees/%.*s\", (int)(path + len - name), name));\n \tlen = sb_repo.len;\n \tif (safe_create_leading_directories_const(sb_repo.buf))\n \t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n-\t\t\t  sb_repo.buf);\n+\t\t\tsb_repo.buf);\n \twhile (!stat(sb_repo.buf, &st)) {\n \t\tcounter++;\n \t\tstrbuf_setlen(&sb_repo, len);\n@@ -218,14 +219,14 @@ static int add_worktree(const char *path, const char **child_argv)\n \tstrbuf_addf(&sb_git, \"%s/.git\", path);\n \tif (safe_create_leading_directories_const(sb_git.buf))\n \t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n-\t\t\t  sb_git.buf);\n+\t\t\tsb_git.buf);\n \tjunk_work_tree = xstrdup(path);\n \n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/gitdir\", sb_repo.buf);\n \twrite_file(sb.buf, 1, \"%s\\n\", real_path(sb_git.buf));\n \twrite_file(sb_git.buf, 1, \"gitdir: %s/worktrees/%s\\n\",\n-\t\t   real_path(get_git_common_dir()), name);\n+\t\t\treal_path(get_git_common_dir()), name);\n \t/*\n \t * This is to keep resolve_ref() happy. We need a valid HEAD\n \t * or is_git_directory() will reject the directory. Moreover, HEAD\n@@ -280,9 +281,9 @@ static int add(int ac, const char **av, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT__FORCE(&force, N_(\"checkout <branch> even if already checked out in other worktree\")),\n \t\tOPT_STRING('b', NULL, &new_branch, N_(\"branch\"),\n-\t\t\t   N_(\"create a new branch\")),\n+\t\t\t\tN_(\"create a new branch\")),\n \t\tOPT_STRING('B', NULL, &new_branch_force, N_(\"branch\"),\n-\t\t\t   N_(\"create or reset a branch\")),\n+\t\t\t\tN_(\"create or reset a branch\")),\n \t\tOPT_BOOL(0, \"detach\", &detach, N_(\"detach HEAD at named commit\")),\n \t\tOPT_END()\n \t};\n@@ -316,6 +317,71 @@ static int add(int ac, const char **av, const char *prefix)\n \treturn add_worktree(path, cmd.argv);\n }\n \n+static int list(int ac, const char **av, const char *prefix)\n+{\n+\tint main_only = 0;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"main-only\", &main_only, N_(\"only list the main worktree\")),\n+\t\tOPT_END()\n+\t};\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tconst char *work_tree;\n+\twork_tree = get_git_work_tree();\n+\tif (!work_tree)\n+\t\tdie(\"This operation must be run in a work tree\");\n+\n+\tstruct strbuf worktree_git_path = STRBUF_INIT;\n+\tstrbuf_addf(&worktree_git_path, _(\"%s/.git\"), work_tree);\n+\n+\tstruct strbuf main_work_tree = STRBUF_INIT;\n+\tif (is_directory(worktree_git_path.buf)) {\n+\t\t/* This is the main tree */\n+\t\tstrbuf_addstr(&main_work_tree, work_tree);\n+\t} else {\n+\t\tconst char *git_dir = get_git_dir();\n+\t\tstrbuf_addf(&main_work_tree, \"%.*s\", (int)(strstr(git_dir, \"/.git/\") - git_dir), git_dir);\n+\t}\n+\tprintf(\"%s\\n\", main_work_tree.buf);\n+\n+\tif (!main_only) {\n+\t\tchdir( main_work_tree.buf );\n+\t\tif ( is_directory(git_path(\"worktrees\")) ) {\n+\t\t\tDIR *dir = opendir( git_path(\"worktrees\") );\n+\t\t\tif (dir != NULL) {\n+\t\t\t\tstruct dirent *d;\n+\t\t\t\tstruct stat st;\n+\t\t\t\tchar *path;\n+\t\t\t\tint fd, len;\n+\t\t\t\twhile ((d = readdir(dir)) != NULL) {\n+\t\t\t\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tif (stat(git_path(\"worktrees/%s/gitdir\", d->d_name), &st))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tfd = open(git_path(\"worktrees/%s/gitdir\", d->d_name), O_RDONLY);\n+\t\t\t\t\tif (fd < 0)\n+\t\t\t\t\t\tcontinue;\n+\n+\t\t\t\t\tlen = st.st_size;\n+\t\t\t\t\tpath = xmalloc(len + 1);\n+\t\t\t\t\tread_in_full(fd, path, len);\n+\t\t\t\t\tclose(fd);\n+\n+\t\t\t\t\tprintf(\"%.*s\\n\", (int)(strstr(path, \"/.git\") - path), path);\n+\t\t\t\t\tfree(path);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tclosedir(dir);\n+\t\t}\n+\t}\n+\tstrbuf_release(&main_work_tree);\n+\tstrbuf_release(&worktree_git_path);\n+\treturn 0;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -328,5 +394,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn add(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"prune\"))\n \t\treturn prune(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"list\"))\n+\t\treturn list(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh\nnew file mode 100755\nindex 0000000..998b34f\n--- /dev/null\n+++ b/t/t2027-worktree-list.sh\n@@ -0,0 +1,68 @@\n+#!/bin/sh\n+\n+test_description='test git worktree list'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit init\n+'\n+\n+\n+test_expect_success '\"list\" all worktrees from main' '\n+\torig_path=$PWD &&\n+\tgit rev-parse --show-toplevel >expect &&\n+\tgit worktree add --detach here master &&\n+\t(\n+\t\tcd here &&\n+\t\tgit rev-parse --show-toplevel >>\"$orig_path/expect\" &&\n+\t\tcd \"$orig_path\" &&\n+\t\tgit worktree list >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\trm -rf here &&\n+\t\tgit worktree prune\n+\t)\n+'\n+test_expect_success '\"list\" all worktrees from linked' '\n+\torig_path=$PWD &&\n+\tgit rev-parse --show-toplevel >expect &&\n+\tgit worktree add --detach here master &&\n+\t(\n+\t\tcd here &&\n+\t\tgit rev-parse --show-toplevel >>\"$orig_path/expect\" &&\n+\t\tgit worktree list >actual &&\n+\t\ttest_cmp \"$orig_path/expect\" actual &&\n+\t\tcd \"$orig_path\" &&\n+\t\trm -rf here &&\n+\t\tgit worktree prune\n+\t)\n+'\n+\n+test_expect_success '\"list\" main worktree from main' '\n+\torig_path=$PWD &&\n+\tgit rev-parse --show-toplevel >expect &&\n+\tgit worktree add --detach here master &&\n+\t(\n+\t\tcd here &&\n+\t\tcd \"$orig_path\" &&\n+\t\tgit worktree list --main-only >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\trm -rf here &&\n+\t\tgit worktree prune\n+\t)\n+'\n+test_expect_success '\"list\" main worktree from linked' '\n+\torig_path=$PWD &&\n+\tgit rev-parse --show-toplevel >expect &&\n+\tgit worktree add --detach here master &&\n+\t(\n+\t\tcd here &&\n+\t\tgit worktree list --main-only >actual &&\n+\t\ttest_cmp \"$orig_path/expect\" actual &&\n+\t\tcd \"$orig_path\" &&\n+\t\trm -rf here &&\n+\t\tgit worktree prune\n+\t)\n+'\n+\n+test_done\n-- \n2.5.0\n"},{"id":"267667","messageId":"m2vbcpnedg.fsf@linux-m68k.org","threadId":"40039","inReplyTo":"1439079599-87904-2-git-send-email-rappazzo@gmail.com","subject":"Re: [PATCH v2] worktree: list operation","fromName":"Andreas Schwab","fromEmail":"schwab@linux-m68k.org","sentAt":"2015-08-09T06:51:39Z","receivedAt":"2015-08-09T06:51:39Z","isPatch":true,"sender":{"key":"schwab@linux-m68k.org","avatar":"https://avatars.githubusercontent.com/u/2175493?v=4"},"body":"Michael Rappazzo <rappazzo@gmail.com> writes:\n\n> @@ -36,7 +37,7 @@ static int prune_worktree(const char *id, struct strbuf *reason)\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\t\tid, strerror(errno));\n\nThere are still a lot of spurious whitespace changes.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n"},{"id":"267669","messageId":"CAPig+cTTrv2C7JLu1dr4+N8xo+7YQ+deiwLDA835wBGD6fhS1g@mail.gmail.com","threadId":"40039","inReplyTo":"1439079599-87904-2-git-send-email-rappazzo@gmail.com","subject":"Re: [PATCH v2] worktree: list operation","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2015-08-09T07:45:43Z","receivedAt":"2015-08-09T07:45:43Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Thanks for the patch. Comments below...\n\nOn Sat, Aug 8, 2015 at 8:19 PM, Michael Rappazzo <rappazzo@gmail.com> wrote:\n> worktree: list operation\n\nImperative mood:\n\n    worktree: add 'list' command\n\n> 'git worktree list' will list the main worktree followed by any linked\n> worktrees which were created using 'git worktree add'.  The option\n> '--main-only' will restrict the list to only the main worktree.\n\nMissing sign-off.\n\n> ---\n> diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\n> index 3387e2f..2b6b543 100644\n> --- a/Documentation/git-worktree.txt\n> +++ b/Documentation/git-worktree.txt\n> @@ -59,6 +60,10 @@ prune::\n> +list::\n> +\n> +List the main worktree followed by all of the linked worktrees.\n> +\n>  OPTIONS\n>  -------\n> @@ -86,6 +91,9 @@ OPTIONS\n> +--main-only::\n> +       With `list`, only list the main worktree.\n\nConsidering that the main worktree is always listed first, I wonder\nhow much value the --main-only option has. That is (Windows aside),\nthe same can easily be achieved via:\n\n    git worktree list | head -1\n\nThe more options we have, the more we have to document, test, and\nsupport, so I'm feeling skeptical about --main-only since it can be\neasily handled externally (for instance, via \"head\").\n\n>  -v::\n>  --verbose::\n>         With `prune`, report all removals.\n> @@ -36,7 +37,7 @@ static int prune_worktree(const char *id, struct strbuf *reason)\n>         fd = open(git_path(\"worktrees/%s/gitdir\", id), O_RDONLY);\n>         if (fd < 0) {\n>                 strbuf_addf(reason, _(\"Removing worktrees/%s: unable to read gitdir file (%s)\"),\n> -                           id, strerror(errno));\n> +                       id, strerror(errno));\n\nUnintended whitespace change here (and 7 places below)? The\nindentation looks fine as-is, so I think you don't want to include\nthese changes.\n\n>                 return 1;\n>         }\n>         len = st.st_size;\n> @@ -316,6 +317,71 @@ static int add(int ac, const char **av, const char *prefix)\n> +static int list(int ac, const char **av, const char *prefix)\n> +{\n> +       int main_only = 0;\n> +       struct option options[] = {\n> +               OPT_BOOL(0, \"main-only\", &main_only, N_(\"only list the main worktree\")),\n> +               OPT_END()\n> +       };\n> +\n> +       ac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n> +       if (ac)\n> +               usage_with_options(worktree_usage, options);\n> +\n> +       const char *work_tree;\n> +       work_tree = get_git_work_tree();\n> +       if (!work_tree)\n> +               die(\"This operation must be run in a work tree\");\n\nWhy this restriction? Other git-worktree operations support a bare\nrepository, so this one ought to also.\n\n(In fact, when dealing with a bare repository, the \"main worktree\" is\nreally the bare repository, so the term \"worktree\" is a bit of a\nmisnomer.)\n\n> +       struct strbuf worktree_git_path = STRBUF_INIT;\n> +       strbuf_addf(&worktree_git_path, _(\"%s/.git\"), work_tree);\n> +\n> +       struct strbuf main_work_tree = STRBUF_INIT;\n> +       if (is_directory(worktree_git_path.buf)) {\n\nThis is probably not a valid test. Even in the main worktree, \".git\"\nmay not be a directory; it could be a symref to the actual repository.\n\n> +               /* This is the main tree */\n> +               strbuf_addstr(&main_work_tree, work_tree);\n> +       } else {\n> +               const char *git_dir = get_git_dir();\n> +               strbuf_addf(&main_work_tree, \"%.*s\", (int)(strstr(git_dir, \"/.git/\") - git_dir), git_dir);\n> +       }\n> +       printf(\"%s\\n\", main_work_tree.buf);\n\nThis can probably all be done more simply by taking a hint from the\ncode which tests if a branch is already checked out in the main or a\nlinked worktree. For the main worktree, it just uses\nget_git_common_dir() and strips the trailing \".git\" and prints that.\nFor instance:\n\n    strbuf_addstr(&sb, get_git_common_dir());\n    strbuf_strip_suffix(&sb, \".git\");\n    printf(...);\n\nshould probably suffice in place of all the above code.\n\n> +       if (!main_only) {\n> +               chdir( main_work_tree.buf );\n\nStyle: Drop spaces inside parentheses.\n\nI realize that the program exits after printing the list of worktrees,\nbut this chdir() makes me uncomfortable, partly because it's not\nnecessary, and partly because it could negatively impact code which\nsomeone later adds to extend \"list\", if that new code expects the\ncurrent working directory to be the top of the worktree (as most git\ncode assumes).\n\n> +               if ( is_directory(git_path(\"worktrees\")) ) {\n> +                       DIR *dir = opendir( git_path(\"worktrees\") );\n\nStyle: Drop spaces inside parentheses (both lines).\n\nThe opendir() will fail if the directory doesn't exist anyhow, so the\n'if (is_directory(...))' is superfluous and can be dropped altogether.\n\nTaking a hint from the code which tests if a branch is already checked\nout elsewhere:\n\n    strbuf_addf(&sb, \"%s/worktrees\", get_git_common_dir());\n    dir = opendir(sb.buf);\n\n> +                       if (dir != NULL) {\n\nStyle: if (dir) {\n\n> +                               struct dirent *d;\n> +                               struct stat st;\n> +                               char *path;\n> +                               int fd, len;\n> +                               while ((d = readdir(dir)) != NULL) {\n> +                                       if (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n> +                                               continue;\n\nThis is at least the third bit of functionality which needs to iterate\nover the linked worktrees and glean information about them. As such,\nit would make sense to factor out that logic so that it can be\nre-used, as one or more preparatory patches if you feel so inclined\n(though is not a requirement by any means).\n\n> +                                       if (stat(git_path(\"worktrees/%s/gitdir\", d->d_name), &st))\n> +                                               continue;\n> +                                       fd = open(git_path(\"worktrees/%s/gitdir\", d->d_name), O_RDONLY);\n> +                                       if (fd < 0)\n> +                                               continue;\n> +\n> +                                       len = st.st_size;\n> +                                       path = xmalloc(len + 1);\n> +                                       read_in_full(fd, path, len);\n> +                                       close(fd);\n\nI realize that this code was pretty much copied from elsewhere in\nworktree.c, but it could be simplified considerably by taking\nadvantage of strbuf_read_file() to slurp the entire file into a\nstrbuf, thus allowing you to drop the stat(), open(), xmalloc(),\nread_in_full(), and close() calls:\n\n    if (strbuf_read_file(&sb, \"whatever/.../gitdir\", 0) < 0)\n        continue;\n     strbuf_rtrim(&sb);\n     strbuf_strip_suffix(&sb, \".git\");\n\n> +                                       printf(\"%.*s\\n\", (int)(strstr(path, \"/.git\") - path), path);\n\nFor git-worktree commands such as \"lock\", \"mv\", \"remove\", it likely\nwill be nice to allow people to specify the linked worktree not only\nby path, but also by tag, and possibly even by $(basename $path) if\nnot ambiguous. Therefore, to support such usage, at minimum, I think\nyou also want to show the worktree's tag (d->d_name) in addition to\nthe path.\n\nOther information which would be nice to display for each worktree\n(possibly controlled by a --verbose flag):\n\n   * the checked out branch or detached head\n   * whether it is locked\n        - the lock reason (if available)\n        - and whether the worktree is currently accessible\n    * whether it can be pruned\n        - and the prune reason if so\n\nThe prune reason could be obtained by factoring out the\nreason-determination code from worktree.c:prune_worktree() to make it\nre-usable.\n\nFor scripters, a --porcelain option might be useful.\n\nNone of this additional functionality is an immediate requirement, and\nwouldn't belong in this patch anyhow, but can be added via follow-up\npatches (if you or someone else is interested in the task).\n\n> +                                       free(path);\n> +                               }\n> +                       }\n> +                       closedir(dir);\n> +               }\n> +       }\n> +       strbuf_release(&main_work_tree);\n> +       strbuf_release(&worktree_git_path);\n> +       return 0;\n> +}\n> diff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh\n> new file mode 100755\n> index 0000000..998b34f\n> --- /dev/null\n> +++ b/t/t2027-worktree-list.sh\n> @@ -0,0 +1,68 @@\n> +#!/bin/sh\n> +\n> +test_description='test git worktree list'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'setup' '\n> +       test_commit init\n> +'\n> +\n> +\n\nDrop the extra blank line.\n\n> +test_expect_success '\"list\" all worktrees from main' '\n> +       orig_path=$PWD &&\n> +       git rev-parse --show-toplevel >expect &&\n> +       git worktree add --detach here master &&\n> +       (\n> +               cd here &&\n> +               git rev-parse --show-toplevel >>\"$orig_path/expect\" &&\n> +               cd \"$orig_path\" &&\n\nThe normal and automatic way to return to the original directory is\nsimply to end the subshell; that is, have the closing ')' at this\npoint. No need for 'orig_path'.\n\nIn fact, in this case, you don't even need a subshell at all because,\nthese days, you don't need to 'cd here'. Instead, take advantage of\n-C:\n\n    git -C here rev-parse --show-toplevel >>expect &&\n\n> +               git worktree list >actual &&\n> +               test_cmp expect actual &&\n> +               rm -rf here &&\n> +               git worktree prune\n> +       )\n> +'\n> +test_expect_success '\"list\" all worktrees from linked' '\n> +       orig_path=$PWD &&\n> +       git rev-parse --show-toplevel >expect &&\n> +       git worktree add --detach here master &&\n> +       (\n> +               cd here &&\n> +               git rev-parse --show-toplevel >>\"$orig_path/expect\" &&\n> +               git worktree list >actual &&\n> +               test_cmp \"$orig_path/expect\" actual &&\n> +               cd \"$orig_path\" &&\n\nDitto. Just end the subshell to return to the original directory. Or,\nbetter, ditch the subshell entirely and use -C:\n\n    git rev-parse -C here --show-toplevel >>expect &&\n    git -C here worktree list >actual &&\n\n> +               rm -rf here &&\n> +               git worktree prune\n> +       )\n> +'\n> +\n> +test_expect_success '\"list\" main worktree from main' '\n> +       orig_path=$PWD &&\n> +       git rev-parse --show-toplevel >expect &&\n> +       git worktree add --detach here master &&\n> +       (\n> +               cd here &&\n> +               cd \"$orig_path\" &&\n> +               git worktree list --main-only >actual &&\n> +               test_cmp expect actual &&\n> +               rm -rf here &&\n> +               git worktree prune\n> +       )\n> +'\n> +test_expect_success '\"list\" main worktree from linked' '\n> +       orig_path=$PWD &&\n> +       git rev-parse --show-toplevel >expect &&\n> +       git worktree add --detach here master &&\n> +       (\n> +               cd here &&\n> +               git worktree list --main-only >actual &&\n> +               test_cmp \"$orig_path/expect\" actual &&\n> +               cd \"$orig_path\" &&\n> +               rm -rf here &&\n> +               git worktree prune\n> +       )\n> +'\n> +\n> +test_done\n> --\n> 2.5.0\n"}]}