{"thread":{"id":"40159","subject":"[PATCH v5 1/2] worktree: add 'for_each_worktree' function","startedAt":"2015-08-22T21:51:32Z","lastAt":"2015-08-25T03:01:33Z","messageCount":6,"participants":["Michael Rappazzo","Junio C Hamano","Eric Sunshine","Mikael Magnusson"],"isPatch":true,"patchVersion":5,"patchTotal":2},"messages":[{"id":"268519","messageId":"1440280294-50679-1-git-send-email-rappazzo@gmail.com","threadId":"40159","inReplyTo":null,"subject":"[PATCH v5 0/2] Worktree: for-each function and list command","fromName":"Michael Rappazzo","fromEmail":"rappazzo@gmail.com","sentAt":"2015-08-22T21:51:32Z","receivedAt":"2015-08-22T21:51:32Z","isPatch":true,"sender":{"key":"rappazzo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/525287?v=4"},"body":"Changes since v4:\n - reduced memory usage by reusing string buffer variables\n - re-scoped variables in the for-each function\n - added tests for 'worktree list' with bare repos\n\nNotes from previous discussion:\n - The following snippet:\n\n> +\t/* If the common_dir DOES NOT end with '/.git', then it is bare */\n> +\tmain_is_bare = !strbuf_strip_suffix(&worktree_path, \"/.git\");\n\nThis code is run when the current dir is in a linked worktree (not the primary \nworktree).  In that context, the git config _does_ report that 'core.bare' is\ntrue when the primary repo is bare, however `is_bare_repository()` returns\nfalse.  The worktree_path also needs to the \"/.git\" removed from it.  \nTherefore, I opted to keep the code like this.\n\n\nMichael Rappazzo (2):\n  worktree: add 'for_each_worktree' function\n  worktree: add 'list' command\n\n Documentation/git-worktree.txt |  11 +++-\n builtin/worktree.c             | 138 +++++++++++++++++++++++++++++++++++++++++\n t/t2027-worktree-list.sh       |  81 ++++++++++++++++++++++++\n 3 files changed, 229 insertions(+), 1 deletion(-)\n create mode 100755 t/t2027-worktree-list.sh\n\n-- \n2.5.0\n"},{"id":"268518","messageId":"1440280294-50679-2-git-send-email-rappazzo@gmail.com","threadId":"40159","inReplyTo":"1440280294-50679-1-git-send-email-rappazzo@gmail.com","subject":"[PATCH v5 1/2] worktree: add 'for_each_worktree' function","fromName":"Michael Rappazzo","fromEmail":"rappazzo@gmail.com","sentAt":"2015-08-22T21:51:33Z","receivedAt":"2015-08-22T21:51:33Z","isPatch":true,"sender":{"key":"rappazzo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/525287?v=4"},"body":"for_each_worktree iterates through each worktree and invokes a callback\nfunction.  The main worktree (if not bare) is always encountered first,\nfollowed by worktrees created by `git worktree add`.\n\nIf the callback function returns a non-zero value, iteration stops, and\nthe callback's return value is returned from the for_each_worktree\nfunction.\n\nSigned-off-by: Michael Rappazzo <rappazzo@gmail.com>\n---\n builtin/worktree.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 83 insertions(+)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 430b51e..7b3cb96 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -26,6 +26,14 @@ static int show_only;\n static int verbose;\n static unsigned long expire;\n \n+/*\n+ * The signature for the callback function for the for_each_worktree()\n+ * function below.  The memory pointed to by the callback arguments\n+ * is only guaranteed to be valid for the duration of a single\n+ * callback invocation.\n+ */\n+typedef int each_worktree_fn(const char *path, const char *git_dir, void *cb_data);\n+\n static int prune_worktree(const char *id, struct strbuf *reason)\n {\n \tstruct stat st;\n@@ -359,6 +367,81 @@ static int add(int ac, const char **av, const char *prefix)\n \treturn add_worktree(path, branch, &opts);\n }\n \n+/*\n+ * Iterate through each worktree and invoke the callback function.  If\n+ * the callback function returns non-zero, the iteration stops, and\n+ * this function returns that return value\n+ */\n+static int for_each_worktree(each_worktree_fn fn, void *cb_data)\n+{\n+\tstruct strbuf worktree_path = STRBUF_INIT;\n+\tstruct strbuf worktree_git = STRBUF_INIT;\n+\tconst char *common_dir;\n+\tint main_is_bare = 0;\n+\tint ret = 0;\n+\n+\tcommon_dir = get_git_common_dir();\n+\tif (!strcmp(common_dir, get_git_dir())) {\n+\t\t/* simple case - this is the main repo */\n+\t\tmain_is_bare = is_bare_repository();\n+\t\tif (!main_is_bare) {\n+\t\t\tstrbuf_addstr(&worktree_path, get_git_work_tree());\n+\t\t} else {\n+\t\t\tstrbuf_addstr(&worktree_path, common_dir);\n+\t\t}\n+\t} else {\n+\t\tstrbuf_addstr(&worktree_path, common_dir);\n+\t\t/* If the common_dir DOES NOT end with '/.git', then it is bare */\n+\t\tmain_is_bare = !strbuf_strip_suffix(&worktree_path, \"/.git\");\n+\t}\n+\tstrbuf_addstr(&worktree_git, worktree_path.buf);\n+\n+\tif (!main_is_bare) {\n+\t\tstrbuf_addstr(&worktree_git, \"/.git\");\n+\n+\t\tret = fn(worktree_path.buf, worktree_git.buf, cb_data);\n+\t\tif (ret)\n+\t\t\tgoto done;\n+\t}\n+\tstrbuf_addstr(&worktree_git, \"/worktrees\");\n+\n+\tif (is_directory(worktree_git.buf)) {\n+\t\tDIR *dir = opendir(worktree_git.buf);\n+\t\tif (dir) {\n+\t\t\tstruct stat st;\n+\t\t\tstruct dirent *d;\n+\t\t\tsize_t base_path_len = worktree_git.len;\n+\n+\t\t\twhile ((d = readdir(dir)) != NULL) {\n+\t\t\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tstrbuf_setlen(&worktree_git, base_path_len);\n+\t\t\t\tstrbuf_addf(&worktree_git, \"/%s/gitdir\", d->d_name);\n+\n+\t\t\t\tif (stat(worktree_git.buf, &st)) {\n+\t\t\t\t\tfprintf(stderr, \"Unable to read worktree git dir: %s\\n\", worktree_git.buf);\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tstrbuf_reset(&worktree_path);\n+\t\t\t\tstrbuf_read_file(&worktree_path, worktree_git.buf, st.st_size);\n+\t\t\t\tstrbuf_strip_suffix(&worktree_path, \"/.git\\n\");\n+\n+\t\t\t\tstrbuf_strip_suffix(&worktree_git, \"/gitdir\");\n+\t\t\t\tret = fn(worktree_path.buf, worktree_git.buf, cb_data);\n+\t\t\t\tif (ret)\n+\t\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\tclosedir(dir);\n+\t}\n+done:\n+\tstrbuf_release(&worktree_git);\n+\tstrbuf_release(&worktree_path);\n+\treturn ret;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n-- \n2.5.0\n"},{"id":"268520","messageId":"1440280294-50679-3-git-send-email-rappazzo@gmail.com","threadId":"40159","inReplyTo":"1440280294-50679-1-git-send-email-rappazzo@gmail.com","subject":"[PATCH v5 2/2] worktree: add 'list' command","fromName":"Michael Rappazzo","fromEmail":"rappazzo@gmail.com","sentAt":"2015-08-22T21:51:34Z","receivedAt":"2015-08-22T21:51:34Z","isPatch":true,"sender":{"key":"rappazzo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/525287?v=4"},"body":"'git worktree list' uses the for_each_worktree function to iterate,\nand outputs in the format: '<worktree>  (<short-ref>)'\n\nSigned-off-by: Michael Rappazzo <rappazzo@gmail.com>\n---\n Documentation/git-worktree.txt | 11 +++++-\n builtin/worktree.c             | 55 ++++++++++++++++++++++++++++\n t/t2027-worktree-list.sh       | 81 ++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 146 insertions(+), 1 deletion(-)\n create mode 100755 t/t2027-worktree-list.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex fb68156..e953b4e 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' [--path-only]\n \n DESCRIPTION\n -----------\n@@ -59,6 +60,12 @@ 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.  The default\n+format of the list includes the full path to the worktree and the branch or\n+revision that the head of that worktree is currently pointing to.\n+\n OPTIONS\n -------\n \n@@ -93,6 +100,9 @@ OPTIONS\n --expire <time>::\n \tWith `prune`, only expire unused working trees older than <time>.\n \n+--path-only\n+\tWith `list`, only show the worktree path\n+\n DETAILS\n -------\n Each linked working tree has a private sub-directory in the repository's\n@@ -167,7 +177,6 @@ performed manually, such as:\n - `remove` to remove a linked working tree and its administrative files (and\n   warn if the working tree is dirty)\n - `mv` to move or rename a working tree and update its administrative files\n-- `list` to list linked working trees\n - `lock` to prevent automatic pruning of administrative files (for instance,\n   for a working tree on a portable device)\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 7b3cb96..673f292 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -12,6 +12,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@@ -442,6 +443,58 @@ done:\n \treturn ret;\n }\n \n+/* list callback data */\n+struct list_opts {\n+\tint path_only;\n+};\n+\n+static int print_worktree_details(const char *path, const char *git_dir, void *cb_data)\n+{\n+\tstruct strbuf head_file = STRBUF_INIT;\n+\tstruct strbuf head_ref = STRBUF_INIT;\n+\tstruct stat st;\n+\tstruct list_opts *opts = cb_data;\n+\tconst char *ref_prefix = \"ref: refs/heads/\";\n+\n+\tstrbuf_addf(&head_file, \"%s/HEAD\", git_dir);\n+\tif (!opts->path_only && !stat(head_file.buf, &st)) {\n+\t\tstrbuf_read_file(&head_ref, head_file.buf, st.st_size);\n+\t\tstrbuf_strip_suffix(&head_ref, \"\\n\");\n+\n+\t\tif (starts_with(head_ref.buf, ref_prefix)) {\n+\t\t\t/* branch checked out */\n+\t\t\tstrbuf_remove(&head_ref, 0, strlen(ref_prefix));\n+\t\t/* } else {\n+\t\t *  headless -- no-op\n+\t\t */\n+\t\t}\n+\t\tprintf(\"%s  (%s)\\n\", path, head_ref.buf);\n+\t} else {\n+\t\tprintf(\"%s\\n\", path);\n+\t}\n+\n+\tstrbuf_release(&head_ref);\n+\tstrbuf_release(&head_file);\n+\treturn 0;\n+}\n+\n+static int list(int ac, const char **av, const char *prefix)\n+{\n+\tstruct list_opts opts;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"path-only\", &opts.path_only, N_(\"only show the path of the worktree\")),\n+\t\tOPT_END()\n+\t};\n+\n+\topts.path_only = 0;\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+\treturn for_each_worktree(&print_worktree_details, &opts);\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -454,5 +507,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..66a635a\n--- /dev/null\n+++ b/t/t2027-worktree-list.sh\n@@ -0,0 +1,81 @@\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+test_expect_success '\"list\" all worktrees from main' '\n+\techo \"$(git rev-parse --show-toplevel)  ($(git symbolic-ref --short HEAD))\" >expect &&\n+\tgit worktree add --detach here master &&\n+\techo \"$(git -C here rev-parse --show-toplevel)  ($(git -C here rev-parse HEAD))\" >>expect &&\n+\tgit worktree list >actual &&\n+\ttest_cmp expect actual &&\n+\trm -rf here &&\n+\tgit worktree prune\n+'\n+\n+test_expect_success '\"list\" all worktrees from linked' '\n+\techo \"$(git rev-parse --show-toplevel)  ($(git symbolic-ref --short HEAD))\" >expect &&\n+\tgit worktree add --detach here master &&\n+\techo \"$(git -C here rev-parse --show-toplevel)  ($(git -C here rev-parse HEAD))\" >>expect &&\n+\tgit -C here worktree list >actual &&\n+\ttest_cmp expect actual &&\n+\trm -rf here &&\n+\tgit worktree prune\n+'\n+\n+test_expect_success '\"list\" all worktrees from main --path-only' '\n+\tgit rev-parse --show-toplevel >expect &&\n+\tgit worktree add --detach here master &&\n+\tgit -C here rev-parse --show-toplevel >>expect &&\n+\tgit worktree list --path-only >actual &&\n+\ttest_cmp expect actual &&\n+\trm -rf here &&\n+\tgit worktree prune\n+'\n+\n+test_expect_success '\"list\" all worktrees from linked --path-only' '\n+\tgit rev-parse --show-toplevel >expect &&\n+\tgit worktree add --detach here master &&\n+\tgit -C here rev-parse --show-toplevel >>expect &&\n+\tgit -C here worktree list --path-only >actual &&\n+\ttest_cmp expect actual &&\n+\trm -rf here &&\n+\tgit worktree prune\n+'\n+\n+test_expect_success 'bare repo setup' '\n+\tgit init --bare bare1 &&\n+\techo \"data\" > file1 &&\n+\tgit add file1 &&\n+\tgit commit -m\"File1: add data\" &&\n+\tgit push bare1 master &&\n+\tgit reset --hard HEAD^\n+'\n+\n+test_expect_success '\"list\" all worktrees from bare main' '\n+\tgit -C bare1 worktree add --detach ../there master &&\n+\techo \"$(git -C there rev-parse --show-toplevel)  ($(git -C there rev-parse HEAD))\" >expect &&\n+\tgit -C bare1 worktree list >actual &&\n+\ttest_cmp expect actual &&\n+\trm -rf there &&\n+\tgit -C bare1 worktree prune\n+'\n+test_expect_success '\"list\" all worktrees from worktree with a bare main' '\n+\tgit -C bare1 worktree add --detach ../there master &&\n+\techo \"$(git -C there rev-parse --show-toplevel)  ($(git -C there rev-parse HEAD))\" >expect &&\n+\tgit -C there worktree list >actual &&\n+\ttest_cmp expect actual &&\n+\trm -rf there &&\n+\tgit -C bare1 worktree prune\n+'\n+\n+test_expect_success 'bare repo cleanup' '\n+\trm -rf bare1\n+'\n+\n+test_done\n-- \n2.5.0\n"},{"id":"268591","messageId":"xmqqpp2c4l5u.fsf@gitster.dls.corp.google.com","threadId":"40159","inReplyTo":"1440280294-50679-3-git-send-email-rappazzo@gmail.com","subject":"Re: [PATCH v5 2/2] worktree: add 'list' command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-08-24T18:05:17Z","receivedAt":"2015-08-24T18:05:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Rappazzo <rappazzo@gmail.com> writes:\n\n> +static int print_worktree_details(const char *path, const char *git_dir, void *cb_data)\n> +{\n> +\tstruct strbuf head_file = STRBUF_INIT;\n> +\tstruct strbuf head_ref = STRBUF_INIT;\n> +\tstruct stat st;\n> +\tstruct list_opts *opts = cb_data;\n> +\tconst char *ref_prefix = \"ref: refs/heads/\";\n> +\n> +\tstrbuf_addf(&head_file, \"%s/HEAD\", git_dir);\n> +\tif (!opts->path_only && !stat(head_file.buf, &st)) {\n> +\t\tstrbuf_read_file(&head_ref, head_file.buf, st.st_size);\n\nThis does not work for traditional \"symlinked HEAD\", no?\n\nI'd prefer to see the callback functions of for_each_worktree() not\nto duplicate the logic we already have elsewhere in the system.\nSuch an incomplete attempt to duplication (as we see here) will lead\nto unnecessary bugs (as we see here).\n\nConceptually, for-each-worktree should give us the worktree root\n(i.e. equivalent to $GIT_WORK_TREE in the pre-multi-worktree world)\nand the git directory (i.e. equivalent to $GIT_DIR in the\npre-multi-worktree world), and the callbacks should be able to do an\nequivalent of\n\n    system(\"git --work-tree=$GIT_WORK_TREE --git-dir=$GIT_DIR cmd\")\n\nwhere in this particular case \"cmd\" may be \"symbolic-ref HEAD\" or\nsomething, no?\n\n> +\t\tstrbuf_strip_suffix(&head_ref, \"\\n\");\n> +\n> +\t\tif (starts_with(head_ref.buf, ref_prefix)) {\n> +\t\t\t/* branch checked out */\n> +\t\t\tstrbuf_remove(&head_ref, 0, strlen(ref_prefix));\n> +\t\t/* } else {\n> +\t\t *  headless -- no-op\n> +\t\t */\n> +\t\t}\n> +\t\tprintf(\"%s  (%s)\\n\", path, head_ref.buf);\n\nIs this new command meant to be a Porcelain?  This would not work as\na plumbing that produces a machine-parseable stable output.\n\nI am not saying that it _should_; I do not know if we even need a\n'list' command that is driven from an end-user script that gives\nanything more than \"where the work trees are\".\n\nMy inclination is to suggest dropping the \"which branch\" code\naltogether and only give \"path_only\" behaviour.\n\nThanks.\n"},{"id":"268621","messageId":"CAPig+cRQ0bwhx9dB=j6VWY4EieJ6Emzqru9Wmwy=4tAFRt6Otg@mail.gmail.com","threadId":"40159","inReplyTo":"xmqqpp2c4l5u.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v5 2/2] worktree: add 'list' command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2015-08-25T01:07:43Z","receivedAt":"2015-08-25T01:07:43Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Aug 24, 2015 at 2:05 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Michael Rappazzo <rappazzo@gmail.com> writes:\n>> +             strbuf_strip_suffix(&head_ref, \"\\n\");\n>> +\n>> +             if (starts_with(head_ref.buf, ref_prefix)) {\n>> +                     /* branch checked out */\n>> +                     strbuf_remove(&head_ref, 0, strlen(ref_prefix));\n>> +             /* } else {\n>> +              *  headless -- no-op\n>> +              */\n>> +             }\n>> +             printf(\"%s  (%s)\\n\", path, head_ref.buf);\n>\n> Is this new command meant to be a Porcelain?  This would not work as\n> a plumbing that produces a machine-parseable stable output.\n>\n> I am not saying that it _should_; I do not know if we even need a\n> 'list' command that is driven from an end-user script that gives\n> anything more than \"where the work trees are\".\n>\n> My inclination is to suggest dropping the \"which branch\" code\n> altogether and only give \"path_only\" behaviour.\n\nThe \"which branch\" was probably added in response to this [1] review,\nwhich suggested that at some point, we might want to provide the user\nwith interesting information about each worktree, such as\nbranch/detached head, tag, locked status (plus lock reason and whether\ncurrently accessible), prune-able status (plus reason). This could\noptionally be controlled by --verbose or some other extended\nformatting option.\n\nThe same review also suggested a --porcelain option for script writers.\n\n[1]: http://article.gmane.org/gmane.comp.version-control.git/275528\n"},{"id":"268623","messageId":"CAHYJk3SaDAJ6rfB2=GtGdeL47=Ckyrktd3qEM-UBH4ZbPYtKgg@mail.gmail.com","threadId":"40159","inReplyTo":"1440280294-50679-3-git-send-email-rappazzo@gmail.com","subject":"Re: [PATCH v5 2/2] worktree: add 'list' command","fromName":"Mikael Magnusson","fromEmail":"mikachu@gmail.com","sentAt":"2015-08-25T03:01:33Z","receivedAt":"2015-08-25T03:01:33Z","isPatch":true,"sender":{"key":"mikachu@gmail.com","avatar":null},"body":"On Sat, Aug 22, 2015 at 11:51 PM, Michael Rappazzo <rappazzo@gmail.com> wrote:\n> 'git worktree list' uses the for_each_worktree function to iterate,\n> and outputs in the format: '<worktree>  (<short-ref>)'\n>\n> Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>\n> ---\n>  Documentation/git-worktree.txt | 11 +++++-\n>  builtin/worktree.c             | 55 ++++++++++++++++++++++++++++\n>  t/t2027-worktree-list.sh       | 81 ++++++++++++++++++++++++++++++++++++++++++\n>  3 files changed, 146 insertions(+), 1 deletion(-)\n>  create mode 100755 t/t2027-worktree-list.sh\n>\n> diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\n> index fb68156..e953b4e 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' [--path-only]\n>\n>  DESCRIPTION\n>  -----------\n> @@ -59,6 +60,12 @@ 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.  The default\n> +format of the list includes the full path to the worktree and the branch or\n> +revision that the head of that worktree is currently pointing to.\n\nMaybe just \"and the branch or revision currently checked out in that worktree.\"?\n\n-- \nMikael Magnusson\n"}]}