{"thread":{"id":"42034","subject":"possible to checkout same branch in different worktree","startedAt":"2016-04-15T13:53:47Z","lastAt":"2016-06-30T16:03:21Z","messageCount":162,"participants":["Reto Hablützel","Junio C Hamano","Duy Nguyen","Nguyễn Thái Ngọc Duy","Ramsay Jones","Eric Sunshine","Jeff King","Johannes Schindelin","Torsten Bögershausen"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"283529","messageId":"CAJZYdzhG8h3s=Ep1fuGbam1cWhYkv0tW6tQ7pBGGj+fj6=Nrsw@mail.gmail.com","threadId":"42034","inReplyTo":null,"subject":"possible to checkout same branch in different worktree","fromName":"Reto Hablützel","fromEmail":"rethab.ch@gmail.com","sentAt":"2016-04-15T13:53:47Z","receivedAt":"2016-04-15T13:53:47Z","isPatch":false,"sender":{"key":"rethab.ch@gmail.com","avatar":null},"body":"Hi,\n\nthe checkout command prevents me from checking out a branch in the\ncurrent worktree if it is already checked out in another worktree.\n\nHowever, if I rebase the branch in the current worktree onto the\nbranch in the other worktree, I end up in a situation where the same\nbranch is checked out twice in the two worktrees.\n\n- Reto\n\n--\n\nPGP\nKey ID = 0x67D30264\nFingerprint = E235 F740 4419 5B4B 29EC 3437 CBF2 2CB7 67D3 0264\n"},{"id":"283534","messageId":"xmqqk2jydgu8.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"CAJZYdzhG8h3s=Ep1fuGbam1cWhYkv0tW6tQ7pBGGj+fj6=Nrsw@mail.gmail.com","subject":"Re: possible to checkout same branch in different worktree","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-15T15:36:31Z","receivedAt":"2016-04-15T15:36:31Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Reto Hablützel <rethab.ch@gmail.com> writes:\n\n> the checkout command prevents me from checking out a branch in the\n> current worktree if it is already checked out in another worktree.\n>\n> However, if I rebase the branch in the current worktree onto the\n> branch in the other worktree, I end up in a situation where the same\n> branch is checked out twice in the two worktrees.\n\nI agree that any end-user facing subcommand like \"git rebase\", even\nif it is not \"git checkout\", should refuse to work on and update a\nbranch that is checked out elsewhere.  Otherwise it will end up\ncausing confusion.\n\nThanks for a report.  The \"multiple worktree\" feature is still\nhighly experimental and those who are working on it would appreciate\nyour reporting issues with it.\n"},{"id":"283635","messageId":"CACsJy8BOwzJSmS3PPeeRuaRwq=+kYLH8NX0i7P_wfDW9gLt6bA@mail.gmail.com","threadId":"42034","inReplyTo":"xmqqk2jydgu8.fsf@gitster.mtv.corp.google.com","subject":"Re: possible to checkout same branch in different worktree","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-16T00:51:48Z","receivedAt":"2016-04-16T00:51:48Z","isPatch":false,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Apr 15, 2016 at 10:36 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Reto Hablützel <rethab.ch@gmail.com> writes:\n>\n>> the checkout command prevents me from checking out a branch in the\n>> current worktree if it is already checked out in another worktree.\n>>\n>> However, if I rebase the branch in the current worktree onto the\n>> branch in the other worktree, I end up in a situation where the same\n>> branch is checked out twice in the two worktrees.\n>\n> I agree that any end-user facing subcommand like \"git rebase\", even\n> if it is not \"git checkout\", should refuse to work on and update a\n> branch that is checked out elsewhere.  Otherwise it will end up\n> causing confusion.\n\nI agree. I suppose we need same treatment for git-push? A push can be\nrejected if the pushed ref is being checked out. Suppose HEAD is in\nthe middle of a rebase (or am), it fails to detect ref name (and thus\ncheckout state) the same way here and we definitely do not want \"git\nrebase\" to simply overwrite whatever is updated by the push request.\n-- \nDuy\n"},{"id":"283673","messageId":"1460897965-486-1-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"CACsJy8BOwzJSmS3PPeeRuaRwq=+kYLH8NX0i7P_wfDW9gLt6bA@mail.gmail.com","subject":"[PATCH 0/7] fix checking out a being-rebased branch","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-17T12:59:18Z","receivedAt":"2016-04-17T12:59:18Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"First cut. I need opinions on 05/07, which converts\nwt_status_get_state() to support selecting any worktree. I'm not super\nhappy with leaving \"TODO: not supported yet\" comments, even though\nstrictly speaking this series does not need it.\n\nThe reason it's left unsupported is, in order to add\nworktree_get_sha1() or similar, resolve_ref() will be touched. But\nthat function is going under some changes for quite some time now, for\nlmdb backend support, and I don't want to create more code conflicts\nthere, potentially delaying lmdb backend further.\n\nAnother option is leave wt_status_get_state() alone, factor out the\nrebase-detection code and use that for worktree/checkout. We save a\nfew syscalls this way too.\n\nComments?\n\n  [01/07] path.c: add git_common_path() and strbuf_git_common_path()\n  [02/07] worktree.c: store \"id\" instead of \"git_dir\"\n  [03/07] path.c: refactor and add worktree_git_path()\n  [04/07] worktree.c: add worktree_git_path_..._head()\n  [05/07] wt-status.c: make wt_status_get_state() support worktree\n  [06/07] worktree.c: avoid referencing to worktrees[i] multiple times\n  [07/07] checkout: prevent checking out a branch being rebased in another worktree\n\nTotal 6 files changed, 167 insertions(+), 38 deletions(-)\n"},{"id":"283674","messageId":"1460897965-486-2-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1460897965-486-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 1/7] path.c: add git_common_path() and strbuf_git_common_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-17T12:59:19Z","receivedAt":"2016-04-17T12:59:19Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"These are mostly convenient functions to reduce code duplication. Most\nof the time, we should be able to get by with git_path() which handles\n$GIT_COMMON_DIR internally. However there are a few cases where we need\nto construct paths manually, for example some paths from a specific\nworktree. These functions will enable that.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |  3 +++\n path.c  | 29 +++++++++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 2711048..c04a17f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -799,11 +799,14 @@ extern void check_repository_format(void);\n  */\n extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n+extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n \n extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \t__attribute__((format (printf, 3, 4)));\n extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n+extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,\ndiff --git a/path.c b/path.c\nindex bbaea5a..2ebb23d 100644\n--- a/path.c\n+++ b/path.c\n@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,\n \tva_end(args);\n }\n \n+static void do_git_common_path(struct strbuf *buf,\n+\t\t\t       const char *fmt,\n+\t\t\t       va_list args)\n+{\n+\tstrbuf_addstr(buf, get_git_common_dir());\n+\tif (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_vaddf(buf, fmt, args);\n+\tstrbuf_cleanup_path(buf);\n+}\n+\n+const char *git_common_path(const char *fmt, ...)\n+{\n+\tstruct strbuf *pathname = get_pathname();\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_common_path(pathname, fmt, args);\n+\tva_end(args);\n+\treturn pathname->buf;\n+}\n+\n+void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n+{\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_common_path(sb, fmt, args);\n+\tva_end(args);\n+}\n+\n int validate_headref(const char *path)\n {\n \tstruct stat st;\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283675","messageId":"1460897965-486-3-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1460897965-486-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 2/7] worktree.c: store \"id\" instead of \"git_dir\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-17T12:59:20Z","receivedAt":"2016-04-17T12:59:20Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We can reconstruct git_dir from id quite easily. It's a bit hackier to\ndo the reverse.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 31 ++++++++++++++++++-------------\n worktree.h |  8 +++++++-\n 2 files changed, 25 insertions(+), 14 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex 6181a66..5ae54f0 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -9,7 +9,7 @@ void free_worktrees(struct worktree **worktrees)\n \n \tfor (i = 0; worktrees[i]; i++) {\n \t\tfree(worktrees[i]->path);\n-\t\tfree(worktrees[i]->git_dir);\n+\t\tfree(worktrees[i]->id);\n \t\tfree(worktrees[i]->head_ref);\n \t\tfree(worktrees[i]);\n \t}\n@@ -74,13 +74,11 @@ static struct worktree *get_main_worktree(void)\n \tstruct worktree *worktree = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf worktree_path = STRBUF_INIT;\n-\tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf head_ref = STRBUF_INIT;\n \tint is_bare = 0;\n \tint is_detached = 0;\n \n-\tstrbuf_addf(&gitdir, \"%s\", absolute_path(get_git_common_dir()));\n-\tstrbuf_addbuf(&worktree_path, &gitdir);\n+\tstrbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));\n \tis_bare = !strbuf_strip_suffix(&worktree_path, \"/.git\");\n \tif (is_bare)\n \t\tstrbuf_strip_suffix(&worktree_path, \"/.\");\n@@ -92,7 +90,7 @@ static struct worktree *get_main_worktree(void)\n \n \tworktree = xmalloc(sizeof(struct worktree));\n \tworktree->path = strbuf_detach(&worktree_path, NULL);\n-\tworktree->git_dir = strbuf_detach(&gitdir, NULL);\n+\tworktree->id = NULL;\n \tworktree->is_bare = is_bare;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n@@ -100,7 +98,6 @@ static struct worktree *get_main_worktree(void)\n \n done:\n \tstrbuf_release(&path);\n-\tstrbuf_release(&gitdir);\n \tstrbuf_release(&worktree_path);\n \tstrbuf_release(&head_ref);\n \treturn worktree;\n@@ -111,16 +108,13 @@ static struct worktree *get_linked_worktree(const char *id)\n \tstruct worktree *worktree = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf worktree_path = STRBUF_INIT;\n-\tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf head_ref = STRBUF_INIT;\n \tint is_detached = 0;\n \n \tif (!id)\n \t\tdie(\"Missing linked worktree name\");\n \n-\tstrbuf_addf(&gitdir, \"%s/worktrees/%s\",\n-\t\t\tabsolute_path(get_git_common_dir()), id);\n-\tstrbuf_addf(&path, \"%s/gitdir\", gitdir.buf);\n+\tstrbuf_git_common_path(&path, \"worktrees/%s/gitdir\", id);\n \tif (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)\n \t\t/* invalid gitdir file */\n \t\tgoto done;\n@@ -140,7 +134,7 @@ static struct worktree *get_linked_worktree(const char *id)\n \n \tworktree = xmalloc(sizeof(struct worktree));\n \tworktree->path = strbuf_detach(&worktree_path, NULL);\n-\tworktree->git_dir = strbuf_detach(&gitdir, NULL);\n+\tworktree->id = xstrdup(id);\n \tworktree->is_bare = 0;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n@@ -148,7 +142,6 @@ static struct worktree *get_linked_worktree(const char *id)\n \n done:\n \tstrbuf_release(&path);\n-\tstrbuf_release(&gitdir);\n \tstrbuf_release(&worktree_path);\n \tstrbuf_release(&head_ref);\n \treturn worktree;\n@@ -188,6 +181,16 @@ struct worktree **get_worktrees(void)\n \treturn list;\n }\n \n+const char *get_worktree_git_dir(const struct worktree *wt)\n+{\n+\tif (!wt)\n+\t\treturn get_git_dir();\n+\telse if (!wt->id)\n+\t\treturn get_git_common_dir();\n+\telse\n+\t\treturn git_common_path(\"worktrees/%s\", wt->id);\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\n@@ -199,7 +202,9 @@ char *find_shared_symref(const char *symref, const char *target)\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n-\t\tstrbuf_addf(&path, \"%s/%s\", worktrees[i]->git_dir, symref);\n+\t\tstrbuf_addf(&path, \"%s/%s\",\n+\t\t\t    get_worktree_git_dir(worktrees[i]),\n+\t\t\t    symref);\n \n \t\tif (parse_ref(path.buf, &sb, NULL)) {\n \t\t\tcontinue;\ndiff --git a/worktree.h b/worktree.h\nindex b4b3dda..3198c8d 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -3,7 +3,7 @@\n \n struct worktree {\n \tchar *path;\n-\tchar *git_dir;\n+\tchar *id;\n \tchar *head_ref;\n \tunsigned char head_sha1[20];\n \tint is_detached;\n@@ -23,6 +23,12 @@ struct worktree {\n extern struct worktree **get_worktrees(void);\n \n /*\n+ * Return git dir of the worktree. Note that the path may be relative.\n+ * If wt is NULL, git dir of current worktree is returned.\n+ */\n+extern const char *get_worktree_git_dir(const struct worktree *wt);\n+\n+/*\n  * Free up the memory for worktree(s)\n  */\n extern void free_worktrees(struct worktree **);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283676","messageId":"1460897965-486-4-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1460897965-486-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 3/7] path.c: refactor and add worktree_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-17T12:59:21Z","receivedAt":"2016-04-17T12:59:21Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"do_git_path(), which is the common code for all git_path* functions, is\nmodified to take a worktree struct and can produce paths for any\nworktree.\n\nworktree_git_path() is the first function that makes use of this. It can\nbe used to write code that can examine any worktree. For example,\nwt_status_get_state() will be converted using this to take\nam/rebase/... state of any worktree.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n path.c     | 34 ++++++++++++++++++++++++++++------\n worktree.h | 11 +++++++++++\n 2 files changed, 39 insertions(+), 6 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 2ebb23d..c421d37 100644\n--- a/path.c\n+++ b/path.c\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"dir.h\"\n+#include \"worktree.h\"\n \n static int get_st_mode_bits(const char *path, int *mode)\n {\n@@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)\n \t\tupdate_common_dir(buf, git_dir_len, NULL);\n }\n \n-static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)\n+static void do_git_path(const struct worktree *wt, struct strbuf *buf,\n+\t\t\tconst char *fmt, va_list args)\n {\n \tint gitdir_len;\n-\tstrbuf_addstr(buf, get_git_dir());\n+\tstrbuf_addstr(buf, get_worktree_git_dir(wt));\n \tif (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n \t\tstrbuf_addch(buf, '/');\n \tgitdir_len = buf->len;\n@@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)\n \tva_list args;\n \tstrbuf_reset(buf);\n \tva_start(args, fmt);\n-\tdo_git_path(buf, fmt, args);\n+\tdo_git_path(NULL, buf, fmt, args);\n \tva_end(args);\n \treturn buf->buf;\n }\n@@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n {\n \tva_list args;\n \tva_start(args, fmt);\n-\tdo_git_path(sb, fmt, args);\n+\tdo_git_path(NULL, sb, fmt, args);\n \tva_end(args);\n }\n \n@@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)\n \tstruct strbuf *pathname = get_pathname();\n \tva_list args;\n \tva_start(args, fmt);\n-\tdo_git_path(pathname, fmt, args);\n+\tdo_git_path(NULL, pathname, fmt, args);\n \tva_end(args);\n \treturn pathname->buf;\n }\n@@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)\n \tstruct strbuf path = STRBUF_INIT;\n \tva_list args;\n \tva_start(args, fmt);\n-\tdo_git_path(&path, fmt, args);\n+\tdo_git_path(NULL, &path, fmt, args);\n \tva_end(args);\n \treturn strbuf_detach(&path, NULL);\n }\n@@ -454,6 +456,26 @@ const char *mkpath(const char *fmt, ...)\n \treturn cleanup_path(pathname->buf);\n }\n \n+const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)\n+{\n+\tstruct strbuf *pathname = get_pathname();\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_path(wt, pathname, fmt, args);\n+\tva_end(args);\n+\treturn pathname->buf;\n+}\n+\n+char *worktree_git_pathdup(const struct worktree *wt, const char *fmt, ...)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_path(wt, &path, fmt, args);\n+\tva_end(args);\n+\treturn strbuf_detach(&path, NULL);\n+}\n+\n static void do_submodule_path(struct strbuf *buf, const char *path,\n \t\t\t      const char *fmt, va_list args)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 3198c8d..9ae2028 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -41,4 +41,15 @@ extern void free_worktrees(struct worktree **);\n  */\n extern char *find_shared_symref(const char *symref, const char *target);\n \n+/*\n+ * Similar to git_path() and git_pathdup() but can produce paths for a\n+ * specified worktree instead of current one\n+ */\n+extern const char *worktree_git_path(const struct worktree *wt,\n+\t\t\t\t     const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n+extern char *worktree_git_pathdup(const struct worktree *wt,\n+\t\t\t\t  const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n+\n #endif\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283677","messageId":"1460897965-486-5-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1460897965-486-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 4/7] worktree.c: add worktree_git_path_..._head()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-17T12:59:22Z","receivedAt":"2016-04-17T12:59:22Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"These are a variant of git_path_..._head(), defined with GIT_PATH_FUNC\nmacro but they takes worktree into account. To be used when\nwt_status_get_state() is converted to be worktree-aware.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c |  4 ++++\n worktree.h | 13 +++++++++++++\n 2 files changed, 17 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 5ae54f0..c5bf583 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -222,3 +222,7 @@ char *find_shared_symref(const char *symref, const char *target)\n \n \treturn existing;\n }\n+\n+WORKTREE_GIT_PATH_FUNC(merge_head, \"MERGE_HEAD\")\n+WORKTREE_GIT_PATH_FUNC(cherry_pick_head, \"CHERRY_PICK_HEAD\")\n+WORKTREE_GIT_PATH_FUNC(revert_head, \"REVERT_HEAD\")\ndiff --git a/worktree.h b/worktree.h\nindex 9ae2028..b7c5da0 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -52,4 +52,17 @@ extern char *worktree_git_pathdup(const struct worktree *wt,\n \t\t\t\t  const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n \n+#define WORKTREE_GIT_PATH_FUNC(func, filename) \\\n+\tconst char *worktree_git_path_ ## func(const struct worktree *wt) \\\n+\t{ \\\n+\t\tstatic char *ret; \\\n+\t\tif (!ret) \\\n+\t\t\tret = worktree_git_pathdup(wt, filename); \\\n+\t\treturn ret; \\\n+\t}\n+\n+const char *worktree_git_path_merge_head(const struct worktree *);\n+const char *worktree_git_path_cherry_pick_head(const struct worktree *);\n+const char *worktree_git_path_revert_head(const struct worktree *);\n+\n #endif\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283679","messageId":"1460897965-486-6-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1460897965-486-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 5/7] wt-status.c: make wt_status_get_state() support worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-17T12:59:23Z","receivedAt":"2016-04-17T12:59:23Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The function is to be used to detect if a ref is checked out in another\nworktree. In some cases, parsing \"HEAD\" alone is not enough because the\nworktree may be under rebase or bisect.\n\nNote that because get_sha1() cannot take a worktree (and of course will\nnot, we need something like worktree_get_sha1), cherry-pick and revert\ndetection will not work when the examined worktree is not the current\none. The same for get_detached_from.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n wt-status.c | 42 +++++++++++++++++++++++++-----------------\n wt-status.h |  3 +++\n 2 files changed, 28 insertions(+), 17 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 1ea2ebe..6cca3a6 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -15,6 +15,7 @@\n #include \"column.h\"\n #include \"strbuf.h\"\n #include \"utf8.h\"\n+#include \"worktree.h\"\n \n static const char cut_line[] =\n \"------------------------ >8 ------------------------\\n\";\n@@ -1262,13 +1263,13 @@ static void show_bisect_in_progress(struct wt_status *s,\n /*\n  * Extract branch information from rebase/bisect\n  */\n-static char *read_and_strip_branch(const char *path)\n+static char *read_and_strip_branch(const struct worktree *wt, const char *path)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \tunsigned char sha1[20];\n \tconst char *branch_name;\n \n-\tif (strbuf_read_file(&sb, git_path(\"%s\", path), 0) <= 0)\n+\tif (strbuf_read_file(&sb, worktree_git_path(wt, \"%s\", path), 0) <= 0)\n \t\tgoto got_nothing;\n \n \twhile (sb.len && sb.buf[sb.len - 1] == '\\n')\n@@ -1363,45 +1364,52 @@ static void wt_status_get_detached_from(struct wt_status_state *state)\n void wt_status_get_state(struct wt_status_state *state,\n \t\t\t int get_detached_from)\n {\n+\tconst struct worktree *wt = state->wt;\n \tstruct stat st;\n \tunsigned char sha1[20];\n \n-\tif (!stat(git_path_merge_head(), &st)) {\n+\tif (!stat(worktree_git_path_merge_head(wt), &st)) {\n \t\tstate->merge_in_progress = 1;\n-\t} else if (!stat(git_path(\"rebase-apply\"), &st)) {\n-\t\tif (!stat(git_path(\"rebase-apply/applying\"), &st)) {\n+\t} else if (!stat(worktree_git_path(wt, \"rebase-apply\"), &st)) {\n+\t\tif (!stat(worktree_git_path(wt, \"rebase-apply/applying\"), &st)) {\n \t\t\tstate->am_in_progress = 1;\n-\t\t\tif (!stat(git_path(\"rebase-apply/patch\"), &st) && !st.st_size)\n+\t\t\tif (!stat(worktree_git_path(wt, \"rebase-apply/patch\"), &st) && !st.st_size)\n \t\t\t\tstate->am_empty_patch = 1;\n \t\t} else {\n \t\t\tstate->rebase_in_progress = 1;\n-\t\t\tstate->branch = read_and_strip_branch(\"rebase-apply/head-name\");\n-\t\t\tstate->onto = read_and_strip_branch(\"rebase-apply/onto\");\n+\t\t\tstate->branch = read_and_strip_branch(wt, \"rebase-apply/head-name\");\n+\t\t\tstate->onto = read_and_strip_branch(wt, \"rebase-apply/onto\");\n \t\t}\n-\t} else if (!stat(git_path(\"rebase-merge\"), &st)) {\n-\t\tif (!stat(git_path(\"rebase-merge/interactive\"), &st))\n+\t} else if (!stat(worktree_git_path(wt, \"rebase-merge\"), &st)) {\n+\t\tif (!stat(worktree_git_path(wt, \"rebase-merge/interactive\"), &st))\n \t\t\tstate->rebase_interactive_in_progress = 1;\n \t\telse\n \t\t\tstate->rebase_in_progress = 1;\n-\t\tstate->branch = read_and_strip_branch(\"rebase-merge/head-name\");\n-\t\tstate->onto = read_and_strip_branch(\"rebase-merge/onto\");\n-\t} else if (!stat(git_path_cherry_pick_head(), &st) &&\n+\t\tstate->branch = read_and_strip_branch(wt, \"rebase-merge/head-name\");\n+\t\tstate->onto = read_and_strip_branch(wt, \"rebase-merge/onto\");\n+\t} else if (!wt && /* TODO: get_sha1() cannot select worktree yet */\n+\t\t   !stat(worktree_git_path_cherry_pick_head(wt), &st) &&\n \t\t\t!get_sha1(\"CHERRY_PICK_HEAD\", sha1)) {\n \t\tstate->cherry_pick_in_progress = 1;\n \t\thashcpy(state->cherry_pick_head_sha1, sha1);\n \t}\n-\tif (!stat(git_path(\"BISECT_LOG\"), &st)) {\n+\tif (!stat(worktree_git_path(wt, \"BISECT_LOG\"), &st)) {\n \t\tstate->bisect_in_progress = 1;\n-\t\tstate->branch = read_and_strip_branch(\"BISECT_START\");\n+\t\tstate->branch = read_and_strip_branch(wt, \"BISECT_START\");\n \t}\n-\tif (!stat(git_path_revert_head(), &st) &&\n+\tif (!wt &&  /* TODO: get_sha1() cannot select worktree yet */\n+\t    !stat(worktree_git_path_revert_head(wt), &st) &&\n \t    !get_sha1(\"REVERT_HEAD\", sha1)) {\n \t\tstate->revert_in_progress = 1;\n \t\thashcpy(state->revert_head_sha1, sha1);\n \t}\n \n-\tif (get_detached_from)\n+\tif (get_detached_from) {\n+\t\tif (wt)\n+\t\t\tdie(\"BUG: %s:%d: non-zero get_detached_from is not supported\",\n+\t\t\t    __FILE__, __LINE__);\n \t\twt_status_get_detached_from(state);\n+\t}\n }\n \n static void wt_status_print_state(struct wt_status *s,\ndiff --git a/wt-status.h b/wt-status.h\nindex c9b3b74..d31db38 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -6,6 +6,8 @@\n #include \"color.h\"\n #include \"pathspec.h\"\n \n+struct worktree;\n+\n enum color_wt_status {\n \tWT_STATUS_HEADER = 0,\n \tWT_STATUS_UPDATED,\n@@ -77,6 +79,7 @@ struct wt_status {\n };\n \n struct wt_status_state {\n+\tstruct worktree *wt;\n \tint merge_in_progress;\n \tint am_in_progress;\n \tint am_empty_patch;\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283678","messageId":"1460897965-486-7-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1460897965-486-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 6/7] worktree.c: avoid referencing to worktrees[i] multiple times","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-17T12:59:24Z","receivedAt":"2016-04-17T12:59:24Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex c5bf583..3b7c6f2 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -200,10 +200,12 @@ char *find_shared_symref(const char *symref, const char *target)\n \tint i = 0;\n \n \tfor (i = 0; worktrees[i]; i++) {\n+\t\tstruct worktree *wt = worktrees[i];\n+\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n \t\tstrbuf_addf(&path, \"%s/%s\",\n-\t\t\t    get_worktree_git_dir(worktrees[i]),\n+\t\t\t    get_worktree_git_dir(wt),\n \t\t\t    symref);\n \n \t\tif (parse_ref(path.buf, &sb, NULL)) {\n@@ -211,7 +213,7 @@ char *find_shared_symref(const char *symref, const char *target)\n \t\t}\n \n \t\tif (!strcmp(sb.buf, target)) {\n-\t\t\texisting = xstrdup(worktrees[i]->path);\n+\t\t\texisting = xstrdup(wt->path);\n \t\t\tbreak;\n \t\t}\n \t}\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283680","messageId":"1460897965-486-8-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1460897965-486-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 7/7] checkout: prevent checking out a branch being rebased in another worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-17T12:59:25Z","receivedAt":"2016-04-17T12:59:25Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We failed to detect this case because the rebase-in-progress worktree\nhas HEAD in detached state. Some more poking around is needed to\ndetermine if a rebase is in progress.\n\nNote that even though bisect can also put a worktree in detached state\nand hide the real branch, we allow to checkout anyway. This is because\nunlike rebase, bisect does not update the branch. Yes it's still a bit\nconfusing when the user doing the bisecting realizes that the branch is\nupdated elsewhere, but at least we do not lose any valuable ref update.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 23 +++++++++++++++++++++++\n 1 file changed, 23 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 3b7c6f2..6dc40c2 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -2,6 +2,7 @@\n #include \"refs.h\"\n #include \"strbuf.h\"\n #include \"worktree.h\"\n+#include \"wt-status.h\"\n \n void free_worktrees(struct worktree **worktrees)\n {\n@@ -202,6 +203,28 @@ char *find_shared_symref(const char *symref, const char *target)\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstruct worktree *wt = worktrees[i];\n \n+\t\tif (wt->is_detached) {\n+\t\t\tstruct wt_status_state wss;\n+\t\t\tint found;\n+\n+\t\t\tmemset(&wss, 0, sizeof(wss));\n+\t\t\twss.wt = wt;\n+\t\t\twt_status_get_state(&wss, 0);\n+\t\t\tfound = (wss.rebase_in_progress ||\n+\t\t\t\t wss.rebase_interactive_in_progress) &&\n+\t\t\t\twss.branch &&\n+\t\t\t\tstarts_with(target, \"refs/heads/\") &&\n+\t\t\t\t!strcmp(wss.branch,\n+\t\t\t\t\ttarget + strlen(\"refs/heads/\"));\n+\t\t\tfree(wss.branch);\n+\t\t\tfree(wss.onto);\n+\t\t\tfree(wss.detached_from);\n+\t\t\tif (found) {\n+\t\t\t\texisting = xstrdup(wt->path);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n \t\tstrbuf_addf(&path, \"%s/%s\",\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283737","messageId":"xmqqpotm6cfq.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"1460897965-486-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 0/7] fix checking out a being-rebased branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-18T17:42:33Z","receivedAt":"2016-04-18T17:42:33Z","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> First cut. I need opinions on 05/07, which converts\n> wt_status_get_state() to support selecting any worktree. I'm not super\n> happy with leaving \"TODO: not supported yet\" comments, even though\n> strictly speaking this series does not need it.\n\nIt is a reasonable idea to hook this to wt_status_get_state(), I\nwould say.\n\n> Another option is leave wt_status_get_state() alone, factor out the\n> rebase-detection code and use that for worktree/checkout. We save a\n> few syscalls this way too.\n>\n> Comments?\n>\n>   [01/07] path.c: add git_common_path() and strbuf_git_common_path()\n>   [02/07] worktree.c: store \"id\" instead of \"git_dir\"\n>   [03/07] path.c: refactor and add worktree_git_path()\n>   [04/07] worktree.c: add worktree_git_path_..._head()\n\nI actually wondered how ky/branch-[dm]-worktree topics to avoid\n\"branch -d\" and \"branch -m\" from removing or renaming a branch that\nis checked out in other worktrees from screwing them up.  There may\nbe a missed opportunity to clean them up with using these?\n\n>   [05/07] wt-status.c: make wt_status_get_state() support worktree\n>   [06/07] worktree.c: avoid referencing to worktrees[i] multiple times\n>   [07/07] checkout: prevent checking out a branch being rebased in another worktree\n>\n> Total 6 files changed, 167 insertions(+), 38 deletions(-)\n"},{"id":"283763","messageId":"CACsJy8BCg3GO1g0Z+S+bDYT3Bdh8d8q0WHz=JoeALvYwfNrM0A@mail.gmail.com","threadId":"42034","inReplyTo":"xmqqpotm6cfq.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 0/7] fix checking out a being-rebased branch","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-19T01:04:46Z","receivedAt":"2016-04-19T01:04:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Apr 19, 2016 at 12:42 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Another option is leave wt_status_get_state() alone, factor out the\n>> rebase-detection code and use that for worktree/checkout. We save a\n>> few syscalls this way too.\n>>\n>> Comments?\n>>\n>>   [01/07] path.c: add git_common_path() and strbuf_git_common_path()\n>>   [02/07] worktree.c: store \"id\" instead of \"git_dir\"\n>>   [03/07] path.c: refactor and add worktree_git_path()\n>>   [04/07] worktree.c: add worktree_git_path_..._head()\n>\n> I actually wondered how ky/branch-[dm]-worktree topics to avoid\n> \"branch -d\" and \"branch -m\" from removing or renaming a branch that\n> is checked out in other worktrees from screwing them up.  There may\n> be a missed opportunity to clean them up with using these?\n\nbranch-m-worktree uses info populated at get_worktrees() phase.\nbranch-d-worktree uses find_shared_symref() which is modified in this\nseries in order to detect rebase branch. So both topics have the same\nproblem when a branch is being rebased and if I do it right, I'll fix\nthem both without extra code. Actually I may need to check\nbranch-m-worktree again, renaming a branch under rebase might cause\nproblems, I need to have a closer look at git-rebase*.sh.\n-- \nDuy\n"},{"id":"283846","messageId":"xmqqpotlz5vh.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"CACsJy8BCg3GO1g0Z+S+bDYT3Bdh8d8q0WHz=JoeALvYwfNrM0A@mail.gmail.com","subject":"Re: [PATCH 0/7] fix checking out a being-rebased branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-19T20:43:46Z","receivedAt":"2016-04-19T20:43:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> On Tue, Apr 19, 2016 at 12:42 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>> Another option is leave wt_status_get_state() alone, factor out the\n>>> rebase-detection code and use that for worktree/checkout. We save a\n>>> few syscalls this way too.\n>>>\n>>> Comments?\n>>>\n>>>   [01/07] path.c: add git_common_path() and strbuf_git_common_path()\n>>>   [02/07] worktree.c: store \"id\" instead of \"git_dir\"\n>>>   [03/07] path.c: refactor and add worktree_git_path()\n>>>   [04/07] worktree.c: add worktree_git_path_..._head()\n>>\n>> I actually wondered how ky/branch-[dm]-worktree topics to avoid\n>> \"branch -d\" and \"branch -m\" from removing or renaming a branch that\n>> is checked out in other worktrees from screwing them up.  There may\n>> be a missed opportunity to clean them up with using these?\n>\n> branch-m-worktree uses info populated at get_worktrees() phase.\n> branch-d-worktree uses find_shared_symref() which is modified in this\n> series in order to detect rebase branch. So both topics have the same\n> problem when a branch is being rebased and if I do it right, I'll fix\n> them both without extra code.\n\nYup, that was exactly why I brought it up.\n\n> Actually I may need to check branch-m-worktree again, renaming a\n> branch under rebase might cause problems, I need to have a closer\n> look at git-rebase*.sh.\n\nThanks.\n"},{"id":"283934","messageId":"1461158693-21289-1-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1460897965-486-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 00/12] fix checking out a being-rebased branch","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:41Z","receivedAt":"2016-04-20T13:24:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Much happier with this version. This makes\n\n - git checkout refuse if a branch is under rebase or bisect\n   elsewhere\n\n - git worktree add refuse if a branch is under rebase or bisect\n\n - git branch -D refuse if a branch is under rebase or bisect.\n   This applies for single worktree case as well.\n\n - git branch -M refuse if a branch is under rebase or bisect\n   (single worktree case as well)\n\n  [01/12] path.c: add git_common_path() and strbuf_git_common_path()\n  [02/12] worktree.c: store \"id\" instead of \"git_dir\"\n  [03/12] worktree.c: make find_shared_symref() return struct worktree *\n  [04/12] worktree.c: mark current worktree\n  [05/12] path.c: refactor and add worktree_git_path()\n  [06/12] wt-status.c: split rebase detection out of wt_status_get_state()\n  [07/12] wt-status.c: make wt_status_check_rebase() work on any worktree\n  [08/12] worktree.c: avoid referencing to worktrees[i] multiple times\n  [09/12] worktree.c: test if branch being rebased in another worktree\n  [10/12] wt-status.c: split bisect detection out of wt_status_get_state()\n  [11/12] worktree.c: test if branch being bisected in another worktree\n  [12/12] branch: do not rename a branch under bisect or rebase\n\nTotal 13 files changed, 335 insertions(+), 67 deletions(-)\n"},{"id":"283933","messageId":"1461158693-21289-2-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 01/12] path.c: add git_common_path() and strbuf_git_common_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:42Z","receivedAt":"2016-04-20T13:24:42Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"These are mostly convenient functions to reduce code duplication. Most\nof the time, we should be able to get by with git_path() which handles\n$GIT_COMMON_DIR internally. However there are a few cases where we need\nto construct paths manually, for example some paths from a specific\nworktree. These functions will enable that.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |  3 +++\n path.c  | 29 +++++++++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 2711048..c04a17f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -799,11 +799,14 @@ extern void check_repository_format(void);\n  */\n extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n+extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n \n extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \t__attribute__((format (printf, 3, 4)));\n extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n+extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,\ndiff --git a/path.c b/path.c\nindex bbaea5a..2ebb23d 100644\n--- a/path.c\n+++ b/path.c\n@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,\n \tva_end(args);\n }\n \n+static void do_git_common_path(struct strbuf *buf,\n+\t\t\t       const char *fmt,\n+\t\t\t       va_list args)\n+{\n+\tstrbuf_addstr(buf, get_git_common_dir());\n+\tif (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_vaddf(buf, fmt, args);\n+\tstrbuf_cleanup_path(buf);\n+}\n+\n+const char *git_common_path(const char *fmt, ...)\n+{\n+\tstruct strbuf *pathname = get_pathname();\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_common_path(pathname, fmt, args);\n+\tva_end(args);\n+\treturn pathname->buf;\n+}\n+\n+void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n+{\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_common_path(sb, fmt, args);\n+\tva_end(args);\n+}\n+\n int validate_headref(const char *path)\n {\n \tstruct stat st;\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283935","messageId":"1461158693-21289-3-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 02/12] worktree.c: store \"id\" instead of \"git_dir\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:43Z","receivedAt":"2016-04-20T13:24:43Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We can reconstruct git_dir from id quite easily. It's a bit hackier to\ndo the reverse.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n branch.c   |  3 ++-\n worktree.c | 31 ++++++++++++++++++-------------\n worktree.h |  8 +++++++-\n 3 files changed, 27 insertions(+), 15 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 4162443..0674a99 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -357,7 +357,8 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref)\n \t\tif (strcmp(oldref, worktrees[i]->head_ref))\n \t\t\tcontinue;\n \n-\t\tif (set_worktree_head_symref(worktrees[i]->git_dir, newref)) {\n+\t\tif (set_worktree_head_symref(get_worktree_git_dir(worktrees[i]),\n+\t\t\t\t\t     newref)) {\n \t\t\tret = -1;\n \t\t\terror(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t      worktrees[i]->path);\ndiff --git a/worktree.c b/worktree.c\nindex 6181a66..5ae54f0 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -9,7 +9,7 @@ void free_worktrees(struct worktree **worktrees)\n \n \tfor (i = 0; worktrees[i]; i++) {\n \t\tfree(worktrees[i]->path);\n-\t\tfree(worktrees[i]->git_dir);\n+\t\tfree(worktrees[i]->id);\n \t\tfree(worktrees[i]->head_ref);\n \t\tfree(worktrees[i]);\n \t}\n@@ -74,13 +74,11 @@ static struct worktree *get_main_worktree(void)\n \tstruct worktree *worktree = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf worktree_path = STRBUF_INIT;\n-\tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf head_ref = STRBUF_INIT;\n \tint is_bare = 0;\n \tint is_detached = 0;\n \n-\tstrbuf_addf(&gitdir, \"%s\", absolute_path(get_git_common_dir()));\n-\tstrbuf_addbuf(&worktree_path, &gitdir);\n+\tstrbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));\n \tis_bare = !strbuf_strip_suffix(&worktree_path, \"/.git\");\n \tif (is_bare)\n \t\tstrbuf_strip_suffix(&worktree_path, \"/.\");\n@@ -92,7 +90,7 @@ static struct worktree *get_main_worktree(void)\n \n \tworktree = xmalloc(sizeof(struct worktree));\n \tworktree->path = strbuf_detach(&worktree_path, NULL);\n-\tworktree->git_dir = strbuf_detach(&gitdir, NULL);\n+\tworktree->id = NULL;\n \tworktree->is_bare = is_bare;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n@@ -100,7 +98,6 @@ static struct worktree *get_main_worktree(void)\n \n done:\n \tstrbuf_release(&path);\n-\tstrbuf_release(&gitdir);\n \tstrbuf_release(&worktree_path);\n \tstrbuf_release(&head_ref);\n \treturn worktree;\n@@ -111,16 +108,13 @@ static struct worktree *get_linked_worktree(const char *id)\n \tstruct worktree *worktree = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf worktree_path = STRBUF_INIT;\n-\tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf head_ref = STRBUF_INIT;\n \tint is_detached = 0;\n \n \tif (!id)\n \t\tdie(\"Missing linked worktree name\");\n \n-\tstrbuf_addf(&gitdir, \"%s/worktrees/%s\",\n-\t\t\tabsolute_path(get_git_common_dir()), id);\n-\tstrbuf_addf(&path, \"%s/gitdir\", gitdir.buf);\n+\tstrbuf_git_common_path(&path, \"worktrees/%s/gitdir\", id);\n \tif (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)\n \t\t/* invalid gitdir file */\n \t\tgoto done;\n@@ -140,7 +134,7 @@ static struct worktree *get_linked_worktree(const char *id)\n \n \tworktree = xmalloc(sizeof(struct worktree));\n \tworktree->path = strbuf_detach(&worktree_path, NULL);\n-\tworktree->git_dir = strbuf_detach(&gitdir, NULL);\n+\tworktree->id = xstrdup(id);\n \tworktree->is_bare = 0;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n@@ -148,7 +142,6 @@ static struct worktree *get_linked_worktree(const char *id)\n \n done:\n \tstrbuf_release(&path);\n-\tstrbuf_release(&gitdir);\n \tstrbuf_release(&worktree_path);\n \tstrbuf_release(&head_ref);\n \treturn worktree;\n@@ -188,6 +181,16 @@ struct worktree **get_worktrees(void)\n \treturn list;\n }\n \n+const char *get_worktree_git_dir(const struct worktree *wt)\n+{\n+\tif (!wt)\n+\t\treturn get_git_dir();\n+\telse if (!wt->id)\n+\t\treturn get_git_common_dir();\n+\telse\n+\t\treturn git_common_path(\"worktrees/%s\", wt->id);\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\n@@ -199,7 +202,9 @@ char *find_shared_symref(const char *symref, const char *target)\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n-\t\tstrbuf_addf(&path, \"%s/%s\", worktrees[i]->git_dir, symref);\n+\t\tstrbuf_addf(&path, \"%s/%s\",\n+\t\t\t    get_worktree_git_dir(worktrees[i]),\n+\t\t\t    symref);\n \n \t\tif (parse_ref(path.buf, &sb, NULL)) {\n \t\t\tcontinue;\ndiff --git a/worktree.h b/worktree.h\nindex b4b3dda..3198c8d 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -3,7 +3,7 @@\n \n struct worktree {\n \tchar *path;\n-\tchar *git_dir;\n+\tchar *id;\n \tchar *head_ref;\n \tunsigned char head_sha1[20];\n \tint is_detached;\n@@ -23,6 +23,12 @@ struct worktree {\n extern struct worktree **get_worktrees(void);\n \n /*\n+ * Return git dir of the worktree. Note that the path may be relative.\n+ * If wt is NULL, git dir of current worktree is returned.\n+ */\n+extern const char *get_worktree_git_dir(const struct worktree *wt);\n+\n+/*\n  * Free up the memory for worktree(s)\n  */\n extern void free_worktrees(struct worktree **);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283936","messageId":"1461158693-21289-4-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 03/12] worktree.c: make find_shared_symref() return struct worktree *","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:44Z","receivedAt":"2016-04-20T13:24:44Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This gives the caller more information and they can answer things like,\n\"is it the main worktree\" or \"is it the current worktree\". The latter\nquestion is needed for the \"checkout a rebase branch\" case later.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n branch.c         |  9 +++++----\n builtin/branch.c |  8 ++++----\n builtin/notes.c  |  8 ++++----\n worktree.c       | 14 +++++++++-----\n worktree.h       |  7 ++++---\n 5 files changed, 26 insertions(+), 20 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 0674a99..a84fb2c 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -336,12 +336,13 @@ void remove_branch_state(void)\n \n void die_if_checked_out(const char *branch)\n {\n-\tchar *existing;\n+\tconst struct worktree *wt;\n \n-\texisting = find_shared_symref(\"HEAD\", branch);\n-\tif (existing) {\n+\twt = find_shared_symref(\"HEAD\", branch);\n+\tif (wt) {\n \t\tskip_prefix(branch, \"refs/heads/\", &branch);\n-\t\tdie(_(\"'%s' is already checked out at '%s'\"), branch, existing);\n+\t\tdie(_(\"'%s' is already checked out at '%s'\"),\n+\t\t    branch, wt->path);\n \t}\n }\n \ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 0adba62..bcde87d 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -220,12 +220,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tname = mkpathdup(fmt, bname.buf);\n \n \t\tif (kinds == FILTER_REFS_BRANCHES) {\n-\t\t\tchar *worktree = find_shared_symref(\"HEAD\", name);\n-\t\t\tif (worktree) {\n+\t\t\tconst struct worktree *wt =\n+\t\t\t\tfind_shared_symref(\"HEAD\", name);\n+\t\t\tif (wt) {\n \t\t\t\terror(_(\"Cannot delete branch '%s' \"\n \t\t\t\t\t\"checked out at '%s'\"),\n-\t\t\t\t      bname.buf, worktree);\n-\t\t\t\tfree(worktree);\n+\t\t\t\t      bname.buf, wt->path);\n \t\t\t\tret = 1;\n \t\t\t\tcontinue;\n \t\t\t}\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 6fd058d..c65b59a 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -847,15 +847,15 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\tupdate_ref(msg.buf, default_notes_ref(), result_sha1, NULL,\n \t\t\t   0, UPDATE_REFS_DIE_ON_ERR);\n \telse { /* Merge has unresolved conflicts */\n-\t\tchar *existing;\n+\t\tconst struct worktree *wt;\n \t\t/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */\n \t\tupdate_ref(msg.buf, \"NOTES_MERGE_PARTIAL\", result_sha1, NULL,\n \t\t\t   0, UPDATE_REFS_DIE_ON_ERR);\n \t\t/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */\n-\t\texisting = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref());\n-\t\tif (existing)\n+\t\twt = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref());\n+\t\tif (wt)\n \t\t\tdie(_(\"A notes merge into %s is already in-progress at %s\"),\n-\t\t\t    default_notes_ref(), existing);\n+\t\t\t    default_notes_ref(), wt->path);\n \t\tif (create_symref(\"NOTES_MERGE_REF\", default_notes_ref(), NULL))\n \t\t\tdie(\"Failed to store link to current notes ref (%s)\",\n \t\t\t    default_notes_ref());\ndiff --git a/worktree.c b/worktree.c\nindex 5ae54f0..360ba41 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -191,14 +191,19 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n-char *find_shared_symref(const char *symref, const char *target)\n+const struct worktree *find_shared_symref(const char *symref,\n+\t\t\t\t\t  const char *target)\n {\n-\tchar *existing = NULL;\n+\tconst struct worktree *existing = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf sb = STRBUF_INIT;\n-\tstruct worktree **worktrees = get_worktrees();\n+\tstatic struct worktree **worktrees;\n \tint i = 0;\n \n+\tif (worktrees)\n+\t\tfree_worktrees(worktrees);\n+\tworktrees = get_worktrees();\n+\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n@@ -211,14 +216,13 @@ char *find_shared_symref(const char *symref, const char *target)\n \t\t}\n \n \t\tif (!strcmp(sb.buf, target)) {\n-\t\t\texisting = xstrdup(worktrees[i]->path);\n+\t\t\texisting = worktrees[i];\n \t\t\tbreak;\n \t\t}\n \t}\n \n \tstrbuf_release(&path);\n \tstrbuf_release(&sb);\n-\tfree_worktrees(worktrees);\n \n \treturn existing;\n }\ndiff --git a/worktree.h b/worktree.h\nindex 3198c8d..d71d7ec 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -36,9 +36,10 @@ extern void free_worktrees(struct worktree **);\n /*\n  * Check if a per-worktree symref points to a ref in the main worktree\n  * or any linked worktree, and return the path to the exising worktree\n- * if it is.  Returns NULL if there is no existing ref.  The caller is\n- * responsible for freeing the returned path.\n+ * if it is. Returns NULL if there is no existing ref. The result\n+ * may be destroyed by the next call.\n  */\n-extern char *find_shared_symref(const char *symref, const char *target);\n+extern const struct worktree *find_shared_symref(const char *symref,\n+\t\t\t\t\t\t const char *target);\n \n #endif\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283937","messageId":"1461158693-21289-5-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 04/12] worktree.c: mark current worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:45Z","receivedAt":"2016-04-20T13:24:45Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 18 +++++++++++++++++-\n worktree.h |  1 +\n 2 files changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex 360ba41..452f64a 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -2,6 +2,7 @@\n #include \"refs.h\"\n #include \"strbuf.h\"\n #include \"worktree.h\"\n+#include \"dir.h\"\n \n void free_worktrees(struct worktree **worktrees)\n {\n@@ -94,6 +95,7 @@ static struct worktree *get_main_worktree(void)\n \tworktree->is_bare = is_bare;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n+\tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n \n done:\n@@ -138,6 +140,7 @@ static struct worktree *get_linked_worktree(const char *id)\n \tworktree->is_bare = 0;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n+\tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n \n done:\n@@ -150,10 +153,11 @@ done:\n struct worktree **get_worktrees(void)\n {\n \tstruct worktree **list = NULL;\n+\tstruct strbuf git_dir = STRBUF_INIT;\n \tstruct strbuf path = STRBUF_INIT;\n \tDIR *dir;\n \tstruct dirent *d;\n-\tint counter = 0, alloc = 2;\n+\tint i, counter = 0, alloc = 2;\n \n \tlist = xmalloc(alloc * sizeof(struct worktree *));\n \n@@ -178,6 +182,18 @@ struct worktree **get_worktrees(void)\n \t}\n \tALLOC_GROW(list, counter + 1, alloc);\n \tlist[counter] = NULL;\n+\n+\tstrbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n+\tfor (i = 0; i < counter; i++) {\n+\t\tstruct worktree *wt = list[i];\n+\t\tstrbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n+\t\twt->is_current = !strcmp_icase(git_dir.buf, path.buf);\n+\t\tstrbuf_reset(&path);\n+\t\tif (wt->is_current)\n+\t\t\tbreak;\n+\t}\n+\tstrbuf_release(&git_dir);\n+\tstrbuf_release(&path);\n \treturn list;\n }\n \ndiff --git a/worktree.h b/worktree.h\nindex d71d7ec..625fb8d 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -8,6 +8,7 @@ struct worktree {\n \tunsigned char head_sha1[20];\n \tint is_detached;\n \tint is_bare;\n+\tint is_current;\n };\n \n /* Functions for acting on the information about worktrees. */\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283940","messageId":"1461158693-21289-6-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 05/12] path.c: refactor and add worktree_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:46Z","receivedAt":"2016-04-20T13:24:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"do_git_path(), which is the common code for all git_path* functions, is\nmodified to take a worktree struct and can produce paths for any\nworktree.\n\nworktree_git_path() is the first function that makes use of this. It can\nbe used to write code that can examine any worktree. For example,\nwt_status_get_state() will be converted using this to take\nam/rebase/... state of any worktree.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n path.c     | 34 ++++++++++++++++++++++++++++------\n worktree.h | 11 +++++++++++\n 2 files changed, 39 insertions(+), 6 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 2ebb23d..c421d37 100644\n--- a/path.c\n+++ b/path.c\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"dir.h\"\n+#include \"worktree.h\"\n \n static int get_st_mode_bits(const char *path, int *mode)\n {\n@@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)\n \t\tupdate_common_dir(buf, git_dir_len, NULL);\n }\n \n-static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)\n+static void do_git_path(const struct worktree *wt, struct strbuf *buf,\n+\t\t\tconst char *fmt, va_list args)\n {\n \tint gitdir_len;\n-\tstrbuf_addstr(buf, get_git_dir());\n+\tstrbuf_addstr(buf, get_worktree_git_dir(wt));\n \tif (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n \t\tstrbuf_addch(buf, '/');\n \tgitdir_len = buf->len;\n@@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)\n \tva_list args;\n \tstrbuf_reset(buf);\n \tva_start(args, fmt);\n-\tdo_git_path(buf, fmt, args);\n+\tdo_git_path(NULL, buf, fmt, args);\n \tva_end(args);\n \treturn buf->buf;\n }\n@@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n {\n \tva_list args;\n \tva_start(args, fmt);\n-\tdo_git_path(sb, fmt, args);\n+\tdo_git_path(NULL, sb, fmt, args);\n \tva_end(args);\n }\n \n@@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)\n \tstruct strbuf *pathname = get_pathname();\n \tva_list args;\n \tva_start(args, fmt);\n-\tdo_git_path(pathname, fmt, args);\n+\tdo_git_path(NULL, pathname, fmt, args);\n \tva_end(args);\n \treturn pathname->buf;\n }\n@@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)\n \tstruct strbuf path = STRBUF_INIT;\n \tva_list args;\n \tva_start(args, fmt);\n-\tdo_git_path(&path, fmt, args);\n+\tdo_git_path(NULL, &path, fmt, args);\n \tva_end(args);\n \treturn strbuf_detach(&path, NULL);\n }\n@@ -454,6 +456,26 @@ const char *mkpath(const char *fmt, ...)\n \treturn cleanup_path(pathname->buf);\n }\n \n+const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)\n+{\n+\tstruct strbuf *pathname = get_pathname();\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_path(wt, pathname, fmt, args);\n+\tva_end(args);\n+\treturn pathname->buf;\n+}\n+\n+char *worktree_git_pathdup(const struct worktree *wt, const char *fmt, ...)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_path(wt, &path, fmt, args);\n+\tva_end(args);\n+\treturn strbuf_detach(&path, NULL);\n+}\n+\n static void do_submodule_path(struct strbuf *buf, const char *path,\n \t\t\t      const char *fmt, va_list args)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 625fb8d..9d2463e 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -43,4 +43,15 @@ extern void free_worktrees(struct worktree **);\n extern const struct worktree *find_shared_symref(const char *symref,\n \t\t\t\t\t\t const char *target);\n \n+/*\n+ * Similar to git_path() and git_pathdup() but can produce paths for a\n+ * specified worktree instead of current one\n+ */\n+extern const char *worktree_git_path(const struct worktree *wt,\n+\t\t\t\t     const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n+extern char *worktree_git_pathdup(const struct worktree *wt,\n+\t\t\t\t  const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n+\n #endif\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283938","messageId":"1461158693-21289-7-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 06/12] wt-status.c: split rebase detection out of wt_status_get_state()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:47Z","receivedAt":"2016-04-20T13:24:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"worktree.c:find_shared_symref() later needs to know if a branch is being\nrebased, and only rebased only. Split this code so it can be used\nindependently from other in-progress tests.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n wt-status.c | 23 +++++++++++++++++------\n wt-status.h |  1 +\n 2 files changed, 18 insertions(+), 6 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 1ea2ebe..35787ec 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1360,15 +1360,11 @@ static void wt_status_get_detached_from(struct wt_status_state *state)\n \tstrbuf_release(&cb.buf);\n }\n \n-void wt_status_get_state(struct wt_status_state *state,\n-\t\t\t int get_detached_from)\n+int wt_status_check_rebase(struct wt_status_state *state)\n {\n \tstruct stat st;\n-\tunsigned char sha1[20];\n \n-\tif (!stat(git_path_merge_head(), &st)) {\n-\t\tstate->merge_in_progress = 1;\n-\t} else if (!stat(git_path(\"rebase-apply\"), &st)) {\n+\tif (!stat(git_path(\"rebase-apply\"), &st)) {\n \t\tif (!stat(git_path(\"rebase-apply/applying\"), &st)) {\n \t\t\tstate->am_in_progress = 1;\n \t\t\tif (!stat(git_path(\"rebase-apply/patch\"), &st) && !st.st_size)\n@@ -1385,6 +1381,21 @@ void wt_status_get_state(struct wt_status_state *state,\n \t\t\tstate->rebase_in_progress = 1;\n \t\tstate->branch = read_and_strip_branch(\"rebase-merge/head-name\");\n \t\tstate->onto = read_and_strip_branch(\"rebase-merge/onto\");\n+\t} else\n+\t\treturn 0;\n+\treturn 1;\n+}\n+\n+void wt_status_get_state(struct wt_status_state *state,\n+\t\t\t int get_detached_from)\n+{\n+\tstruct stat st;\n+\tunsigned char sha1[20];\n+\n+\tif (!stat(git_path_merge_head(), &st)) {\n+\t\tstate->merge_in_progress = 1;\n+\t} else if (wt_status_check_rebase(state)) {\n+\t\t/* all set */\n \t} else if (!stat(git_path_cherry_pick_head(), &st) &&\n \t\t\t!get_sha1(\"CHERRY_PICK_HEAD\", sha1)) {\n \t\tstate->cherry_pick_in_progress = 1;\ndiff --git a/wt-status.h b/wt-status.h\nindex c9b3b74..b398353 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -100,6 +100,7 @@ void wt_status_prepare(struct wt_status *s);\n void wt_status_print(struct wt_status *s);\n void wt_status_collect(struct wt_status *s);\n void wt_status_get_state(struct wt_status_state *state, int get_detached_from);\n+int wt_status_check_rebase(struct wt_status_state *state);\n \n void wt_shortstatus_print(struct wt_status *s);\n void wt_porcelain_print(struct wt_status *s);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283939","messageId":"1461158693-21289-8-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 07/12] wt-status.c: make wt_status_check_rebase() work on any worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:48Z","receivedAt":"2016-04-20T13:24:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is a preparation step for find_shared_symref() to detect if any\nworktree is being rebased.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n wt-status.c | 33 ++++++++++++++++++++-------------\n wt-status.h |  5 ++++-\n 2 files changed, 24 insertions(+), 14 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 35787ec..2295682 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -15,6 +15,7 @@\n #include \"column.h\"\n #include \"strbuf.h\"\n #include \"utf8.h\"\n+#include \"worktree.h\"\n \n static const char cut_line[] =\n \"------------------------ >8 ------------------------\\n\";\n@@ -1262,13 +1263,13 @@ static void show_bisect_in_progress(struct wt_status *s,\n /*\n  * Extract branch information from rebase/bisect\n  */\n-static char *read_and_strip_branch(const char *path)\n+static char *get_branch(const struct worktree *wt, const char *path)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \tunsigned char sha1[20];\n \tconst char *branch_name;\n \n-\tif (strbuf_read_file(&sb, git_path(\"%s\", path), 0) <= 0)\n+\tif (strbuf_read_file(&sb, worktree_git_path(wt, \"%s\", path), 0) <= 0)\n \t\tgoto got_nothing;\n \n \twhile (sb.len && sb.buf[sb.len - 1] == '\\n')\n@@ -1295,6 +1296,11 @@ got_nothing:\n \treturn NULL;\n }\n \n+static char *read_and_strip_branch(const char *path)\n+{\n+\treturn get_branch(NULL, path);\n+}\n+\n struct grab_1st_switch_cbdata {\n \tstruct strbuf buf;\n \tunsigned char nsha1[20];\n@@ -1360,27 +1366,28 @@ static void wt_status_get_detached_from(struct wt_status_state *state)\n \tstrbuf_release(&cb.buf);\n }\n \n-int wt_status_check_rebase(struct wt_status_state *state)\n+int wt_status_check_rebase(const struct worktree *wt,\n+\t\t\t   struct wt_status_state *state)\n {\n \tstruct stat st;\n \n-\tif (!stat(git_path(\"rebase-apply\"), &st)) {\n-\t\tif (!stat(git_path(\"rebase-apply/applying\"), &st)) {\n+\tif (!stat(worktree_git_path(wt, \"rebase-apply\"), &st)) {\n+\t\tif (!stat(worktree_git_path(wt, \"rebase-apply/applying\"), &st)) {\n \t\t\tstate->am_in_progress = 1;\n-\t\t\tif (!stat(git_path(\"rebase-apply/patch\"), &st) && !st.st_size)\n+\t\t\tif (!stat(worktree_git_path(wt, \"rebase-apply/patch\"), &st) && !st.st_size)\n \t\t\t\tstate->am_empty_patch = 1;\n \t\t} else {\n \t\t\tstate->rebase_in_progress = 1;\n-\t\t\tstate->branch = read_and_strip_branch(\"rebase-apply/head-name\");\n-\t\t\tstate->onto = read_and_strip_branch(\"rebase-apply/onto\");\n+\t\t\tstate->branch = get_branch(wt, \"rebase-apply/head-name\");\n+\t\t\tstate->onto = get_branch(wt, \"rebase-apply/onto\");\n \t\t}\n-\t} else if (!stat(git_path(\"rebase-merge\"), &st)) {\n-\t\tif (!stat(git_path(\"rebase-merge/interactive\"), &st))\n+\t} else if (!stat(worktree_git_path(wt, \"rebase-merge\"), &st)) {\n+\t\tif (!stat(worktree_git_path(wt, \"rebase-merge/interactive\"), &st))\n \t\t\tstate->rebase_interactive_in_progress = 1;\n \t\telse\n \t\t\tstate->rebase_in_progress = 1;\n-\t\tstate->branch = read_and_strip_branch(\"rebase-merge/head-name\");\n-\t\tstate->onto = read_and_strip_branch(\"rebase-merge/onto\");\n+\t\tstate->branch = get_branch(wt, \"rebase-merge/head-name\");\n+\t\tstate->onto = get_branch(wt, \"rebase-merge/onto\");\n \t} else\n \t\treturn 0;\n \treturn 1;\n@@ -1394,7 +1401,7 @@ void wt_status_get_state(struct wt_status_state *state,\n \n \tif (!stat(git_path_merge_head(), &st)) {\n \t\tstate->merge_in_progress = 1;\n-\t} else if (wt_status_check_rebase(state)) {\n+\t} else if (wt_status_check_rebase(NULL, state)) {\n \t\t/* all set */\n \t} else if (!stat(git_path_cherry_pick_head(), &st) &&\n \t\t\t!get_sha1(\"CHERRY_PICK_HEAD\", sha1)) {\ndiff --git a/wt-status.h b/wt-status.h\nindex b398353..c4ddcad 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -6,6 +6,8 @@\n #include \"color.h\"\n #include \"pathspec.h\"\n \n+struct worktree;\n+\n enum color_wt_status {\n \tWT_STATUS_HEADER = 0,\n \tWT_STATUS_UPDATED,\n@@ -100,7 +102,8 @@ void wt_status_prepare(struct wt_status *s);\n void wt_status_print(struct wt_status *s);\n void wt_status_collect(struct wt_status *s);\n void wt_status_get_state(struct wt_status_state *state, int get_detached_from);\n-int wt_status_check_rebase(struct wt_status_state *state);\n+int wt_status_check_rebase(const struct worktree *wt,\n+\t\t\t   struct wt_status_state *state);\n \n void wt_shortstatus_print(struct wt_status *s);\n void wt_porcelain_print(struct wt_status *s);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283941","messageId":"1461158693-21289-9-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 08/12] worktree.c: avoid referencing to worktrees[i] multiple times","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:49Z","receivedAt":"2016-04-20T13:24:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex 452f64a..b5ca78f 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -221,10 +221,12 @@ const struct worktree *find_shared_symref(const char *symref,\n \tworktrees = get_worktrees();\n \n \tfor (i = 0; worktrees[i]; i++) {\n+\t\tstruct worktree *wt = worktrees[i];\n+\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n \t\tstrbuf_addf(&path, \"%s/%s\",\n-\t\t\t    get_worktree_git_dir(worktrees[i]),\n+\t\t\t    get_worktree_git_dir(wt),\n \t\t\t    symref);\n \n \t\tif (parse_ref(path.buf, &sb, NULL)) {\n@@ -232,7 +234,7 @@ const struct worktree *find_shared_symref(const char *symref,\n \t\t}\n \n \t\tif (!strcmp(sb.buf, target)) {\n-\t\t\texisting = worktrees[i];\n+\t\t\texisting = wt;\n \t\t\tbreak;\n \t\t}\n \t}\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283944","messageId":"1461158693-21289-10-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 09/12] worktree.c: test if branch being rebased in another worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:50Z","receivedAt":"2016-04-20T13:24:50Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This function find_shared_symref() is used in a couple places:\n\n1) in builtin/branch.c: it's used to detect if a branch is checked out\n   elsewhere and refuse to delete the branch.\n\n2) in builtin/notes.c: it's used to detect if a note is being merged in\n   another worktree\n\n3) in branch.c, the function die_if_checked_out() is actually used by\n   \"git checkout\" and \"git worktree add\" to see if a branch is already\n   checked out elsewhere and refuse the operation.\n\nIn cases 1 and 3, if a rebase is ongoing, \"HEAD\" will be in detached\nmode, find_shared_symref() fails to detect it and declares \"no branch is\nchecked out here\", which is incorrect.\n\nThis patch tightens the test. If the given symref is \"HEAD\", we try to\ndetect if rebase is ongoing. If so return the branch being rebased. This\nmakes checkout and branch delete operations safer because you can't\ncheckout a branch being rebased in another place, or delete it.\n\nSpecial case for checkout. If the current branch is being rebased,\ngit-rebase.sh may use \"git checkout\" to abort and return back to the\noriginal branch. The updated test in find_shared_symref() will prevent\nthat and \"git rebase --abort\" will fail as a result.\nfind_shared_symref() and die_if_checked_out() have to learn a new\noption ignore_current_worktree to loose the test a bit.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n branch.c                |  4 ++--\n branch.h                |  2 +-\n builtin/branch.c        |  2 +-\n builtin/checkout.c      |  2 +-\n builtin/notes.c         |  2 +-\n builtin/worktree.c      |  4 ++--\n t/t2025-worktree-add.sh | 38 ++++++++++++++++++++++++++++++++++++++\n worktree.c              | 32 +++++++++++++++++++++++++++++++-\n worktree.h              |  3 ++-\n 9 files changed, 79 insertions(+), 10 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex a84fb2c..8e323d3 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -334,11 +334,11 @@ void remove_branch_state(void)\n \tunlink(git_path_squash_msg());\n }\n \n-void die_if_checked_out(const char *branch)\n+void die_if_checked_out(const char *branch, int ignore_current_worktree)\n {\n \tconst struct worktree *wt;\n \n-\twt = find_shared_symref(\"HEAD\", branch);\n+\twt = find_shared_symref(\"HEAD\", branch, ignore_current_worktree);\n \tif (wt) {\n \t\tskip_prefix(branch, \"refs/heads/\", &branch);\n \t\tdie(_(\"'%s' is already checked out at '%s'\"),\ndiff --git a/branch.h b/branch.h\nindex d69163d..b2f9649 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -58,7 +58,7 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);\n  * worktree and die (with a message describing its checkout location) if\n  * it is.\n  */\n-extern void die_if_checked_out(const char *branch);\n+extern void die_if_checked_out(const char *branch, int ignore_current_worktree);\n \n /*\n  * Update all per-worktree HEADs pointing at the old ref to point the new ref.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex bcde87d..bf91bbd 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -221,7 +221,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \n \t\tif (kinds == FILTER_REFS_BRANCHES) {\n \t\t\tconst struct worktree *wt =\n-\t\t\t\tfind_shared_symref(\"HEAD\", name);\n+\t\t\t\tfind_shared_symref(\"HEAD\", name, 0);\n \t\t\tif (wt) {\n \t\t\t\terror(_(\"Cannot delete branch '%s' \"\n \t\t\t\t\t\"checked out at '%s'\"),\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex efcbd8f..6041718 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1111,7 +1111,7 @@ static int checkout_branch(struct checkout_opts *opts,\n \t\tchar *head_ref = resolve_refdup(\"HEAD\", 0, sha1, &flag);\n \t\tif (head_ref &&\n \t\t    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))\n-\t\t\tdie_if_checked_out(new->path);\n+\t\t\tdie_if_checked_out(new->path, 1);\n \t\tfree(head_ref);\n \t}\n \ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex c65b59a..f154a69 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -852,7 +852,7 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\tupdate_ref(msg.buf, \"NOTES_MERGE_PARTIAL\", result_sha1, NULL,\n \t\t\t   0, UPDATE_REFS_DIE_ON_ERR);\n \t\t/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */\n-\t\twt = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref());\n+\t\twt = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref(), 0);\n \t\tif (wt)\n \t\t\tdie(_(\"A notes merge into %s is already in-progress at %s\"),\n \t\t\t    default_notes_ref(), wt->path);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d8e3795..12c0af7 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname,\n \tif (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&\n \t\t ref_exists(symref.buf)) { /* it's a branch */\n \t\tif (!opts->force)\n-\t\t\tdie_if_checked_out(symref.buf);\n+\t\t\tdie_if_checked_out(symref.buf, 0);\n \t} else { /* must be a commit */\n \t\tcommit = lookup_commit_reference_by_name(refname);\n \t\tif (!commit)\n@@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix)\n \t\tif (!opts.force &&\n \t\t    !strbuf_check_branch_ref(&symref, opts.new_branch) &&\n \t\t    ref_exists(symref.buf))\n-\t\t\tdie_if_checked_out(symref.buf);\n+\t\t\tdie_if_checked_out(symref.buf, 0);\n \t\tstrbuf_release(&symref);\n \t}\n \ndiff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh\nindex 3acb992..da54327 100755\n--- a/t/t2025-worktree-add.sh\n+++ b/t/t2025-worktree-add.sh\n@@ -4,6 +4,8 @@ test_description='test git worktree add'\n \n . ./test-lib.sh\n \n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n test_expect_success 'setup' '\n \ttest_commit init\n '\n@@ -225,4 +227,40 @@ test_expect_success '\"add\" worktree with --checkout' '\n \ttest_cmp init.t swamp2/init.t\n '\n \n+test_expect_success 'put a worktree under rebase' '\n+\tgit worktree add under-rebase &&\n+\t(\n+\t\tcd under-rebase &&\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"edit 1\" git rebase -i HEAD^ &&\n+\t\tgit worktree list | grep \"under-rebase.*detached HEAD\"\n+\t)\n+'\n+\n+test_expect_success 'add a worktree, checking out a rebased branch' '\n+\ttest_must_fail git worktree add new-rebase under-rebase &&\n+\t! test -d new-rebase\n+'\n+\n+test_expect_success 'checking out a rebased branch from another worktree' '\n+\tgit worktree add new-place &&\n+\ttest_must_fail git -C new-place checkout under-rebase\n+'\n+\n+test_expect_success 'not allow to delete a branch under rebase' '\n+\t(\n+\t\tcd under-rebase &&\n+\t\ttest_must_fail git branch -D under-rebase\n+\t)\n+'\n+\n+test_expect_success 'check out from current worktree branch ok' '\n+\t(\n+\t\tcd under-rebase &&\n+\t\tgit checkout under-rebase &&\n+\t\tgit checkout - &&\n+\t\tgit rebase --abort\n+\t)\n+'\n+\n test_done\ndiff --git a/worktree.c b/worktree.c\nindex b5ca78f..dc380a2 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -3,6 +3,7 @@\n #include \"strbuf.h\"\n #include \"worktree.h\"\n #include \"dir.h\"\n+#include \"wt-status.h\"\n \n void free_worktrees(struct worktree **worktrees)\n {\n@@ -207,8 +208,27 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n+static int is_worktree_being_rebased(const struct worktree *wt,\n+\t\t\t\t     const char *target)\n+{\n+\tstruct wt_status_state state;\n+\tint found_rebase;\n+\n+\tmemset(&state, 0, sizeof(state));\n+\tfound_rebase = wt_status_check_rebase(wt, &state) &&\n+\t\t((state.rebase_in_progress ||\n+\t\t  state.rebase_interactive_in_progress) &&\n+\t\t state.branch &&\n+\t\t starts_with(target, \"refs/heads/\") &&\n+\t\t !strcmp(state.branch, target + strlen(\"refs/heads/\")));\n+\tfree(state.branch);\n+\tfree(state.onto);\n+\treturn found_rebase;\n+}\n+\n const struct worktree *find_shared_symref(const char *symref,\n-\t\t\t\t\t  const char *target)\n+\t\t\t\t\t  const char *target,\n+\t\t\t\t\t  int ignore_current_worktree)\n {\n \tconst struct worktree *existing = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n@@ -223,6 +243,16 @@ const struct worktree *find_shared_symref(const char *symref,\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstruct worktree *wt = worktrees[i];\n \n+\t\tif (ignore_current_worktree && wt->is_current)\n+\t\t\tcontinue;\n+\n+\t\tif (wt->is_detached && !strcmp(symref, \"HEAD\")) {\n+\t\t\tif (is_worktree_being_rebased(wt, target)) {\n+\t\t\t\texisting = wt;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n \t\tstrbuf_addf(&path, \"%s/%s\",\ndiff --git a/worktree.h b/worktree.h\nindex 9d2463e..fb9f5cc 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -41,7 +41,8 @@ extern void free_worktrees(struct worktree **);\n  * may be destroyed by the next call.\n  */\n extern const struct worktree *find_shared_symref(const char *symref,\n-\t\t\t\t\t\t const char *target);\n+\t\t\t\t\t\t const char *target,\n+\t\t\t\t\t\t int ignore_current_worktree);\n \n /*\n  * Similar to git_path() and git_pathdup() but can produce paths for a\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283943","messageId":"1461158693-21289-11-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 10/12] wt-status.c: split bisect detection out of wt_status_get_state()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:51Z","receivedAt":"2016-04-20T13:24:51Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"And make it work with any given worktree, in preparation for (again)\nfind_shared_symref(). read_and_strip_branch() is deleted because it's\nno longer used.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n wt-status.c | 23 ++++++++++++++---------\n wt-status.h |  2 ++\n 2 files changed, 16 insertions(+), 9 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 2295682..36c85f8 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1296,11 +1296,6 @@ got_nothing:\n \treturn NULL;\n }\n \n-static char *read_and_strip_branch(const char *path)\n-{\n-\treturn get_branch(NULL, path);\n-}\n-\n struct grab_1st_switch_cbdata {\n \tstruct strbuf buf;\n \tunsigned char nsha1[20];\n@@ -1393,6 +1388,19 @@ int wt_status_check_rebase(const struct worktree *wt,\n \treturn 1;\n }\n \n+int wt_status_check_bisect(const struct worktree *wt,\n+\t\t\t   struct wt_status_state *state)\n+{\n+\tstruct stat st;\n+\n+\tif (!stat(worktree_git_path(wt, \"BISECT_LOG\"), &st)) {\n+\t\tstate->bisect_in_progress = 1;\n+\t\tstate->branch = get_branch(wt, \"BISECT_START\");\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n void wt_status_get_state(struct wt_status_state *state,\n \t\t\t int get_detached_from)\n {\n@@ -1408,10 +1416,7 @@ void wt_status_get_state(struct wt_status_state *state,\n \t\tstate->cherry_pick_in_progress = 1;\n \t\thashcpy(state->cherry_pick_head_sha1, sha1);\n \t}\n-\tif (!stat(git_path(\"BISECT_LOG\"), &st)) {\n-\t\tstate->bisect_in_progress = 1;\n-\t\tstate->branch = read_and_strip_branch(\"BISECT_START\");\n-\t}\n+\twt_status_check_bisect(NULL, state);\n \tif (!stat(git_path_revert_head(), &st) &&\n \t    !get_sha1(\"REVERT_HEAD\", sha1)) {\n \t\tstate->revert_in_progress = 1;\ndiff --git a/wt-status.h b/wt-status.h\nindex c4ddcad..2ca93f6 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -104,6 +104,8 @@ void wt_status_collect(struct wt_status *s);\n void wt_status_get_state(struct wt_status_state *state, int get_detached_from);\n int wt_status_check_rebase(const struct worktree *wt,\n \t\t\t   struct wt_status_state *state);\n+int wt_status_check_bisect(const struct worktree *wt,\n+\t\t\t   struct wt_status_state *state);\n \n void wt_shortstatus_print(struct wt_status *s);\n void wt_porcelain_print(struct wt_status *s);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283942","messageId":"1461158693-21289-12-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 11/12] worktree.c: test if branch being bisected in another worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:52Z","receivedAt":"2016-04-20T13:24:52Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Similar to the rebase case, we want to detect if \"HEAD\" in some worktree\nis being bisected because\n\n1) we do not want to checkout this branch in another worktree, after\n   bisect is done it will want to go back to this branch\n\n2) we do not want to delete the branch is either or git bisect will\n   fail to return to the (long gone) branch\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n t/t2025-worktree-add.sh | 13 +++++++++++++\n worktree.c              | 19 +++++++++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh\nindex da54327..8f53944 100755\n--- a/t/t2025-worktree-add.sh\n+++ b/t/t2025-worktree-add.sh\n@@ -263,4 +263,17 @@ test_expect_success 'check out from current worktree branch ok' '\n \t)\n '\n \n+test_expect_success 'checkout a branch under bisect' '\n+\tgit worktree add under-bisect &&\n+\t(\n+\t\tcd under-bisect &&\n+\t\tgit bisect start &&\n+\t\tgit bisect bad &&\n+\t\tgit bisect good HEAD~2 &&\n+\t\tgit worktree list | grep \"under-bisect.*detached HEAD\" &&\n+\t\ttest_must_fail git worktree add new-bisect under-bisect &&\n+\t\t! test -d new-bisect\n+\t)\n+'\n+\n test_done\ndiff --git a/worktree.c b/worktree.c\nindex dc380a2..7b66071 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -226,6 +226,21 @@ static int is_worktree_being_rebased(const struct worktree *wt,\n \treturn found_rebase;\n }\n \n+static int is_worktree_being_bisected(const struct worktree *wt,\n+\t\t\t\t      const char *target)\n+{\n+\tstruct wt_status_state state;\n+\tint found_rebase;\n+\n+\tmemset(&state, 0, sizeof(state));\n+\tfound_rebase = wt_status_check_bisect(wt, &state) &&\n+\t\tstate.branch &&\n+\t\tstarts_with(target, \"refs/heads/\") &&\n+\t\t!strcmp(state.branch, target + strlen(\"refs/heads/\"));\n+\tfree(state.branch);\n+\treturn found_rebase;\n+}\n+\n const struct worktree *find_shared_symref(const char *symref,\n \t\t\t\t\t  const char *target,\n \t\t\t\t\t  int ignore_current_worktree)\n@@ -251,6 +266,10 @@ const struct worktree *find_shared_symref(const char *symref,\n \t\t\t\texisting = wt;\n \t\t\t\tbreak;\n \t\t\t}\n+\t\t\tif (is_worktree_being_bisected(wt, target)) {\n+\t\t\t\texisting = wt;\n+\t\t\t\tbreak;\n+\t\t\t}\n \t\t}\n \n \t\tstrbuf_reset(&path);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283945","messageId":"1461158693-21289-13-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 12/12] branch: do not rename a branch under bisect or rebase","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:24:53Z","receivedAt":"2016-04-20T13:24:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The branch name in that case could be saved in rebase's head_name or\nbisect's BISECT_START files. Ideally we should try to update them as\nwell. But it's trickier (*). Let's play safe and see if the user\ncomplains about inconveniences before doing that.\n\n(*) If we do it, bisect and rebase need to provide an API to rename\nbranches. We can't do it in worktree.c or builtin/branch.c because\nwhen other people change rebase/bisect code, they may not be aware of\nthis code and accidentally break it (e.g. rename the branch file, or\nrefer to the branch in new files). It's a lot more work.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/branch.c        | 25 +++++++++++++++++++++++++\n t/t2025-worktree-add.sh |  8 ++++++++\n worktree.c              |  8 ++++----\n worktree.h              |  3 +++\n 4 files changed, 40 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex bf91bbd..3a2eceb 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -524,6 +524,29 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin\n \tref_array_clear(&array);\n }\n \n+static void reject_rebase_or_bisect_branch(const char *target)\n+{\n+\tstruct worktree **worktrees = get_worktrees();\n+\tint i;\n+\n+\tfor (i = 0; worktrees[i]; i++) {\n+\t\tstruct worktree *wt = worktrees[i];\n+\n+\t\tif (!wt->is_detached)\n+\t\t\tcontinue;\n+\n+\t\tif (is_worktree_being_rebased(wt, target))\n+\t\t\tdie(_(\"Branch %s is being rebased at %s\"),\n+\t\t\t    target, wt->path);\n+\n+\t\tif (is_worktree_being_bisected(wt, target))\n+\t\t\tdie(_(\"Branch %s is being bisected at %s\"),\n+\t\t\t    target, wt->path);\n+\t}\n+\n+\tfree_worktrees(worktrees);\n+}\n+\n static void rename_branch(const char *oldname, const char *newname, int force)\n {\n \tstruct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;\n@@ -553,6 +576,8 @@ static void rename_branch(const char *oldname, const char *newname, int force)\n \n \tvalidate_new_branchname(newname, &newref, force, clobber_head_ok);\n \n+\treject_rebase_or_bisect_branch(oldref.buf);\n+\n \tstrbuf_addf(&logmsg, \"Branch: renamed %s to %s\",\n \t\t oldref.buf, newref.buf);\n \ndiff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh\nindex 8f53944..3a22fc5 100755\n--- a/t/t2025-worktree-add.sh\n+++ b/t/t2025-worktree-add.sh\n@@ -254,6 +254,10 @@ test_expect_success 'not allow to delete a branch under rebase' '\n \t)\n '\n \n+test_expect_success 'rename a branch under rebase not allowed' '\n+\ttest_must_fail git branch -M under-rebase rebase-with-new-name\n+'\n+\n test_expect_success 'check out from current worktree branch ok' '\n \t(\n \t\tcd under-rebase &&\n@@ -276,4 +280,8 @@ test_expect_success 'checkout a branch under bisect' '\n \t)\n '\n \n+test_expect_success 'rename a branch under bisect not allowed' '\n+\ttest_must_fail git branch -M under-bisect bisect-with-new-name\n+'\n+\n test_done\ndiff --git a/worktree.c b/worktree.c\nindex 7b66071..8a3d394 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -208,8 +208,8 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n-static int is_worktree_being_rebased(const struct worktree *wt,\n-\t\t\t\t     const char *target)\n+int is_worktree_being_rebased(const struct worktree *wt,\n+\t\t\t      const char *target)\n {\n \tstruct wt_status_state state;\n \tint found_rebase;\n@@ -226,8 +226,8 @@ static int is_worktree_being_rebased(const struct worktree *wt,\n \treturn found_rebase;\n }\n \n-static int is_worktree_being_bisected(const struct worktree *wt,\n-\t\t\t\t      const char *target)\n+int is_worktree_being_bisected(const struct worktree *wt,\n+\t\t\t       const char *target)\n {\n \tstruct wt_status_state state;\n \tint found_rebase;\ndiff --git a/worktree.h b/worktree.h\nindex fb9f5cc..d4a3534 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -44,6 +44,9 @@ extern const struct worktree *find_shared_symref(const char *symref,\n \t\t\t\t\t\t const char *target,\n \t\t\t\t\t\t int ignore_current_worktree);\n \n+int is_worktree_being_rebased(const struct worktree *wt, const char *target);\n+int is_worktree_being_bisected(const struct worktree *wt, const char *target);\n+\n /*\n  * Similar to git_path() and git_pathdup() but can produce paths for a\n  * specified worktree instead of current one\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"283946","messageId":"57178897.20509@ramsayjones.plus.com","threadId":"42034","inReplyTo":"1461158693-21289-7-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 06/12] wt-status.c: split rebase detection out of wt_status_get_state()","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2016-04-20T13:48:07Z","receivedAt":"2016-04-20T13:48:07Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 20/04/16 14:24, Nguyễn Thái Ngọc Duy wrote:\n> worktree.c:find_shared_symref() later needs to know if a branch is being\n> rebased, and only rebased only. Split this code so it can be used\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nErr ... what?\n\nATB,\nRamsay Jones\n"},{"id":"283947","messageId":"CACsJy8CV+MtLYumsej6VDkBZMqTmSHedpVhe6pwhz_Vokw3_fA@mail.gmail.com","threadId":"42034","inReplyTo":"57178897.20509@ramsayjones.plus.com","subject":"Re: [PATCH v2 06/12] wt-status.c: split rebase detection out of wt_status_get_state()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-20T13:54:27Z","receivedAt":"2016-04-20T13:54:27Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Apr 20, 2016 at 8:48 PM, Ramsay Jones\n<ramsay@ramsayjones.plus.com> wrote:\n>\n>\n> On 20/04/16 14:24, Nguyễn Thái Ngọc Duy wrote:\n>> worktree.c:find_shared_symref() later needs to know if a branch is being\n>> rebased, and only rebased only. Split this code so it can be used\n> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n> Err ... what?\n\nwt_status_get_state() detects more than rebase, it does bisect,\ncherry-pick, detached head as well. I \"only\" too much there though.\n-- \nDuy\n"},{"id":"283968","messageId":"xmqqlh48w6rh.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"1461158693-21289-4-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 03/12] worktree.c: make find_shared_symref() return struct worktree *","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-20T17:05:06Z","receivedAt":"2016-04-20T17:05:06Z","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> This gives the caller more information and they can answer things like,\n> \"is it the main worktree\" or \"is it the current worktree\". The latter\n> question is needed for the \"checkout a rebase branch\" case later.\n\nThat makes good sense.\n\n> diff --git a/worktree.h b/worktree.h\n> index 3198c8d..d71d7ec 100644\n> --- a/worktree.h\n> +++ b/worktree.h\n> @@ -36,9 +36,10 @@ extern void free_worktrees(struct worktree **);\n>  /*\n>   * Check if a per-worktree symref points to a ref in the main worktree\n>   * or any linked worktree, and return the path to the exising worktree\n> - * if it is.  Returns NULL if there is no existing ref.  The caller is\n> - * responsible for freeing the returned path.\n> + * if it is. Returns NULL if there is no existing ref. The result\n> + * may be destroyed by the next call.\n>   */\n\nTo return and keep alive one worktree[] instance (i.e. \"existing\"),\nthe code holds onto the entire return value from get_worktrees(), if\nI am not misreading it.  Typically you would have only a handful of\nworktrees so this may not be an issue, though.\n"},{"id":"283971","messageId":"xmqqh9eww40g.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"1461158693-21289-10-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 09/12] worktree.c: test if branch being rebased in another worktree","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-20T18:04:31Z","receivedAt":"2016-04-20T18:04:31Z","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> Subject: Re: [PATCH v2 09/12] worktree.c: test if branch being rebased in another worktree\n\nLacks the verb?  Perhaps s/being/is/ is sufficient.\n\n> This function find_shared_symref() is used in a couple places:\n>\n> 1) in builtin/branch.c: it's used to detect if a branch is checked out\n>    elsewhere and refuse to delete the branch.\n>\n> 2) in builtin/notes.c: it's used to detect if a note is being merged in\n>    another worktree\n>\n> 3) in branch.c, the function die_if_checked_out() is actually used by\n>    \"git checkout\" and \"git worktree add\" to see if a branch is already\n>    checked out elsewhere and refuse the operation.\n>\n> In cases 1 and 3, if a rebase is ongoing, \"HEAD\" will be in detached\n> mode, find_shared_symref() fails to detect it and declares \"no branch is\n> checked out here\", which is incorrect.\n\nwhich is technically correct but is not what we want to check.\n\n> This patch tightens the test. If the given symref is \"HEAD\", we try to\n> detect if rebase is ongoing. If so return the branch being rebased. This\n> makes checkout and branch delete operations safer because you can't\n> checkout a branch being rebased in another place, or delete it.\n\nIs rebase the only thing that tentatively detach before working on\nthe real branch?  It may be currently the case, but I would imagine\nthat we want to makefind-shared-symref to be responsible for\ndetecting new cases other than rebase that we may introduce in the\nfuture, in which case we may want to leave come comment near the\nfunction to describe that expectation.\n\n> Special case for checkout. If the current branch is being rebased,\n> git-rebase.sh may use \"git checkout\" to abort and return back to the\n> original branch. The updated test in find_shared_symref() will prevent\n> that and \"git rebase --abort\" will fail as a result.\n> find_shared_symref() and die_if_checked_out() have to learn a new\n> option ignore_current_worktree to loose the test a bit.\n\ns/loose/&n/\n\n> +void die_if_checked_out(const char *branch, int ignore_current_worktree)\n>  {\n>  \tconst struct worktree *wt;\n>  \n> -\twt = find_shared_symref(\"HEAD\", branch);\n> +\twt = find_shared_symref(\"HEAD\", branch, ignore_current_worktree);\n>  \tif (wt) {\n>  \t\tskip_prefix(branch, \"refs/heads/\", &branch);\n>  \t\tdie(_(\"'%s' is already checked out at '%s'\"),\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index efcbd8f..6041718 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -1111,7 +1111,7 @@ static int checkout_branch(struct checkout_opts *opts,\n>  \t\tchar *head_ref = resolve_refdup(\"HEAD\", 0, sha1, &flag);\n>  \t\tif (head_ref &&\n>  \t\t    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))\n> -\t\t\tdie_if_checked_out(new->path);\n> +\t\t\tdie_if_checked_out(new->path, 1);\n>  \t\tfree(head_ref);\n>  \t}\n\nSo the idea is \"if the branch is checked out (or \"being worked on\"\neven if technically the HEAD is detached, like with 'rebase')\nanywhere, callers of die-if-checked-out in general want to die; but\nfor this caller, it is OK if the place the branch is checked out or\nbeing worked on is in this repository\"?\n\nI understand die_if_checked_out() taking that \"ignore this one\" bit\nmay be sensible, but I do not understand why find_shared_symref()\nneeds to be told about it.  The change makes the meaning of the\nfind_shared_symref() function unclear.  It used to mean \"This\nsymbolic ref cannot point at the same ref in different worktrees, so\nfor a given pair of a symbolic ref and a concrete ref, there can be\nat most one worktree in which the symbolic ref points at that ref\".\nThat is already a mouthful.  As the worktree structure already have\n\"Am I the current worktree?\" bit, \"ignore\" logic can easily be done\ninside die_if_checked_out() and that would help find_shared_symref()\nstay simpler and more focused function, no?\n"},{"id":"283972","messageId":"xmqqd1pkw3sh.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 00/12] fix checking out a being-rebased branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-20T18:09:18Z","receivedAt":"2016-04-20T18:09:18Z","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> Much happier with this version. This makes\n>\n>  - git checkout refuse if a branch is under rebase or bisect\n>    elsewhere\n>\n>  - git worktree add refuse if a branch is under rebase or bisect\n>\n>  - git branch -D refuse if a branch is under rebase or bisect.\n>    This applies for single worktree case as well.\n>\n>  - git branch -M refuse if a branch is under rebase or bisect\n>    (single worktree case as well)\n\nI agree that this reads much nicer than v1, especially with the\nabstraction around find_shared_symref() that returns the actual\nworktree.\n\n>   [01/12] path.c: add git_common_path() and strbuf_git_common_path()\n>   [02/12] worktree.c: store \"id\" instead of \"git_dir\"\n>   [03/12] worktree.c: make find_shared_symref() return struct worktree *\n>   [04/12] worktree.c: mark current worktree\n>   [05/12] path.c: refactor and add worktree_git_path()\n>   [06/12] wt-status.c: split rebase detection out of wt_status_get_state()\n>   [07/12] wt-status.c: make wt_status_check_rebase() work on any worktree\n>   [08/12] worktree.c: avoid referencing to worktrees[i] multiple times\n>   [09/12] worktree.c: test if branch being rebased in another worktree\n>   [10/12] wt-status.c: split bisect detection out of wt_status_get_state()\n>   [11/12] worktree.c: test if branch being bisected in another worktree\n>   [12/12] branch: do not rename a branch under bisect or rebase\n>\n> Total 13 files changed, 335 insertions(+), 67 deletions(-)\n"},{"id":"283973","messageId":"CAPig+cSRQBJM9xFnDszC84Z3JaF83sOg7=z-w6nRQdcu6nA3Bw@mail.gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 01/12] path.c: add git_common_path() and strbuf_git_common_path()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-04-20T18:11:15Z","receivedAt":"2016-04-20T18:11:15Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Apr 20, 2016 at 9:24 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> diff --git a/path.c b/path.c\n> @@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,\n> +const char *git_common_path(const char *fmt, ...)\n> +{\n> +       struct strbuf *pathname = get_pathname();\n> +       va_list args;\n> +       va_start(args, fmt);\n> +       do_git_common_path(pathname, fmt, args);\n> +       va_end(args);\n> +       return pathname->buf;\n\nIs the caller expected to free this value? If not, then shouldn't\n'pathname' be static? If so, then perhaps strbuf_detach() would be\nclearer (and return 'char *' rather than 'const char *').\n\n> +}\n"},{"id":"284013","messageId":"CACsJy8BYmXi_a-sJ2Z+J-rd+DTyQYnznc87_2EHfv_Ym-Fz0pg@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cSRQBJM9xFnDszC84Z3JaF83sOg7=z-w6nRQdcu6nA3Bw@mail.gmail.com","subject":"Re: [PATCH v2 01/12] path.c: add git_common_path() and strbuf_git_common_path()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-21T00:28:06Z","receivedAt":"2016-04-21T00:28:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Apr 21, 2016 at 1:11 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Wed, Apr 20, 2016 at 9:24 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> diff --git a/path.c b/path.c\n>> @@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,\n>> +const char *git_common_path(const char *fmt, ...)\n>> +{\n>> +       struct strbuf *pathname = get_pathname();\n>> +       va_list args;\n>> +       va_start(args, fmt);\n>> +       do_git_common_path(pathname, fmt, args);\n>> +       va_end(args);\n>> +       return pathname->buf;\n>\n> Is the caller expected to free this value? If not, then shouldn't\n> 'pathname' be static? If so, then perhaps strbuf_detach() would be\n> clearer (and return 'char *' rather than 'const char *').\n\nget_pathname() actually holds a ring of static buffer. So no, we don't\nneed static pathname, it can be a new buffer next time, mostly to to\nmake printf(\"%s %s\", git_common_path(..), git_common_path(..)) work.\nAnd no the caller is not supposed to free it, a little bit more\nconvenient.\n-- \nDuy\n"},{"id":"284014","messageId":"CACsJy8CSuy_ZnNNm+w+KjGkz9AF2CLLXcRZ6sTtp-VFOkNA73g@mail.gmail.com","threadId":"42034","inReplyTo":"xmqqh9eww40g.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v2 09/12] worktree.c: test if branch being rebased in another worktree","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-21T00:37:03Z","receivedAt":"2016-04-21T00:37:03Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Apr 21, 2016 at 1:04 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> diff --git a/builtin/checkout.c b/builtin/checkout.c\n>> index efcbd8f..6041718 100644\n>> --- a/builtin/checkout.c\n>> +++ b/builtin/checkout.c\n>> @@ -1111,7 +1111,7 @@ static int checkout_branch(struct checkout_opts *opts,\n>>               char *head_ref = resolve_refdup(\"HEAD\", 0, sha1, &flag);\n>>               if (head_ref &&\n>>                   (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))\n>> -                     die_if_checked_out(new->path);\n>> +                     die_if_checked_out(new->path, 1);\n>>               free(head_ref);\n>>       }\n>\n> So the idea is \"if the branch is checked out (or \"being worked on\"\n> even if technically the HEAD is detached, like with 'rebase')\n> anywhere, callers of die-if-checked-out in general want to die; but\n> for this caller, it is OK if the place the branch is checked out or\n> being worked on is in this repository\"?\n>\n> I understand die_if_checked_out() taking that \"ignore this one\" bit\n> may be sensible, but I do not understand why find_shared_symref()\n> needs to be told about it.  The change makes the meaning of the\n> find_shared_symref() function unclear.  It used to mean \"This\n> symbolic ref cannot point at the same ref in different worktrees, so\n> for a given pair of a symbolic ref and a concrete ref, there can be\n> at most one worktree in which the symbolic ref points at that ref\".\n> That is already a mouthful.  As the worktree structure already have\n> \"Am I the current worktree?\" bit, \"ignore\" logic can easily be done\n> inside die_if_checked_out() and that would help find_shared_symref()\n> stay simpler and more focused function, no?\n\nThat was the intention when I made find_shared_symref() return struct\nworktree * instead of char *. I forget why I changed my mind and not\ndo so. The only case when find_shared_symref should do this is when a\nref is shared twice, then we need to ignore current worktree from\ninside the loop. But that can't happen. Will move this\nignore-current-worktree test to die_if_checked_out().\n-- \nDuy\n"},{"id":"284019","messageId":"CAPig+cTMRJYFwzRbsoyd06BKwKOLf0LJirnB5rp4fWDb09r6Fg@mail.gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-4-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 03/12] worktree.c: make find_shared_symref() return struct worktree *","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-04-21T07:02:53Z","receivedAt":"2016-04-21T07:02:53Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Apr 20, 2016 at 9:24 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> This gives the caller more information and they can answer things like,\n> \"is it the main worktree\" or \"is it the current worktree\". The latter\n> question is needed for the \"checkout a rebase branch\" case later.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/worktree.h b/worktree.h\n> @@ -36,9 +36,10 @@ extern void free_worktrees(struct worktree **);\n>  /*\n>   * Check if a per-worktree symref points to a ref in the main worktree\n>   * or any linked worktree, and return the path to the exising worktree\n\nDoesn't \"return the path\" become outdated with this patch?\n\nAlso (not a new problem): s/exising/existing/\n\n> - * if it is.  Returns NULL if there is no existing ref.  The caller is\n> - * responsible for freeing the returned path.\n> + * if it is. Returns NULL if there is no existing ref. The result\n> + * may be destroyed by the next call.\n>   */\n> -extern char *find_shared_symref(const char *symref, const char *target);\n> +extern const struct worktree *find_shared_symref(const char *symref,\n> +                                                const char *target);\n"},{"id":"284021","messageId":"CAPig+cR99Nx=dk+zT2Jgpr2ZgYM4v0q2vX7v6TXmWHU5k6ga3w@mail.gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-5-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 04/12] worktree.c: mark current worktree","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-04-21T07:20:07Z","receivedAt":"2016-04-21T07:20:07Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Apr 20, 2016 at 9:24 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/worktree.c b/worktree.c\n> @@ -178,6 +182,18 @@ struct worktree **get_worktrees(void)\n>         }\n>         ALLOC_GROW(list, counter + 1, alloc);\n>         list[counter] = NULL;\n> +\n> +       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n> +       for (i = 0; i < counter; i++) {\n> +               struct worktree *wt = list[i];\n> +               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n> +               wt->is_current = !strcmp_icase(git_dir.buf, path.buf);\n\nCan you talk a bit about why this uses 'icase'? Should it be\nrespecting cache.h:ignore_case?\n\n> +               strbuf_reset(&path);\n> +               if (wt->is_current)\n> +                       break;\n> +       }\n> +       strbuf_release(&git_dir);\n> +       strbuf_release(&path);\n\nMinor: Would it make sense to place this new code in its own function\n-- say, mark_current_worktree() -- to keep get_worktrees() from\nbecoming overlong?\n\n>         return list;\n>  }\n"},{"id":"284023","messageId":"CACsJy8DwzfsPthWy6pG2t0cGVT0kcrJT7xWdyvs+0EPFDbNtSQ@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cR99Nx=dk+zT2Jgpr2ZgYM4v0q2vX7v6TXmWHU5k6ga3w@mail.gmail.com","subject":"Re: [PATCH v2 04/12] worktree.c: mark current worktree","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-21T08:19:48Z","receivedAt":"2016-04-21T08:19:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Apr 21, 2016 at 2:20 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Wed, Apr 20, 2016 at 9:24 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>> diff --git a/worktree.c b/worktree.c\n>> @@ -178,6 +182,18 @@ struct worktree **get_worktrees(void)\n>>         }\n>>         ALLOC_GROW(list, counter + 1, alloc);\n>>         list[counter] = NULL;\n>> +\n>> +       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n>> +       for (i = 0; i < counter; i++) {\n>> +               struct worktree *wt = list[i];\n>> +               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n>> +               wt->is_current = !strcmp_icase(git_dir.buf, path.buf);\n>\n> Can you talk a bit about why this uses 'icase'? Should it be\n> respecting cache.h:ignore_case?\n\nIt does.That function (in dir.c) is just one-liner\n\n    return ignore_case ? strcasecmp(a, b) : strcmp(a, b);\n\nI admit though, the naming does not make that clear.\n\n>> +               strbuf_reset(&path);\n>> +               if (wt->is_current)\n>> +                       break;\n>> +       }\n>> +       strbuf_release(&git_dir);\n>> +       strbuf_release(&path);\n>\n> Minor: Would it make sense to place this new code in its own function\n> -- say, mark_current_worktree() -- to keep get_worktrees() from\n> becoming overlong?\n\nGood idea. Will do.\n-- \nDuy\n"},{"id":"284027","messageId":"CACsJy8ATaYx0Fk-7HK9Rz_o687hxCLuOJYJ6DkBd4Xk8sP+byw@mail.gmail.com","threadId":"42034","inReplyTo":"CACsJy8DwzfsPthWy6pG2t0cGVT0kcrJT7xWdyvs+0EPFDbNtSQ@mail.gmail.com","subject":"Re: [PATCH v2 04/12] worktree.c: mark current worktree","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-21T09:33:09Z","receivedAt":"2016-04-21T09:33:09Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Apr 21, 2016 at 3:19 PM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Thu, Apr 21, 2016 at 2:20 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> On Wed, Apr 20, 2016 at 9:24 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>>> ---\n>>> diff --git a/worktree.c b/worktree.c\n>>> @@ -178,6 +182,18 @@ struct worktree **get_worktrees(void)\n>>>         }\n>>>         ALLOC_GROW(list, counter + 1, alloc);\n>>>         list[counter] = NULL;\n>>> +\n>>> +       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n>>> +       for (i = 0; i < counter; i++) {\n>>> +               struct worktree *wt = list[i];\n>>> +               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n>>> +               wt->is_current = !strcmp_icase(git_dir.buf, path.buf);\n>>\n>> Can you talk a bit about why this uses 'icase'? Should it be\n>> respecting cache.h:ignore_case?\n>\n> It does.That function (in dir.c) is just one-liner\n>\n>     return ignore_case ? strcasecmp(a, b) : strcmp(a, b);\n>\n> I admit though, the naming does not make that clear.\n\nWhile we're at it, how about renaming it to pathcmp (and its friend\nstrncmp_icase to pathncmp)?\n-- \nDuy\n"},{"id":"284035","messageId":"CAPig+cQkT8vb-OVdS_ALVuDii9njY6BN2FhkXt=CavAfyeO2Fg@mail.gmail.com","threadId":"42034","inReplyTo":"CACsJy8ATaYx0Fk-7HK9Rz_o687hxCLuOJYJ6DkBd4Xk8sP+byw@mail.gmail.com","subject":"Re: [PATCH v2 04/12] worktree.c: mark current worktree","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-04-21T14:23:09Z","receivedAt":"2016-04-21T14:23:09Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Apr 21, 2016 at 5:33 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Thu, Apr 21, 2016 at 3:19 PM, Duy Nguyen <pclouds@gmail.com> wrote:\n>> On Thu, Apr 21, 2016 at 2:20 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>>> On Wed, Apr 20, 2016 at 9:24 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>>>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>>>> +       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n>>>> +       for (i = 0; i < counter; i++) {\n>>>> +               struct worktree *wt = list[i];\n>>>> +               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n>>>> +               wt->is_current = !strcmp_icase(git_dir.buf, path.buf);\n>>>\n>>> Can you talk a bit about why this uses 'icase'? Should it be\n>>> respecting cache.h:ignore_case?\n>>\n>> It does.That function (in dir.c) is just one-liner\n>>\n>>     return ignore_case ? strcasecmp(a, b) : strcmp(a, b);\n>>\n>> I admit though, the naming does not make that clear.\n\nUgh, this is only the fourth patch, yet the second stupid review\nmistake I've made thus far in this series. For some reason, I kept\nreading this as a call to strcasecmp() or stricmp() rather than\nstrcmp_icase(). Worse, I had even consulted path.c:strcmp_icase() to\nsee how the issue was handled there.\n\n> While we're at it, how about renaming it to pathcmp (and its friend\n> strncmp_icase to pathncmp)?\n\nYes, that seems like a good idea. For anyone familiar with\nstrcasecmp() or stricmp(), having \"icase\" in the name makes it seem as\nthough it's unconditionally case-insensitive, so dropping it from the\nname would likely be beneficial.\n"},{"id":"284036","messageId":"20160421151355.GA6200@sigill.intra.peff.net","threadId":"42034","inReplyTo":"CAPig+cQkT8vb-OVdS_ALVuDii9njY6BN2FhkXt=CavAfyeO2Fg@mail.gmail.com","subject":"Re: [PATCH v2 04/12] worktree.c: mark current worktree","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-04-21T15:13:56Z","receivedAt":"2016-04-21T15:13:56Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Apr 21, 2016 at 10:23:09AM -0400, Eric Sunshine wrote:\n\n> > While we're at it, how about renaming it to pathcmp (and its friend\n> > strncmp_icase to pathncmp)?\n> \n> Yes, that seems like a good idea. For anyone familiar with\n> strcasecmp() or stricmp(), having \"icase\" in the name makes it seem as\n> though it's unconditionally case-insensitive, so dropping it from the\n> name would likely be beneficial.\n\nSeconded (thirded?). I have been caught by this confusion in the past,\ntoo.\n\n-Peff\n"},{"id":"284037","messageId":"xmqqk2jrt1kz.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160421151355.GA6200@sigill.intra.peff.net","subject":"Re: [PATCH v2 04/12] worktree.c: mark current worktree","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-21T15:37:32Z","receivedAt":"2016-04-21T15:37:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Thu, Apr 21, 2016 at 10:23:09AM -0400, Eric Sunshine wrote:\n>\n>> > While we're at it, how about renaming it to pathcmp (and its friend\n>> > strncmp_icase to pathncmp)?\n>> \n>> Yes, that seems like a good idea. For anyone familiar with\n>> strcasecmp() or stricmp(), having \"icase\" in the name makes it seem as\n>> though it's unconditionally case-insensitive, so dropping it from the\n>> name would likely be beneficial.\n>\n> Seconded (thirded?). I have been caught by this confusion in the past,\n> too.\n\nI agree that strcmp_icase() gives a false impression that it always\nignores case differences, but a new name that does not at all hint\nthat it may do icase comparison as necessary will catch me by an\nopposite confusion in the future.\n\nI have not yet formed a firm opinion if pathcmp() conveys enough\nhint.\n"},{"id":"284039","messageId":"20160421154058.GA9525@sigill.intra.peff.net","threadId":"42034","inReplyTo":"xmqqk2jrt1kz.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v2 04/12] worktree.c: mark current worktree","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-04-21T15:40:59Z","receivedAt":"2016-04-21T15:40:59Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Apr 21, 2016 at 08:37:32AM -0700, Junio C Hamano wrote:\n\n> >> > While we're at it, how about renaming it to pathcmp (and its friend\n> >> > strncmp_icase to pathncmp)?\n> >> \n> >> Yes, that seems like a good idea. For anyone familiar with\n> >> strcasecmp() or stricmp(), having \"icase\" in the name makes it seem as\n> >> though it's unconditionally case-insensitive, so dropping it from the\n> >> name would likely be beneficial.\n> >\n> > Seconded (thirded?). I have been caught by this confusion in the past,\n> > too.\n> \n> I agree that strcmp_icase() gives a false impression that it always\n> ignores case differences, but a new name that does not at all hint\n> that it may do icase comparison as necessary will catch me by an\n> opposite confusion in the future.\n\nTo me, the benefit is that you don't have to care about ignore_case. You\nhave asked to compare two paths, and any system-appropriate magic should\nbe applied. That may be icase, or it may be weird unicode normalization.\n\nI think the key thing missing is that this is only about _filesystem_\npaths. You would not want to use it for tree-to-tree pathname\ncomparisons. So maybe \"fspath\" or something would be more descriptive.\n\n-Peff\n"},{"id":"284040","messageId":"xmqqfuuft1c1.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160421154058.GA9525@sigill.intra.peff.net","subject":"Re: [PATCH v2 04/12] worktree.c: mark current worktree","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-21T15:42:54Z","receivedAt":"2016-04-21T15:42:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> To me, the benefit is that you don't have to care about ignore_case. You\n> have asked to compare two paths, and any system-appropriate magic should\n> be applied. That may be icase, or it may be weird unicode normalization.\n>\n> I think the key thing missing is that this is only about _filesystem_\n> paths. You would not want to use it for tree-to-tree pathname\n> comparisons. So maybe \"fspath\" or something would be more descriptive.\n\nYup, I would be very happy with \"fs\" in the name.\n"},{"id":"284112","messageId":"1461330096-21783-1-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461158693-21289-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 00/13] nd/worktree-various-heads","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:23Z","receivedAt":"2016-04-22T13:01:23Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"v3 fixes all the things found in v2, deletes unused stuff and adds a\nnew patch 01/13 that renames str(n)cmp_icase to fspath(n)cmp.\n\nInterdiff\n\ndiff --git a/branch.c b/branch.c\nindex 8e323d3..a5a8dcb 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -338,12 +338,12 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree)\n {\n \tconst struct worktree *wt;\n \n-\twt = find_shared_symref(\"HEAD\", branch, ignore_current_worktree);\n-\tif (wt) {\n-\t\tskip_prefix(branch, \"refs/heads/\", &branch);\n-\t\tdie(_(\"'%s' is already checked out at '%s'\"),\n-\t\t    branch, wt->path);\n-\t}\n+\twt = find_shared_symref(\"HEAD\", branch);\n+\tif (!wt || (ignore_current_worktree && wt->is_current))\n+\t\treturn;\n+\tskip_prefix(branch, \"refs/heads/\", &branch);\n+\tdie(_(\"'%s' is already checked out at '%s'\"),\n+\t    branch, wt->path);\n }\n \n int replace_each_worktree_head_symref(const char *oldref, const char *newref)\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 3a2eceb..b488c3f 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -221,7 +221,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \n \t\tif (kinds == FILTER_REFS_BRANCHES) {\n \t\t\tconst struct worktree *wt =\n-\t\t\t\tfind_shared_symref(\"HEAD\", name, 0);\n+\t\t\t\tfind_shared_symref(\"HEAD\", name);\n \t\t\tif (wt) {\n \t\t\t\terror(_(\"Cannot delete branch '%s' \"\n \t\t\t\t\t\"checked out at '%s'\"),\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex f154a69..c65b59a 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -852,7 +852,7 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\tupdate_ref(msg.buf, \"NOTES_MERGE_PARTIAL\", result_sha1, NULL,\n \t\t\t   0, UPDATE_REFS_DIE_ON_ERR);\n \t\t/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */\n-\t\twt = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref(), 0);\n+\t\twt = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref());\n \t\tif (wt)\n \t\t\tdie(_(\"A notes merge into %s is already in-progress at %s\"),\n \t\t\t    default_notes_ref(), wt->path);\ndiff --git a/dir.c b/dir.c\nindex 996653b..f04bd3b 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -53,13 +53,12 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \tint check_only, const struct path_simplify *simplify);\n static int get_dtype(struct dirent *de, const char *path, int len);\n \n-/* helper string functions with support for the ignore_case flag */\n-int strcmp_icase(const char *a, const char *b)\n+int fspathcmp(const char *a, const char *b)\n {\n \treturn ignore_case ? strcasecmp(a, b) : strcmp(a, b);\n }\n \n-int strncmp_icase(const char *a, const char *b, size_t count)\n+int fspathncmp(const char *a, const char *b, size_t count)\n {\n \treturn ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);\n }\n@@ -802,12 +801,12 @@ int match_basename(const char *basename, int basenamelen,\n {\n \tif (prefix == patternlen) {\n \t\tif (patternlen == basenamelen &&\n-\t\t    !strncmp_icase(pattern, basename, basenamelen))\n+\t\t    !fspathncmp(pattern, basename, basenamelen))\n \t\t\treturn 1;\n \t} else if (flags & EXC_FLAG_ENDSWITH) {\n \t\t/* \"*literal\" matching against \"fooliteral\" */\n \t\tif (patternlen - 1 <= basenamelen &&\n-\t\t    !strncmp_icase(pattern + 1,\n+\t\t    !fspathncmp(pattern + 1,\n \t\t\t\t   basename + basenamelen - (patternlen - 1),\n \t\t\t\t   patternlen - 1))\n \t\t\treturn 1;\n@@ -844,7 +843,7 @@ int match_pathname(const char *pathname, int pathlen,\n \t */\n \tif (pathlen < baselen + 1 ||\n \t    (baselen && pathname[baselen] != '/') ||\n-\t    strncmp_icase(pathname, base, baselen))\n+\t    fspathncmp(pathname, base, baselen))\n \t\treturn 0;\n \n \tnamelen = baselen ? pathlen - baselen - 1 : pathlen;\n@@ -858,7 +857,7 @@ int match_pathname(const char *pathname, int pathlen,\n \t\tif (prefix > namelen)\n \t\t\treturn 0;\n \n-\t\tif (strncmp_icase(pattern, name, prefix))\n+\t\tif (fspathncmp(pattern, name, prefix))\n \t\t\treturn 0;\n \t\tpattern += prefix;\n \t\tpatternlen -= prefix;\ndiff --git a/dir.h b/dir.h\nindex 301b737..e34d555 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -270,8 +270,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);\n /* tries to remove the path with empty directories along it, ignores ENOENT */\n extern int remove_path(const char *path);\n \n-extern int strcmp_icase(const char *a, const char *b);\n-extern int strncmp_icase(const char *a, const char *b, size_t count);\n+extern int fspathcmp(const char *a, const char *b);\n+extern int fspathncmp(const char *a, const char *b, size_t count);\n extern int fnmatch_icase(const char *pattern, const char *string, int flags);\n \n /*\ndiff --git a/fast-import.c b/fast-import.c\nindex 9fc7093..339cd38 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1512,7 +1512,7 @@ static int tree_content_set(\n \tt = root->tree;\n \tfor (i = 0; i < t->entry_count; i++) {\n \t\te = t->entries[i];\n-\t\tif (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {\n+\t\tif (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {\n \t\t\tif (!*slash1) {\n \t\t\t\tif (!S_ISDIR(mode)\n \t\t\t\t\t\t&& e->versions[1].mode == mode\n@@ -1602,7 +1602,7 @@ static int tree_content_remove(\n \tt = root->tree;\n \tfor (i = 0; i < t->entry_count; i++) {\n \t\te = t->entries[i];\n-\t\tif (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {\n+\t\tif (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {\n \t\t\tif (*slash1 && !S_ISDIR(e->versions[1].mode))\n \t\t\t\t/*\n \t\t\t\t * If p names a file in some subdirectory, and a\n@@ -1669,7 +1669,7 @@ static int tree_content_get(\n \tt = root->tree;\n \tfor (i = 0; i < t->entry_count; i++) {\n \t\te = t->entries[i];\n-\t\tif (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {\n+\t\tif (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {\n \t\t\tif (!*slash1)\n \t\t\t\tgoto found_entry;\n \t\t\tif (!S_ISDIR(e->versions[1].mode))\ndiff --git a/path.c b/path.c\nindex c421d37..8fdd187 100644\n--- a/path.c\n+++ b/path.c\n@@ -466,16 +466,6 @@ const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)\n \treturn pathname->buf;\n }\n \n-char *worktree_git_pathdup(const struct worktree *wt, const char *fmt, ...)\n-{\n-\tstruct strbuf path = STRBUF_INIT;\n-\tva_list args;\n-\tva_start(args, fmt);\n-\tdo_git_path(wt, &path, fmt, args);\n-\tva_end(args);\n-\treturn strbuf_detach(&path, NULL);\n-}\n-\n static void do_submodule_path(struct strbuf *buf, const char *path,\n \t\t\t      const char *fmt, va_list args)\n {\ndiff --git a/sha1_file.c b/sha1_file.c\nindex d0f2aa0..ea6381b 100644\n--- a/sha1_file.c\n+++ b/sha1_file.c\n@@ -301,7 +301,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,\n \t\t\treturn -1;\n \t\t}\n \t}\n-\tif (!strcmp_icase(ent->base, normalized_objdir)) {\n+\tif (!fspathcmp(ent->base, normalized_objdir)) {\n \t\tfree(ent);\n \t\treturn -1;\n \t}\ndiff --git a/worktree.c b/worktree.c\nindex 8a3d394..4817d60 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -151,14 +151,32 @@ done:\n \treturn worktree;\n }\n \n+static void mark_current_worktree(struct worktree **worktrees)\n+{\n+\tstruct strbuf git_dir = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tint i;\n+\n+\tstrbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n+\tfor (i = 0; worktrees[i]; i++) {\n+\t\tstruct worktree *wt = worktrees[i];\n+\t\tstrbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n+\t\twt->is_current = !fspathcmp(git_dir.buf, path.buf);\n+\t\tstrbuf_reset(&path);\n+\t\tif (wt->is_current)\n+\t\t\tbreak;\n+\t}\n+\tstrbuf_release(&git_dir);\n+\tstrbuf_release(&path);\n+}\n+\n struct worktree **get_worktrees(void)\n {\n \tstruct worktree **list = NULL;\n-\tstruct strbuf git_dir = STRBUF_INIT;\n \tstruct strbuf path = STRBUF_INIT;\n \tDIR *dir;\n \tstruct dirent *d;\n-\tint i, counter = 0, alloc = 2;\n+\tint counter = 0, alloc = 2;\n \n \tlist = xmalloc(alloc * sizeof(struct worktree *));\n \n@@ -184,17 +202,7 @@ struct worktree **get_worktrees(void)\n \tALLOC_GROW(list, counter + 1, alloc);\n \tlist[counter] = NULL;\n \n-\tstrbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n-\tfor (i = 0; i < counter; i++) {\n-\t\tstruct worktree *wt = list[i];\n-\t\tstrbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n-\t\twt->is_current = !strcmp_icase(git_dir.buf, path.buf);\n-\t\tstrbuf_reset(&path);\n-\t\tif (wt->is_current)\n-\t\t\tbreak;\n-\t}\n-\tstrbuf_release(&git_dir);\n-\tstrbuf_release(&path);\n+\tmark_current_worktree(list);\n \treturn list;\n }\n \n@@ -241,9 +249,14 @@ int is_worktree_being_bisected(const struct worktree *wt,\n \treturn found_rebase;\n }\n \n+/*\n+ * note: this function should be able to detect shared symref even if\n+ * HEAD is temporarily detached (e.g. in the middle of rebase or\n+ * bisect). New commands that do similar things should update this\n+ * function as well.\n+ */\n const struct worktree *find_shared_symref(const char *symref,\n-\t\t\t\t\t  const char *target,\n-\t\t\t\t\t  int ignore_current_worktree)\n+\t\t\t\t\t  const char *target)\n {\n \tconst struct worktree *existing = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n@@ -258,9 +271,6 @@ const struct worktree *find_shared_symref(const char *symref,\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstruct worktree *wt = worktrees[i];\n \n-\t\tif (ignore_current_worktree && wt->is_current)\n-\t\t\tcontinue;\n-\n \t\tif (wt->is_detached && !strcmp(symref, \"HEAD\")) {\n \t\t\tif (is_worktree_being_rebased(wt, target)) {\n \t\t\t\texisting = wt;\ndiff --git a/worktree.h b/worktree.h\nindex d4a3534..1394909 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -36,26 +36,21 @@ extern void free_worktrees(struct worktree **);\n \n /*\n  * Check if a per-worktree symref points to a ref in the main worktree\n- * or any linked worktree, and return the path to the exising worktree\n- * if it is. Returns NULL if there is no existing ref. The result\n- * may be destroyed by the next call.\n+ * or any linked worktree, and return the worktree that holds the ref,\n+ * or NULL otherwise. The result may be destroyed by the next call.\n  */\n extern const struct worktree *find_shared_symref(const char *symref,\n-\t\t\t\t\t\t const char *target,\n-\t\t\t\t\t\t int ignore_current_worktree);\n+\t\t\t\t\t\t const char *target);\n \n int is_worktree_being_rebased(const struct worktree *wt, const char *target);\n int is_worktree_being_bisected(const struct worktree *wt, const char *target);\n \n /*\n- * Similar to git_path() and git_pathdup() but can produce paths for a\n- * specified worktree instead of current one\n+ * Similar to git_path() but can produce paths for a specified\n+ * worktree instead of current one\n  */\n extern const char *worktree_git_path(const struct worktree *wt,\n \t\t\t\t     const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n-extern char *worktree_git_pathdup(const struct worktree *wt,\n-\t\t\t\t  const char *fmt, ...)\n-\t__attribute__((format (printf, 2, 3)));\n \n #endif\ndiff --git a/wt-status.c b/wt-status.c\nindex 971e071..0032ef5 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1410,7 +1410,7 @@ void wt_status_get_state(struct wt_status_state *state,\n \tif (!stat(git_path_merge_head(), &st)) {\n \t\tstate->merge_in_progress = 1;\n \t} else if (wt_status_check_rebase(NULL, state)) {\n-\t\t; /* all set */\n+\t\t;\t\t/* all set */\n \t} else if (!stat(git_path_cherry_pick_head(), &st) &&\n \t\t\t!get_sha1(\"CHERRY_PICK_HEAD\", sha1)) {\n \t\tstate->cherry_pick_in_progress = 1;\n\n  [01/13] dir.c: rename str(n)cmp_icase to fspath(n)cmp\n  [02/13] path.c: add git_common_path() and strbuf_git_common_path()\n  [03/13] worktree.c: store \"id\" instead of \"git_dir\"\n  [04/13] worktree.c: make find_shared_symref() return struct worktree *\n  [05/13] worktree.c: mark current worktree\n  [06/13] path.c: refactor and add worktree_git_path()\n  [07/13] wt-status.c: split rebase detection out of wt_status_get_state()\n  [08/13] wt-status.c: make wt_status_check_rebase() work on any worktree\n  [09/13] worktree.c: avoid referencing to worktrees[i] multiple times\n  [10/13] worktree.c: check whether branch is rebased in another worktree\n  [11/13] wt-status.c: split bisect detection out of wt_status_get_state()\n  [12/13] worktree.c: check whether branch is bisected in another worktree\n  [13/13] branch: do not rename a branch under bisect or rebase\n\nTotal 17 files changed, 344 insertions(+), 82 deletions(-)\n--\nDuy\n"},{"id":"284113","messageId":"1461330096-21783-2-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 01/13] dir.c: rename str(n)cmp_icase to fspath(n)cmp","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:24Z","receivedAt":"2016-04-22T13:01:24Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"These functions compare two paths that are taken from file system.\nDepending on the running file system, paths may need to be compared\ncase-sensitively or not, and maybe even something else in future. The\ncurrent names do not convey that well.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n dir.c         | 13 ++++++-------\n dir.h         |  4 ++--\n fast-import.c |  6 +++---\n sha1_file.c   |  2 +-\n 4 files changed, 12 insertions(+), 13 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 996653b..f04bd3b 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -53,13 +53,12 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n \tint check_only, const struct path_simplify *simplify);\n static int get_dtype(struct dirent *de, const char *path, int len);\n \n-/* helper string functions with support for the ignore_case flag */\n-int strcmp_icase(const char *a, const char *b)\n+int fspathcmp(const char *a, const char *b)\n {\n \treturn ignore_case ? strcasecmp(a, b) : strcmp(a, b);\n }\n \n-int strncmp_icase(const char *a, const char *b, size_t count)\n+int fspathncmp(const char *a, const char *b, size_t count)\n {\n \treturn ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);\n }\n@@ -802,12 +801,12 @@ int match_basename(const char *basename, int basenamelen,\n {\n \tif (prefix == patternlen) {\n \t\tif (patternlen == basenamelen &&\n-\t\t    !strncmp_icase(pattern, basename, basenamelen))\n+\t\t    !fspathncmp(pattern, basename, basenamelen))\n \t\t\treturn 1;\n \t} else if (flags & EXC_FLAG_ENDSWITH) {\n \t\t/* \"*literal\" matching against \"fooliteral\" */\n \t\tif (patternlen - 1 <= basenamelen &&\n-\t\t    !strncmp_icase(pattern + 1,\n+\t\t    !fspathncmp(pattern + 1,\n \t\t\t\t   basename + basenamelen - (patternlen - 1),\n \t\t\t\t   patternlen - 1))\n \t\t\treturn 1;\n@@ -844,7 +843,7 @@ int match_pathname(const char *pathname, int pathlen,\n \t */\n \tif (pathlen < baselen + 1 ||\n \t    (baselen && pathname[baselen] != '/') ||\n-\t    strncmp_icase(pathname, base, baselen))\n+\t    fspathncmp(pathname, base, baselen))\n \t\treturn 0;\n \n \tnamelen = baselen ? pathlen - baselen - 1 : pathlen;\n@@ -858,7 +857,7 @@ int match_pathname(const char *pathname, int pathlen,\n \t\tif (prefix > namelen)\n \t\t\treturn 0;\n \n-\t\tif (strncmp_icase(pattern, name, prefix))\n+\t\tif (fspathncmp(pattern, name, prefix))\n \t\t\treturn 0;\n \t\tpattern += prefix;\n \t\tpatternlen -= prefix;\ndiff --git a/dir.h b/dir.h\nindex 301b737..e34d555 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -270,8 +270,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);\n /* tries to remove the path with empty directories along it, ignores ENOENT */\n extern int remove_path(const char *path);\n \n-extern int strcmp_icase(const char *a, const char *b);\n-extern int strncmp_icase(const char *a, const char *b, size_t count);\n+extern int fspathcmp(const char *a, const char *b);\n+extern int fspathncmp(const char *a, const char *b, size_t count);\n extern int fnmatch_icase(const char *pattern, const char *string, int flags);\n \n /*\ndiff --git a/fast-import.c b/fast-import.c\nindex 9fc7093..339cd38 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1512,7 +1512,7 @@ static int tree_content_set(\n \tt = root->tree;\n \tfor (i = 0; i < t->entry_count; i++) {\n \t\te = t->entries[i];\n-\t\tif (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {\n+\t\tif (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {\n \t\t\tif (!*slash1) {\n \t\t\t\tif (!S_ISDIR(mode)\n \t\t\t\t\t\t&& e->versions[1].mode == mode\n@@ -1602,7 +1602,7 @@ static int tree_content_remove(\n \tt = root->tree;\n \tfor (i = 0; i < t->entry_count; i++) {\n \t\te = t->entries[i];\n-\t\tif (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {\n+\t\tif (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {\n \t\t\tif (*slash1 && !S_ISDIR(e->versions[1].mode))\n \t\t\t\t/*\n \t\t\t\t * If p names a file in some subdirectory, and a\n@@ -1669,7 +1669,7 @@ static int tree_content_get(\n \tt = root->tree;\n \tfor (i = 0; i < t->entry_count; i++) {\n \t\te = t->entries[i];\n-\t\tif (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {\n+\t\tif (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {\n \t\t\tif (!*slash1)\n \t\t\t\tgoto found_entry;\n \t\t\tif (!S_ISDIR(e->versions[1].mode))\ndiff --git a/sha1_file.c b/sha1_file.c\nindex d0f2aa0..ea6381b 100644\n--- a/sha1_file.c\n+++ b/sha1_file.c\n@@ -301,7 +301,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,\n \t\t\treturn -1;\n \t\t}\n \t}\n-\tif (!strcmp_icase(ent->base, normalized_objdir)) {\n+\tif (!fspathcmp(ent->base, normalized_objdir)) {\n \t\tfree(ent);\n \t\treturn -1;\n \t}\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284115","messageId":"1461330096-21783-3-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 02/13] path.c: add git_common_path() and strbuf_git_common_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:25Z","receivedAt":"2016-04-22T13:01:25Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"These are mostly convenient functions to reduce code duplication. Most\nof the time, we should be able to get by with git_path() which handles\n$GIT_COMMON_DIR internally. However there are a few cases where we need\nto construct paths manually, for example some paths from a specific\nworktree. These functions will enable that.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |  3 +++\n path.c  | 29 +++++++++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 2711048..c04a17f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -799,11 +799,14 @@ extern void check_repository_format(void);\n  */\n extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n+extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n \n extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \t__attribute__((format (printf, 3, 4)));\n extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n+extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,\ndiff --git a/path.c b/path.c\nindex bbaea5a..2ebb23d 100644\n--- a/path.c\n+++ b/path.c\n@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,\n \tva_end(args);\n }\n \n+static void do_git_common_path(struct strbuf *buf,\n+\t\t\t       const char *fmt,\n+\t\t\t       va_list args)\n+{\n+\tstrbuf_addstr(buf, get_git_common_dir());\n+\tif (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_vaddf(buf, fmt, args);\n+\tstrbuf_cleanup_path(buf);\n+}\n+\n+const char *git_common_path(const char *fmt, ...)\n+{\n+\tstruct strbuf *pathname = get_pathname();\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_common_path(pathname, fmt, args);\n+\tva_end(args);\n+\treturn pathname->buf;\n+}\n+\n+void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n+{\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_common_path(sb, fmt, args);\n+\tva_end(args);\n+}\n+\n int validate_headref(const char *path)\n {\n \tstruct stat st;\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284114","messageId":"1461330096-21783-4-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 03/13] worktree.c: store \"id\" instead of \"git_dir\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:26Z","receivedAt":"2016-04-22T13:01:26Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We can reconstruct git_dir from id quite easily. It's a bit hackier to\ndo the reverse.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n branch.c   |  3 ++-\n worktree.c | 31 ++++++++++++++++++-------------\n worktree.h |  8 +++++++-\n 3 files changed, 27 insertions(+), 15 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 4162443..0674a99 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -357,7 +357,8 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref)\n \t\tif (strcmp(oldref, worktrees[i]->head_ref))\n \t\t\tcontinue;\n \n-\t\tif (set_worktree_head_symref(worktrees[i]->git_dir, newref)) {\n+\t\tif (set_worktree_head_symref(get_worktree_git_dir(worktrees[i]),\n+\t\t\t\t\t     newref)) {\n \t\t\tret = -1;\n \t\t\terror(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t      worktrees[i]->path);\ndiff --git a/worktree.c b/worktree.c\nindex 6181a66..5ae54f0 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -9,7 +9,7 @@ void free_worktrees(struct worktree **worktrees)\n \n \tfor (i = 0; worktrees[i]; i++) {\n \t\tfree(worktrees[i]->path);\n-\t\tfree(worktrees[i]->git_dir);\n+\t\tfree(worktrees[i]->id);\n \t\tfree(worktrees[i]->head_ref);\n \t\tfree(worktrees[i]);\n \t}\n@@ -74,13 +74,11 @@ static struct worktree *get_main_worktree(void)\n \tstruct worktree *worktree = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf worktree_path = STRBUF_INIT;\n-\tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf head_ref = STRBUF_INIT;\n \tint is_bare = 0;\n \tint is_detached = 0;\n \n-\tstrbuf_addf(&gitdir, \"%s\", absolute_path(get_git_common_dir()));\n-\tstrbuf_addbuf(&worktree_path, &gitdir);\n+\tstrbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));\n \tis_bare = !strbuf_strip_suffix(&worktree_path, \"/.git\");\n \tif (is_bare)\n \t\tstrbuf_strip_suffix(&worktree_path, \"/.\");\n@@ -92,7 +90,7 @@ static struct worktree *get_main_worktree(void)\n \n \tworktree = xmalloc(sizeof(struct worktree));\n \tworktree->path = strbuf_detach(&worktree_path, NULL);\n-\tworktree->git_dir = strbuf_detach(&gitdir, NULL);\n+\tworktree->id = NULL;\n \tworktree->is_bare = is_bare;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n@@ -100,7 +98,6 @@ static struct worktree *get_main_worktree(void)\n \n done:\n \tstrbuf_release(&path);\n-\tstrbuf_release(&gitdir);\n \tstrbuf_release(&worktree_path);\n \tstrbuf_release(&head_ref);\n \treturn worktree;\n@@ -111,16 +108,13 @@ static struct worktree *get_linked_worktree(const char *id)\n \tstruct worktree *worktree = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf worktree_path = STRBUF_INIT;\n-\tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf head_ref = STRBUF_INIT;\n \tint is_detached = 0;\n \n \tif (!id)\n \t\tdie(\"Missing linked worktree name\");\n \n-\tstrbuf_addf(&gitdir, \"%s/worktrees/%s\",\n-\t\t\tabsolute_path(get_git_common_dir()), id);\n-\tstrbuf_addf(&path, \"%s/gitdir\", gitdir.buf);\n+\tstrbuf_git_common_path(&path, \"worktrees/%s/gitdir\", id);\n \tif (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)\n \t\t/* invalid gitdir file */\n \t\tgoto done;\n@@ -140,7 +134,7 @@ static struct worktree *get_linked_worktree(const char *id)\n \n \tworktree = xmalloc(sizeof(struct worktree));\n \tworktree->path = strbuf_detach(&worktree_path, NULL);\n-\tworktree->git_dir = strbuf_detach(&gitdir, NULL);\n+\tworktree->id = xstrdup(id);\n \tworktree->is_bare = 0;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n@@ -148,7 +142,6 @@ static struct worktree *get_linked_worktree(const char *id)\n \n done:\n \tstrbuf_release(&path);\n-\tstrbuf_release(&gitdir);\n \tstrbuf_release(&worktree_path);\n \tstrbuf_release(&head_ref);\n \treturn worktree;\n@@ -188,6 +181,16 @@ struct worktree **get_worktrees(void)\n \treturn list;\n }\n \n+const char *get_worktree_git_dir(const struct worktree *wt)\n+{\n+\tif (!wt)\n+\t\treturn get_git_dir();\n+\telse if (!wt->id)\n+\t\treturn get_git_common_dir();\n+\telse\n+\t\treturn git_common_path(\"worktrees/%s\", wt->id);\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\n@@ -199,7 +202,9 @@ char *find_shared_symref(const char *symref, const char *target)\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n-\t\tstrbuf_addf(&path, \"%s/%s\", worktrees[i]->git_dir, symref);\n+\t\tstrbuf_addf(&path, \"%s/%s\",\n+\t\t\t    get_worktree_git_dir(worktrees[i]),\n+\t\t\t    symref);\n \n \t\tif (parse_ref(path.buf, &sb, NULL)) {\n \t\t\tcontinue;\ndiff --git a/worktree.h b/worktree.h\nindex b4b3dda..3198c8d 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -3,7 +3,7 @@\n \n struct worktree {\n \tchar *path;\n-\tchar *git_dir;\n+\tchar *id;\n \tchar *head_ref;\n \tunsigned char head_sha1[20];\n \tint is_detached;\n@@ -23,6 +23,12 @@ struct worktree {\n extern struct worktree **get_worktrees(void);\n \n /*\n+ * Return git dir of the worktree. Note that the path may be relative.\n+ * If wt is NULL, git dir of current worktree is returned.\n+ */\n+extern const char *get_worktree_git_dir(const struct worktree *wt);\n+\n+/*\n  * Free up the memory for worktree(s)\n  */\n extern void free_worktrees(struct worktree **);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284116","messageId":"1461330096-21783-5-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 04/13] worktree.c: make find_shared_symref() return struct worktree *","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:27Z","receivedAt":"2016-04-22T13:01:27Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This gives the caller more information and they can answer things like,\n\"is it the main worktree\" or \"is it the current worktree\". The latter\nquestion is needed for the \"checkout a rebase branch\" case later.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n branch.c         | 13 +++++++------\n builtin/branch.c |  8 ++++----\n builtin/notes.c  |  8 ++++----\n worktree.c       | 14 +++++++++-----\n worktree.h       |  8 ++++----\n 5 files changed, 28 insertions(+), 23 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 0674a99..1f1fbf5 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -336,13 +336,14 @@ void remove_branch_state(void)\n \n void die_if_checked_out(const char *branch)\n {\n-\tchar *existing;\n+\tconst struct worktree *wt;\n \n-\texisting = find_shared_symref(\"HEAD\", branch);\n-\tif (existing) {\n-\t\tskip_prefix(branch, \"refs/heads/\", &branch);\n-\t\tdie(_(\"'%s' is already checked out at '%s'\"), branch, existing);\n-\t}\n+\twt = find_shared_symref(\"HEAD\", branch);\n+\tif (!wt)\n+\t\treturn;\n+\tskip_prefix(branch, \"refs/heads/\", &branch);\n+\tdie(_(\"'%s' is already checked out at '%s'\"),\n+\t    branch, wt->path);\n }\n \n int replace_each_worktree_head_symref(const char *oldref, const char *newref)\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 0adba62..bcde87d 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -220,12 +220,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tname = mkpathdup(fmt, bname.buf);\n \n \t\tif (kinds == FILTER_REFS_BRANCHES) {\n-\t\t\tchar *worktree = find_shared_symref(\"HEAD\", name);\n-\t\t\tif (worktree) {\n+\t\t\tconst struct worktree *wt =\n+\t\t\t\tfind_shared_symref(\"HEAD\", name);\n+\t\t\tif (wt) {\n \t\t\t\terror(_(\"Cannot delete branch '%s' \"\n \t\t\t\t\t\"checked out at '%s'\"),\n-\t\t\t\t      bname.buf, worktree);\n-\t\t\t\tfree(worktree);\n+\t\t\t\t      bname.buf, wt->path);\n \t\t\t\tret = 1;\n \t\t\t\tcontinue;\n \t\t\t}\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 6fd058d..c65b59a 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -847,15 +847,15 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\tupdate_ref(msg.buf, default_notes_ref(), result_sha1, NULL,\n \t\t\t   0, UPDATE_REFS_DIE_ON_ERR);\n \telse { /* Merge has unresolved conflicts */\n-\t\tchar *existing;\n+\t\tconst struct worktree *wt;\n \t\t/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */\n \t\tupdate_ref(msg.buf, \"NOTES_MERGE_PARTIAL\", result_sha1, NULL,\n \t\t\t   0, UPDATE_REFS_DIE_ON_ERR);\n \t\t/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */\n-\t\texisting = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref());\n-\t\tif (existing)\n+\t\twt = find_shared_symref(\"NOTES_MERGE_REF\", default_notes_ref());\n+\t\tif (wt)\n \t\t\tdie(_(\"A notes merge into %s is already in-progress at %s\"),\n-\t\t\t    default_notes_ref(), existing);\n+\t\t\t    default_notes_ref(), wt->path);\n \t\tif (create_symref(\"NOTES_MERGE_REF\", default_notes_ref(), NULL))\n \t\t\tdie(\"Failed to store link to current notes ref (%s)\",\n \t\t\t    default_notes_ref());\ndiff --git a/worktree.c b/worktree.c\nindex 5ae54f0..360ba41 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -191,14 +191,19 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n-char *find_shared_symref(const char *symref, const char *target)\n+const struct worktree *find_shared_symref(const char *symref,\n+\t\t\t\t\t  const char *target)\n {\n-\tchar *existing = NULL;\n+\tconst struct worktree *existing = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf sb = STRBUF_INIT;\n-\tstruct worktree **worktrees = get_worktrees();\n+\tstatic struct worktree **worktrees;\n \tint i = 0;\n \n+\tif (worktrees)\n+\t\tfree_worktrees(worktrees);\n+\tworktrees = get_worktrees();\n+\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n@@ -211,14 +216,13 @@ char *find_shared_symref(const char *symref, const char *target)\n \t\t}\n \n \t\tif (!strcmp(sb.buf, target)) {\n-\t\t\texisting = xstrdup(worktrees[i]->path);\n+\t\t\texisting = worktrees[i];\n \t\t\tbreak;\n \t\t}\n \t}\n \n \tstrbuf_release(&path);\n \tstrbuf_release(&sb);\n-\tfree_worktrees(worktrees);\n \n \treturn existing;\n }\ndiff --git a/worktree.h b/worktree.h\nindex 3198c8d..ca50e73 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -35,10 +35,10 @@ extern void free_worktrees(struct worktree **);\n \n /*\n  * Check if a per-worktree symref points to a ref in the main worktree\n- * or any linked worktree, and return the path to the exising worktree\n- * if it is.  Returns NULL if there is no existing ref.  The caller is\n- * responsible for freeing the returned path.\n+ * or any linked worktree, and return the worktree that holds the ref,\n+ * or NULL otherwise. The result may be destroyed by the next call.\n  */\n-extern char *find_shared_symref(const char *symref, const char *target);\n+extern const struct worktree *find_shared_symref(const char *symref,\n+\t\t\t\t\t\t const char *target);\n \n #endif\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284117","messageId":"1461330096-21783-6-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 05/13] worktree.c: mark current worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:28Z","receivedAt":"2016-04-22T13:01:28Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 24 ++++++++++++++++++++++++\n worktree.h |  1 +\n 2 files changed, 25 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 360ba41..dc092a7 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -2,6 +2,7 @@\n #include \"refs.h\"\n #include \"strbuf.h\"\n #include \"worktree.h\"\n+#include \"dir.h\"\n \n void free_worktrees(struct worktree **worktrees)\n {\n@@ -94,6 +95,7 @@ static struct worktree *get_main_worktree(void)\n \tworktree->is_bare = is_bare;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n+\tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n \n done:\n@@ -138,6 +140,7 @@ static struct worktree *get_linked_worktree(const char *id)\n \tworktree->is_bare = 0;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n+\tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n \n done:\n@@ -147,6 +150,25 @@ done:\n \treturn worktree;\n }\n \n+static void mark_current_worktree(struct worktree **worktrees)\n+{\n+\tstruct strbuf git_dir = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tint i;\n+\n+\tstrbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n+\tfor (i = 0; worktrees[i]; i++) {\n+\t\tstruct worktree *wt = worktrees[i];\n+\t\tstrbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n+\t\twt->is_current = !fspathcmp(git_dir.buf, path.buf);\n+\t\tstrbuf_reset(&path);\n+\t\tif (wt->is_current)\n+\t\t\tbreak;\n+\t}\n+\tstrbuf_release(&git_dir);\n+\tstrbuf_release(&path);\n+}\n+\n struct worktree **get_worktrees(void)\n {\n \tstruct worktree **list = NULL;\n@@ -178,6 +200,8 @@ struct worktree **get_worktrees(void)\n \t}\n \tALLOC_GROW(list, counter + 1, alloc);\n \tlist[counter] = NULL;\n+\n+\tmark_current_worktree(list);\n \treturn list;\n }\n \ndiff --git a/worktree.h b/worktree.h\nindex ca50e73..ccdf69a 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -8,6 +8,7 @@ struct worktree {\n \tunsigned char head_sha1[20];\n \tint is_detached;\n \tint is_bare;\n+\tint is_current;\n };\n \n /* Functions for acting on the information about worktrees. */\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284118","messageId":"1461330096-21783-7-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 06/13] path.c: refactor and add worktree_git_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:29Z","receivedAt":"2016-04-22T13:01:29Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"do_git_path(), which is the common code for all git_path* functions, is\nmodified to take a worktree struct and can produce paths for any\nworktree.\n\nworktree_git_path() is the first function that makes use of this. It can\nbe used to write code that can examine any worktree. For example,\nwt_status_get_state() will be converted using this to take\nam/rebase/... state of any worktree.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n path.c     | 24 ++++++++++++++++++------\n worktree.h |  8 ++++++++\n 2 files changed, 26 insertions(+), 6 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 2ebb23d..8fdd187 100644\n--- a/path.c\n+++ b/path.c\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"dir.h\"\n+#include \"worktree.h\"\n \n static int get_st_mode_bits(const char *path, int *mode)\n {\n@@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)\n \t\tupdate_common_dir(buf, git_dir_len, NULL);\n }\n \n-static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)\n+static void do_git_path(const struct worktree *wt, struct strbuf *buf,\n+\t\t\tconst char *fmt, va_list args)\n {\n \tint gitdir_len;\n-\tstrbuf_addstr(buf, get_git_dir());\n+\tstrbuf_addstr(buf, get_worktree_git_dir(wt));\n \tif (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n \t\tstrbuf_addch(buf, '/');\n \tgitdir_len = buf->len;\n@@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)\n \tva_list args;\n \tstrbuf_reset(buf);\n \tva_start(args, fmt);\n-\tdo_git_path(buf, fmt, args);\n+\tdo_git_path(NULL, buf, fmt, args);\n \tva_end(args);\n \treturn buf->buf;\n }\n@@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n {\n \tva_list args;\n \tva_start(args, fmt);\n-\tdo_git_path(sb, fmt, args);\n+\tdo_git_path(NULL, sb, fmt, args);\n \tva_end(args);\n }\n \n@@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)\n \tstruct strbuf *pathname = get_pathname();\n \tva_list args;\n \tva_start(args, fmt);\n-\tdo_git_path(pathname, fmt, args);\n+\tdo_git_path(NULL, pathname, fmt, args);\n \tva_end(args);\n \treturn pathname->buf;\n }\n@@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)\n \tstruct strbuf path = STRBUF_INIT;\n \tva_list args;\n \tva_start(args, fmt);\n-\tdo_git_path(&path, fmt, args);\n+\tdo_git_path(NULL, &path, fmt, args);\n \tva_end(args);\n \treturn strbuf_detach(&path, NULL);\n }\n@@ -454,6 +456,16 @@ const char *mkpath(const char *fmt, ...)\n \treturn cleanup_path(pathname->buf);\n }\n \n+const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)\n+{\n+\tstruct strbuf *pathname = get_pathname();\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_path(wt, pathname, fmt, args);\n+\tva_end(args);\n+\treturn pathname->buf;\n+}\n+\n static void do_submodule_path(struct strbuf *buf, const char *path,\n \t\t\t      const char *fmt, va_list args)\n {\ndiff --git a/worktree.h b/worktree.h\nindex ccdf69a..0da8c1f 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -42,4 +42,12 @@ extern void free_worktrees(struct worktree **);\n extern const struct worktree *find_shared_symref(const char *symref,\n \t\t\t\t\t\t const char *target);\n \n+/*\n+ * Similar to git_path() but can produce paths for a specified\n+ * worktree instead of current one\n+ */\n+extern const char *worktree_git_path(const struct worktree *wt,\n+\t\t\t\t     const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n+\n #endif\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284119","messageId":"1461330096-21783-8-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 07/13] wt-status.c: split rebase detection out of wt_status_get_state()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:30Z","receivedAt":"2016-04-22T13:01:30Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"worktree.c:find_shared_symref() later needs to know if a branch is being\nrebased, and only rebase, no cherry-pick, do detached branch... Split\nthis code so it can be used independently from other in-progress tests.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n wt-status.c | 23 +++++++++++++++++------\n wt-status.h |  1 +\n 2 files changed, 18 insertions(+), 6 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 1ea2ebe..ec9240d 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1360,15 +1360,11 @@ static void wt_status_get_detached_from(struct wt_status_state *state)\n \tstrbuf_release(&cb.buf);\n }\n \n-void wt_status_get_state(struct wt_status_state *state,\n-\t\t\t int get_detached_from)\n+int wt_status_check_rebase(struct wt_status_state *state)\n {\n \tstruct stat st;\n-\tunsigned char sha1[20];\n \n-\tif (!stat(git_path_merge_head(), &st)) {\n-\t\tstate->merge_in_progress = 1;\n-\t} else if (!stat(git_path(\"rebase-apply\"), &st)) {\n+\tif (!stat(git_path(\"rebase-apply\"), &st)) {\n \t\tif (!stat(git_path(\"rebase-apply/applying\"), &st)) {\n \t\t\tstate->am_in_progress = 1;\n \t\t\tif (!stat(git_path(\"rebase-apply/patch\"), &st) && !st.st_size)\n@@ -1385,6 +1381,21 @@ void wt_status_get_state(struct wt_status_state *state,\n \t\t\tstate->rebase_in_progress = 1;\n \t\tstate->branch = read_and_strip_branch(\"rebase-merge/head-name\");\n \t\tstate->onto = read_and_strip_branch(\"rebase-merge/onto\");\n+\t} else\n+\t\treturn 0;\n+\treturn 1;\n+}\n+\n+void wt_status_get_state(struct wt_status_state *state,\n+\t\t\t int get_detached_from)\n+{\n+\tstruct stat st;\n+\tunsigned char sha1[20];\n+\n+\tif (!stat(git_path_merge_head(), &st)) {\n+\t\tstate->merge_in_progress = 1;\n+\t} else if (wt_status_check_rebase(state)) {\n+\t\t;\t\t/* all set */\n \t} else if (!stat(git_path_cherry_pick_head(), &st) &&\n \t\t\t!get_sha1(\"CHERRY_PICK_HEAD\", sha1)) {\n \t\tstate->cherry_pick_in_progress = 1;\ndiff --git a/wt-status.h b/wt-status.h\nindex c9b3b74..b398353 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -100,6 +100,7 @@ void wt_status_prepare(struct wt_status *s);\n void wt_status_print(struct wt_status *s);\n void wt_status_collect(struct wt_status *s);\n void wt_status_get_state(struct wt_status_state *state, int get_detached_from);\n+int wt_status_check_rebase(struct wt_status_state *state);\n \n void wt_shortstatus_print(struct wt_status *s);\n void wt_porcelain_print(struct wt_status *s);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284120","messageId":"1461330096-21783-9-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 08/13] wt-status.c: make wt_status_check_rebase() work on any worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:31Z","receivedAt":"2016-04-22T13:01:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is a preparation step for find_shared_symref() to detect if any\nworktree is being rebased.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n wt-status.c | 33 ++++++++++++++++++++-------------\n wt-status.h |  5 ++++-\n 2 files changed, 24 insertions(+), 14 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex ec9240d..ce5080c 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -15,6 +15,7 @@\n #include \"column.h\"\n #include \"strbuf.h\"\n #include \"utf8.h\"\n+#include \"worktree.h\"\n \n static const char cut_line[] =\n \"------------------------ >8 ------------------------\\n\";\n@@ -1262,13 +1263,13 @@ static void show_bisect_in_progress(struct wt_status *s,\n /*\n  * Extract branch information from rebase/bisect\n  */\n-static char *read_and_strip_branch(const char *path)\n+static char *get_branch(const struct worktree *wt, const char *path)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \tunsigned char sha1[20];\n \tconst char *branch_name;\n \n-\tif (strbuf_read_file(&sb, git_path(\"%s\", path), 0) <= 0)\n+\tif (strbuf_read_file(&sb, worktree_git_path(wt, \"%s\", path), 0) <= 0)\n \t\tgoto got_nothing;\n \n \twhile (sb.len && sb.buf[sb.len - 1] == '\\n')\n@@ -1295,6 +1296,11 @@ got_nothing:\n \treturn NULL;\n }\n \n+static char *read_and_strip_branch(const char *path)\n+{\n+\treturn get_branch(NULL, path);\n+}\n+\n struct grab_1st_switch_cbdata {\n \tstruct strbuf buf;\n \tunsigned char nsha1[20];\n@@ -1360,27 +1366,28 @@ static void wt_status_get_detached_from(struct wt_status_state *state)\n \tstrbuf_release(&cb.buf);\n }\n \n-int wt_status_check_rebase(struct wt_status_state *state)\n+int wt_status_check_rebase(const struct worktree *wt,\n+\t\t\t   struct wt_status_state *state)\n {\n \tstruct stat st;\n \n-\tif (!stat(git_path(\"rebase-apply\"), &st)) {\n-\t\tif (!stat(git_path(\"rebase-apply/applying\"), &st)) {\n+\tif (!stat(worktree_git_path(wt, \"rebase-apply\"), &st)) {\n+\t\tif (!stat(worktree_git_path(wt, \"rebase-apply/applying\"), &st)) {\n \t\t\tstate->am_in_progress = 1;\n-\t\t\tif (!stat(git_path(\"rebase-apply/patch\"), &st) && !st.st_size)\n+\t\t\tif (!stat(worktree_git_path(wt, \"rebase-apply/patch\"), &st) && !st.st_size)\n \t\t\t\tstate->am_empty_patch = 1;\n \t\t} else {\n \t\t\tstate->rebase_in_progress = 1;\n-\t\t\tstate->branch = read_and_strip_branch(\"rebase-apply/head-name\");\n-\t\t\tstate->onto = read_and_strip_branch(\"rebase-apply/onto\");\n+\t\t\tstate->branch = get_branch(wt, \"rebase-apply/head-name\");\n+\t\t\tstate->onto = get_branch(wt, \"rebase-apply/onto\");\n \t\t}\n-\t} else if (!stat(git_path(\"rebase-merge\"), &st)) {\n-\t\tif (!stat(git_path(\"rebase-merge/interactive\"), &st))\n+\t} else if (!stat(worktree_git_path(wt, \"rebase-merge\"), &st)) {\n+\t\tif (!stat(worktree_git_path(wt, \"rebase-merge/interactive\"), &st))\n \t\t\tstate->rebase_interactive_in_progress = 1;\n \t\telse\n \t\t\tstate->rebase_in_progress = 1;\n-\t\tstate->branch = read_and_strip_branch(\"rebase-merge/head-name\");\n-\t\tstate->onto = read_and_strip_branch(\"rebase-merge/onto\");\n+\t\tstate->branch = get_branch(wt, \"rebase-merge/head-name\");\n+\t\tstate->onto = get_branch(wt, \"rebase-merge/onto\");\n \t} else\n \t\treturn 0;\n \treturn 1;\n@@ -1394,7 +1401,7 @@ void wt_status_get_state(struct wt_status_state *state,\n \n \tif (!stat(git_path_merge_head(), &st)) {\n \t\tstate->merge_in_progress = 1;\n-\t} else if (wt_status_check_rebase(state)) {\n+\t} else if (wt_status_check_rebase(NULL, state)) {\n \t\t;\t\t/* all set */\n \t} else if (!stat(git_path_cherry_pick_head(), &st) &&\n \t\t\t!get_sha1(\"CHERRY_PICK_HEAD\", sha1)) {\ndiff --git a/wt-status.h b/wt-status.h\nindex b398353..c4ddcad 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -6,6 +6,8 @@\n #include \"color.h\"\n #include \"pathspec.h\"\n \n+struct worktree;\n+\n enum color_wt_status {\n \tWT_STATUS_HEADER = 0,\n \tWT_STATUS_UPDATED,\n@@ -100,7 +102,8 @@ void wt_status_prepare(struct wt_status *s);\n void wt_status_print(struct wt_status *s);\n void wt_status_collect(struct wt_status *s);\n void wt_status_get_state(struct wt_status_state *state, int get_detached_from);\n-int wt_status_check_rebase(struct wt_status_state *state);\n+int wt_status_check_rebase(const struct worktree *wt,\n+\t\t\t   struct wt_status_state *state);\n \n void wt_shortstatus_print(struct wt_status *s);\n void wt_porcelain_print(struct wt_status *s);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284121","messageId":"1461330096-21783-10-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 09/13] worktree.c: avoid referencing to worktrees[i] multiple times","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:32Z","receivedAt":"2016-04-22T13:01:32Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex dc092a7..927905b 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -229,10 +229,12 @@ const struct worktree *find_shared_symref(const char *symref,\n \tworktrees = get_worktrees();\n \n \tfor (i = 0; worktrees[i]; i++) {\n+\t\tstruct worktree *wt = worktrees[i];\n+\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n \t\tstrbuf_addf(&path, \"%s/%s\",\n-\t\t\t    get_worktree_git_dir(worktrees[i]),\n+\t\t\t    get_worktree_git_dir(wt),\n \t\t\t    symref);\n \n \t\tif (parse_ref(path.buf, &sb, NULL)) {\n@@ -240,7 +242,7 @@ const struct worktree *find_shared_symref(const char *symref,\n \t\t}\n \n \t\tif (!strcmp(sb.buf, target)) {\n-\t\t\texisting = worktrees[i];\n+\t\t\texisting = wt;\n \t\t\tbreak;\n \t\t}\n \t}\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284122","messageId":"1461330096-21783-11-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 10/13] worktree.c: check whether branch is rebased in another worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:33Z","receivedAt":"2016-04-22T13:01:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This function find_shared_symref() is used in a couple places:\n\n1) in builtin/branch.c: it's used to detect if a branch is checked out\n   elsewhere and refuse to delete the branch.\n\n2) in builtin/notes.c: it's used to detect if a note is being merged in\n   another worktree\n\n3) in branch.c, the function die_if_checked_out() is actually used by\n   \"git checkout\" and \"git worktree add\" to see if a branch is already\n   checked out elsewhere and refuse the operation.\n\nIn cases 1 and 3, if a rebase is ongoing, \"HEAD\" will be in detached\nmode, find_shared_symref() fails to detect it and declares \"no branch is\nchecked out here\", which is not really what we want.\n\nThis patch tightens the test. If the given symref is \"HEAD\", we try to\ndetect if rebase is ongoing. If so return the branch being rebased. This\nmakes checkout and branch delete operations safer because you can't\ncheckout a branch being rebased in another place, or delete it.\n\nSpecial case for checkout. If the current branch is being rebased,\ngit-rebase.sh may use \"git checkout\" to abort and return back to the\noriginal branch. The updated test in find_shared_symref() will prevent\nthat and \"git rebase --abort\" will fail as a result.\nfind_shared_symref() and die_if_checked_out() have to learn a new\noption ignore_current_worktree to loosen the test a bit.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n branch.c                |  4 ++--\n branch.h                |  2 +-\n builtin/checkout.c      |  2 +-\n builtin/worktree.c      |  4 ++--\n t/t2025-worktree-add.sh | 38 ++++++++++++++++++++++++++++++++++++++\n worktree.c              | 32 ++++++++++++++++++++++++++++++++\n 6 files changed, 76 insertions(+), 6 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 1f1fbf5..a5a8dcb 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -334,12 +334,12 @@ void remove_branch_state(void)\n \tunlink(git_path_squash_msg());\n }\n \n-void die_if_checked_out(const char *branch)\n+void die_if_checked_out(const char *branch, int ignore_current_worktree)\n {\n \tconst struct worktree *wt;\n \n \twt = find_shared_symref(\"HEAD\", branch);\n-\tif (!wt)\n+\tif (!wt || (ignore_current_worktree && wt->is_current))\n \t\treturn;\n \tskip_prefix(branch, \"refs/heads/\", &branch);\n \tdie(_(\"'%s' is already checked out at '%s'\"),\ndiff --git a/branch.h b/branch.h\nindex d69163d..b2f9649 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -58,7 +58,7 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);\n  * worktree and die (with a message describing its checkout location) if\n  * it is.\n  */\n-extern void die_if_checked_out(const char *branch);\n+extern void die_if_checked_out(const char *branch, int ignore_current_worktree);\n \n /*\n  * Update all per-worktree HEADs pointing at the old ref to point the new ref.\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex efcbd8f..6041718 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1111,7 +1111,7 @@ static int checkout_branch(struct checkout_opts *opts,\n \t\tchar *head_ref = resolve_refdup(\"HEAD\", 0, sha1, &flag);\n \t\tif (head_ref &&\n \t\t    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))\n-\t\t\tdie_if_checked_out(new->path);\n+\t\t\tdie_if_checked_out(new->path, 1);\n \t\tfree(head_ref);\n \t}\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d8e3795..12c0af7 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname,\n \tif (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&\n \t\t ref_exists(symref.buf)) { /* it's a branch */\n \t\tif (!opts->force)\n-\t\t\tdie_if_checked_out(symref.buf);\n+\t\t\tdie_if_checked_out(symref.buf, 0);\n \t} else { /* must be a commit */\n \t\tcommit = lookup_commit_reference_by_name(refname);\n \t\tif (!commit)\n@@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix)\n \t\tif (!opts.force &&\n \t\t    !strbuf_check_branch_ref(&symref, opts.new_branch) &&\n \t\t    ref_exists(symref.buf))\n-\t\t\tdie_if_checked_out(symref.buf);\n+\t\t\tdie_if_checked_out(symref.buf, 0);\n \t\tstrbuf_release(&symref);\n \t}\n \ndiff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh\nindex 3acb992..da54327 100755\n--- a/t/t2025-worktree-add.sh\n+++ b/t/t2025-worktree-add.sh\n@@ -4,6 +4,8 @@ test_description='test git worktree add'\n \n . ./test-lib.sh\n \n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n test_expect_success 'setup' '\n \ttest_commit init\n '\n@@ -225,4 +227,40 @@ test_expect_success '\"add\" worktree with --checkout' '\n \ttest_cmp init.t swamp2/init.t\n '\n \n+test_expect_success 'put a worktree under rebase' '\n+\tgit worktree add under-rebase &&\n+\t(\n+\t\tcd under-rebase &&\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"edit 1\" git rebase -i HEAD^ &&\n+\t\tgit worktree list | grep \"under-rebase.*detached HEAD\"\n+\t)\n+'\n+\n+test_expect_success 'add a worktree, checking out a rebased branch' '\n+\ttest_must_fail git worktree add new-rebase under-rebase &&\n+\t! test -d new-rebase\n+'\n+\n+test_expect_success 'checking out a rebased branch from another worktree' '\n+\tgit worktree add new-place &&\n+\ttest_must_fail git -C new-place checkout under-rebase\n+'\n+\n+test_expect_success 'not allow to delete a branch under rebase' '\n+\t(\n+\t\tcd under-rebase &&\n+\t\ttest_must_fail git branch -D under-rebase\n+\t)\n+'\n+\n+test_expect_success 'check out from current worktree branch ok' '\n+\t(\n+\t\tcd under-rebase &&\n+\t\tgit checkout under-rebase &&\n+\t\tgit checkout - &&\n+\t\tgit rebase --abort\n+\t)\n+'\n+\n test_done\ndiff --git a/worktree.c b/worktree.c\nindex 927905b..5043756 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -3,6 +3,7 @@\n #include \"strbuf.h\"\n #include \"worktree.h\"\n #include \"dir.h\"\n+#include \"wt-status.h\"\n \n void free_worktrees(struct worktree **worktrees)\n {\n@@ -215,6 +216,30 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n+static int is_worktree_being_rebased(const struct worktree *wt,\n+\t\t\t\t     const char *target)\n+{\n+\tstruct wt_status_state state;\n+\tint found_rebase;\n+\n+\tmemset(&state, 0, sizeof(state));\n+\tfound_rebase = wt_status_check_rebase(wt, &state) &&\n+\t\t((state.rebase_in_progress ||\n+\t\t  state.rebase_interactive_in_progress) &&\n+\t\t state.branch &&\n+\t\t starts_with(target, \"refs/heads/\") &&\n+\t\t !strcmp(state.branch, target + strlen(\"refs/heads/\")));\n+\tfree(state.branch);\n+\tfree(state.onto);\n+\treturn found_rebase;\n+}\n+\n+/*\n+ * note: this function should be able to detect shared symref even if\n+ * HEAD is temporarily detached (e.g. in the middle of rebase or\n+ * bisect). New commands that do similar things should update this\n+ * function as well.\n+ */\n const struct worktree *find_shared_symref(const char *symref,\n \t\t\t\t\t  const char *target)\n {\n@@ -231,6 +256,13 @@ const struct worktree *find_shared_symref(const char *symref,\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstruct worktree *wt = worktrees[i];\n \n+\t\tif (wt->is_detached && !strcmp(symref, \"HEAD\")) {\n+\t\t\tif (is_worktree_being_rebased(wt, target)) {\n+\t\t\t\texisting = wt;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n \t\tstrbuf_addf(&path, \"%s/%s\",\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284123","messageId":"1461330096-21783-12-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 11/13] wt-status.c: split bisect detection out of wt_status_get_state()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:34Z","receivedAt":"2016-04-22T13:01:34Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"And make it work with any given worktree, in preparation for (again)\nfind_shared_symref(). read_and_strip_branch() is deleted because it's\nno longer used.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n wt-status.c | 23 ++++++++++++++---------\n wt-status.h |  2 ++\n 2 files changed, 16 insertions(+), 9 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex ce5080c..0032ef5 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1296,11 +1296,6 @@ got_nothing:\n \treturn NULL;\n }\n \n-static char *read_and_strip_branch(const char *path)\n-{\n-\treturn get_branch(NULL, path);\n-}\n-\n struct grab_1st_switch_cbdata {\n \tstruct strbuf buf;\n \tunsigned char nsha1[20];\n@@ -1393,6 +1388,19 @@ int wt_status_check_rebase(const struct worktree *wt,\n \treturn 1;\n }\n \n+int wt_status_check_bisect(const struct worktree *wt,\n+\t\t\t   struct wt_status_state *state)\n+{\n+\tstruct stat st;\n+\n+\tif (!stat(worktree_git_path(wt, \"BISECT_LOG\"), &st)) {\n+\t\tstate->bisect_in_progress = 1;\n+\t\tstate->branch = get_branch(wt, \"BISECT_START\");\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n void wt_status_get_state(struct wt_status_state *state,\n \t\t\t int get_detached_from)\n {\n@@ -1408,10 +1416,7 @@ void wt_status_get_state(struct wt_status_state *state,\n \t\tstate->cherry_pick_in_progress = 1;\n \t\thashcpy(state->cherry_pick_head_sha1, sha1);\n \t}\n-\tif (!stat(git_path(\"BISECT_LOG\"), &st)) {\n-\t\tstate->bisect_in_progress = 1;\n-\t\tstate->branch = read_and_strip_branch(\"BISECT_START\");\n-\t}\n+\twt_status_check_bisect(NULL, state);\n \tif (!stat(git_path_revert_head(), &st) &&\n \t    !get_sha1(\"REVERT_HEAD\", sha1)) {\n \t\tstate->revert_in_progress = 1;\ndiff --git a/wt-status.h b/wt-status.h\nindex c4ddcad..2ca93f6 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -104,6 +104,8 @@ void wt_status_collect(struct wt_status *s);\n void wt_status_get_state(struct wt_status_state *state, int get_detached_from);\n int wt_status_check_rebase(const struct worktree *wt,\n \t\t\t   struct wt_status_state *state);\n+int wt_status_check_bisect(const struct worktree *wt,\n+\t\t\t   struct wt_status_state *state);\n \n void wt_shortstatus_print(struct wt_status *s);\n void wt_porcelain_print(struct wt_status *s);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284124","messageId":"1461330096-21783-13-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 12/13] worktree.c: check whether branch is bisected in another worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:35Z","receivedAt":"2016-04-22T13:01:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Similar to the rebase case, we want to detect if \"HEAD\" in some worktree\nis being bisected because\n\n1) we do not want to checkout this branch in another worktree, after\n   bisect is done it will want to go back to this branch\n\n2) we do not want to delete the branch is either or git bisect will\n   fail to return to the (long gone) branch\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n t/t2025-worktree-add.sh | 13 +++++++++++++\n worktree.c              | 19 +++++++++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh\nindex da54327..8f53944 100755\n--- a/t/t2025-worktree-add.sh\n+++ b/t/t2025-worktree-add.sh\n@@ -263,4 +263,17 @@ test_expect_success 'check out from current worktree branch ok' '\n \t)\n '\n \n+test_expect_success 'checkout a branch under bisect' '\n+\tgit worktree add under-bisect &&\n+\t(\n+\t\tcd under-bisect &&\n+\t\tgit bisect start &&\n+\t\tgit bisect bad &&\n+\t\tgit bisect good HEAD~2 &&\n+\t\tgit worktree list | grep \"under-bisect.*detached HEAD\" &&\n+\t\ttest_must_fail git worktree add new-bisect under-bisect &&\n+\t\t! test -d new-bisect\n+\t)\n+'\n+\n test_done\ndiff --git a/worktree.c b/worktree.c\nindex 5043756..aab4b95 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -234,6 +234,21 @@ static int is_worktree_being_rebased(const struct worktree *wt,\n \treturn found_rebase;\n }\n \n+static int is_worktree_being_bisected(const struct worktree *wt,\n+\t\t\t\t      const char *target)\n+{\n+\tstruct wt_status_state state;\n+\tint found_rebase;\n+\n+\tmemset(&state, 0, sizeof(state));\n+\tfound_rebase = wt_status_check_bisect(wt, &state) &&\n+\t\tstate.branch &&\n+\t\tstarts_with(target, \"refs/heads/\") &&\n+\t\t!strcmp(state.branch, target + strlen(\"refs/heads/\"));\n+\tfree(state.branch);\n+\treturn found_rebase;\n+}\n+\n /*\n  * note: this function should be able to detect shared symref even if\n  * HEAD is temporarily detached (e.g. in the middle of rebase or\n@@ -261,6 +276,10 @@ const struct worktree *find_shared_symref(const char *symref,\n \t\t\t\texisting = wt;\n \t\t\t\tbreak;\n \t\t\t}\n+\t\t\tif (is_worktree_being_bisected(wt, target)) {\n+\t\t\t\texisting = wt;\n+\t\t\t\tbreak;\n+\t\t\t}\n \t\t}\n \n \t\tstrbuf_reset(&path);\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"284125","messageId":"1461330096-21783-14-git-send-email-pclouds@gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3 13/13] branch: do not rename a branch under bisect or rebase","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-22T13:01:36Z","receivedAt":"2016-04-22T13:01:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The branch name in that case could be saved in rebase's head_name or\nbisect's BISECT_START files. Ideally we should try to update them as\nwell. But it's trickier (*). Let's play safe and see if the user\ncomplains about inconveniences before doing that.\n\n(*) If we do it, bisect and rebase need to provide an API to rename\nbranches. We can't do it in worktree.c or builtin/branch.c because\nwhen other people change rebase/bisect code, they may not be aware of\nthis code and accidentally break it (e.g. rename the branch file, or\nrefer to the branch in new files). It's a lot more work.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/branch.c        | 25 +++++++++++++++++++++++++\n t/t2025-worktree-add.sh |  8 ++++++++\n worktree.c              |  8 ++++----\n worktree.h              |  3 +++\n 4 files changed, 40 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex bcde87d..b488c3f 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -524,6 +524,29 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin\n \tref_array_clear(&array);\n }\n \n+static void reject_rebase_or_bisect_branch(const char *target)\n+{\n+\tstruct worktree **worktrees = get_worktrees();\n+\tint i;\n+\n+\tfor (i = 0; worktrees[i]; i++) {\n+\t\tstruct worktree *wt = worktrees[i];\n+\n+\t\tif (!wt->is_detached)\n+\t\t\tcontinue;\n+\n+\t\tif (is_worktree_being_rebased(wt, target))\n+\t\t\tdie(_(\"Branch %s is being rebased at %s\"),\n+\t\t\t    target, wt->path);\n+\n+\t\tif (is_worktree_being_bisected(wt, target))\n+\t\t\tdie(_(\"Branch %s is being bisected at %s\"),\n+\t\t\t    target, wt->path);\n+\t}\n+\n+\tfree_worktrees(worktrees);\n+}\n+\n static void rename_branch(const char *oldname, const char *newname, int force)\n {\n \tstruct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;\n@@ -553,6 +576,8 @@ static void rename_branch(const char *oldname, const char *newname, int force)\n \n \tvalidate_new_branchname(newname, &newref, force, clobber_head_ok);\n \n+\treject_rebase_or_bisect_branch(oldref.buf);\n+\n \tstrbuf_addf(&logmsg, \"Branch: renamed %s to %s\",\n \t\t oldref.buf, newref.buf);\n \ndiff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh\nindex 8f53944..3a22fc5 100755\n--- a/t/t2025-worktree-add.sh\n+++ b/t/t2025-worktree-add.sh\n@@ -254,6 +254,10 @@ test_expect_success 'not allow to delete a branch under rebase' '\n \t)\n '\n \n+test_expect_success 'rename a branch under rebase not allowed' '\n+\ttest_must_fail git branch -M under-rebase rebase-with-new-name\n+'\n+\n test_expect_success 'check out from current worktree branch ok' '\n \t(\n \t\tcd under-rebase &&\n@@ -276,4 +280,8 @@ test_expect_success 'checkout a branch under bisect' '\n \t)\n '\n \n+test_expect_success 'rename a branch under bisect not allowed' '\n+\ttest_must_fail git branch -M under-bisect bisect-with-new-name\n+'\n+\n test_done\ndiff --git a/worktree.c b/worktree.c\nindex aab4b95..4817d60 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -216,8 +216,8 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n-static int is_worktree_being_rebased(const struct worktree *wt,\n-\t\t\t\t     const char *target)\n+int is_worktree_being_rebased(const struct worktree *wt,\n+\t\t\t      const char *target)\n {\n \tstruct wt_status_state state;\n \tint found_rebase;\n@@ -234,8 +234,8 @@ static int is_worktree_being_rebased(const struct worktree *wt,\n \treturn found_rebase;\n }\n \n-static int is_worktree_being_bisected(const struct worktree *wt,\n-\t\t\t\t      const char *target)\n+int is_worktree_being_bisected(const struct worktree *wt,\n+\t\t\t       const char *target)\n {\n \tstruct wt_status_state state;\n \tint found_rebase;\ndiff --git a/worktree.h b/worktree.h\nindex 0da8c1f..1394909 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -42,6 +42,9 @@ extern void free_worktrees(struct worktree **);\n extern const struct worktree *find_shared_symref(const char *symref,\n \t\t\t\t\t\t const char *target);\n \n+int is_worktree_being_rebased(const struct worktree *wt, const char *target);\n+int is_worktree_being_bisected(const struct worktree *wt, const char *target);\n+\n /*\n  * Similar to git_path() but can produce paths for a specified\n  * worktree instead of current one\n-- \n2.8.0.rc0.210.gd302cd2\n"},{"id":"285674","messageId":"CAPig+cQszEZLaebwqXNny6_EnhFpwU1xYqEX2_0N=vTEXTk2Fw@mail.gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-6-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 05/13] worktree.c: mark current worktree","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-06T07:51:59Z","receivedAt":"2016-05-06T07:51:59Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Apr 22, 2016 at 9:01 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/worktree.c b/worktree.c\n> @@ -147,6 +150,25 @@ done:\n> +static void mark_current_worktree(struct worktree **worktrees)\n> +{\n> +       struct strbuf git_dir = STRBUF_INIT;\n> +       struct strbuf path = STRBUF_INIT;\n> +       int i;\n> +\n> +       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n\nThis could also just be:\n\n    char *git_dir = xstrdup(absolute_path(...));\n\nwith a corresponding 'free(git_dir)' below.\n\n> +       for (i = 0; worktrees[i]; i++) {\n> +               struct worktree *wt = worktrees[i];\n> +               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n> +               wt->is_current = !fspathcmp(git_dir.buf, path.buf);\n\nSimilarly, it looks like 'path' doesn't need to be a strbuf at all\nsince the result of absolute_path() should remain valid long enough\nfor fspathcmp(). It could just be:\n\n    const char *path = absolute_path(...);\n    wt->is_current = !fspathcmp(git_dir, path);\n\nBut these are very minor; probably not worth a re-roll.\n\n> +               strbuf_reset(&path);\n> +               if (wt->is_current)\n> +                       break;\n> +       }\n> +       strbuf_release(&git_dir);\n> +       strbuf_release(&path);\n> +}\n"},{"id":"285678","messageId":"CACsJy8D+anuZ-278kaz2ewpcLCGOSrdn2Qq1F7fFTuVUTLv5Yw@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cQszEZLaebwqXNny6_EnhFpwU1xYqEX2_0N=vTEXTk2Fw@mail.gmail.com","subject":"Re: [PATCH v3 05/13] worktree.c: mark current worktree","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-06T10:21:05Z","receivedAt":"2016-05-06T10:21:05Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, May 6, 2016 at 2:51 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Fri, Apr 22, 2016 at 9:01 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>> diff --git a/worktree.c b/worktree.c\n>> @@ -147,6 +150,25 @@ done:\n>> +static void mark_current_worktree(struct worktree **worktrees)\n>> +{\n>> +       struct strbuf git_dir = STRBUF_INIT;\n>> +       struct strbuf path = STRBUF_INIT;\n>> +       int i;\n>> +\n>> +       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n>\n> This could also just be:\n>\n>     char *git_dir = xstrdup(absolute_path(...));\n>\n> with a corresponding 'free(git_dir)' below.\n>\n>> +       for (i = 0; worktrees[i]; i++) {\n>> +               struct worktree *wt = worktrees[i];\n>> +               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n>> +               wt->is_current = !fspathcmp(git_dir.buf, path.buf);\n>\n> Similarly, it looks like 'path' doesn't need to be a strbuf at all\n> since the result of absolute_path() should remain valid long enough\n> for fspathcmp(). It could just be:\n>\n>     const char *path = absolute_path(...);\n>     wt->is_current = !fspathcmp(git_dir, path);\n>\n> But these are very minor; probably not worth a re-roll.\n\nYeah. I think the use of strbuf is influenced by the code in\nget_worktrees(). But since this code is now in a separate function, it\nmakes little sense to go with the strbuf hammer. If there's no big\nchange in this series, I'll just do this as a cleanup step in my next\nseries, worktree-move.\n-- \nDuy\n"},{"id":"286057","messageId":"20160510141416.GA22672@lanh","threadId":"42034","inReplyTo":"CACsJy8D+anuZ-278kaz2ewpcLCGOSrdn2Qq1F7fFTuVUTLv5Yw@mail.gmail.com","subject":"Re: [PATCH v3 05/13] worktree.c: mark current worktree","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:14:16Z","receivedAt":"2016-05-10T14:14:16Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, May 06, 2016 at 05:21:05PM +0700, Duy Nguyen wrote:\n> > Similarly, it looks like 'path' doesn't need to be a strbuf at all\n> > since the result of absolute_path() should remain valid long enough\n> > for fspathcmp(). It could just be:\n> >\n> >     const char *path = absolute_path(...);\n> >     wt->is_current = !fspathcmp(git_dir, path);\n> >\n> > But these are very minor; probably not worth a re-roll.\n> \n> Yeah. I think the use of strbuf is influenced by the code in\n> get_worktrees(). But since this code is now in a separate function, it\n> makes little sense to go with the strbuf hammer. If there's no big\n> change in this series, I'll just do this as a cleanup step in my next\n> series, worktree-move.\n\nOn second thought, why hold patches back, lengthen the worktree-move\nseries and make it a pain to review? I moved a few patches from\nworktree-move into this series and I took two other out to create\nnd/error-errno. So I'm going to take more patches out of it, creating\ntwo bite-sized series, to be sent shortly.\n\nThe first one is purely cleanup (ok, 1/7 is not exactly cleanup)\n\n  [1/7] completion: support git-worktree\n  [2/7] worktree.c: rewrite mark_current_worktree() to avoid\n  [3/7] git-worktree.txt: keep subcommand listing in alphabetical\n  [4/7] worktree.c: use is_dot_or_dotdot()\n  [5/7] worktree.c: add clear_worktree()\n  [6/7] worktree: avoid 0{40}, too many zeroes, hard to read\n  [7/7] worktree: simplify prefixing paths\n\nAnd the second one adds \"git worktree lock\" and \"git worktree\nunlock\". This series is built on top of the first one, it needs 1/7.\n\n  [1/5] worktree.c: add find_worktree_by_path()\n  [2/5] worktree.c: add is_main_worktree()\n  [3/5] worktree.c: add is_worktree_locked()\n  [4/5] worktree: add \"lock\" command\n  [5/5] worktree: add \"unlock\" command\n\nAfter this, worktree-move becomes ~10 patch series.\n-- \nDuy\n"},{"id":"286058","messageId":"20160510141553.22967-1-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141416.GA22672@lanh","subject":"[PATCH 1/7] completion: support git-worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:15:47Z","receivedAt":"2016-05-10T14:15:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++\n 1 file changed, 23 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 3402475..d3ac391 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2595,6 +2595,29 @@ _git_whatchanged ()\n \t_git_log\n }\n \n+_git_worktree ()\n+{\n+\tlocal subcommands=\"add list prune\"\n+\tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n+\tif [ -z \"$subcommand\" ]; then\n+\t\t__gitcomp \"$subcommands\"\n+\telse\n+\t\tcase \"$subcommand,$cur\" in\n+\t\tadd,--*)\n+\t\t\t__gitcomp \"--detach --force\"\n+\t\t\t;;\n+\t\tlist,--*)\n+\t\t\t__gitcomp \"--porcelain\"\n+\t\t\t;;\n+\t\tprune,--*)\n+\t\t\t__gitcomp \"--dry-run --expire --verbose\"\n+\t\t\t;;\n+\t\t*)\n+\t\t\t;;\n+\t\tesac\n+\tfi\n+}\n+\n __git_main ()\n {\n \tlocal i c=1 command __git_dir\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286059","messageId":"20160510141553.22967-2-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141553.22967-1-pclouds@gmail.com","subject":"[PATCH 2/7] worktree.c: rewrite mark_current_worktree() to avoid strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:15:48Z","receivedAt":"2016-05-10T14:15:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"strbuf is a bit overkill for this function. What we need is call\nabsolute_path() twice and make sure the second call does not destroy the\nresult of the first. One buffer allocation is enough.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 16 +++++++---------\n 1 file changed, 7 insertions(+), 9 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex 4817d60..6a11611 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -153,21 +153,19 @@ done:\n \n static void mark_current_worktree(struct worktree **worktrees)\n {\n-\tstruct strbuf git_dir = STRBUF_INIT;\n-\tstruct strbuf path = STRBUF_INIT;\n+\tchar *git_dir = xstrdup(absolute_path(get_git_dir()));\n \tint i;\n \n-\tstrbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstruct worktree *wt = worktrees[i];\n-\t\tstrbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n-\t\twt->is_current = !fspathcmp(git_dir.buf, path.buf);\n-\t\tstrbuf_reset(&path);\n-\t\tif (wt->is_current)\n+\t\tconst char *wt_git_dir = get_worktree_git_dir(wt);\n+\n+\t\tif (!fspathcmp(git_dir, absolute_path(wt_git_dir))) {\n+\t\t\twt->is_current = 1;\n \t\t\tbreak;\n+\t\t}\n \t}\n-\tstrbuf_release(&git_dir);\n-\tstrbuf_release(&path);\n+\tfree(git_dir);\n }\n \n struct worktree **get_worktrees(void)\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286060","messageId":"20160510141553.22967-3-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141553.22967-1-pclouds@gmail.com","subject":"[PATCH 3/7] git-worktree.txt: keep subcommand listing in alphabetical order","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:15:49Z","receivedAt":"2016-05-10T14:15:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is probably not the best order. But it makes it no-brainer to know\nwhere to insert new commands. At some point we might want to reorder at\nleast the synopsis part again, grouping commonly use subcommands together.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt | 10 +++++-----\n builtin/worktree.c             |  2 +-\n 2 files changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex c622345..27feff6 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -10,8 +10,8 @@ SYNOPSIS\n --------\n [verse]\n 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]\n-'git worktree prune' [-n] [-v] [--expire <expire>]\n 'git worktree list' [--porcelain]\n+'git worktree prune' [-n] [-v] [--expire <expire>]\n \n DESCRIPTION\n -----------\n@@ -54,10 +54,6 @@ If `<branch>` is omitted and neither `-b` nor `-B` nor `--detached` used,\n then, as a convenience, a new branch based at HEAD is created automatically,\n as if `-b $(basename <path>)` was specified.\n \n-prune::\n-\n-Prune working tree information in $GIT_DIR/worktrees.\n-\n list::\n \n List details of each worktree.  The main worktree is listed first, followed by\n@@ -65,6 +61,10 @@ each of the linked worktrees.  The output details include if the worktree is\n bare, the revision currently checked out, and the branch currently checked out\n (or 'detached HEAD' if none).\n \n+prune::\n+\n+Prune working tree information in $GIT_DIR/worktrees.\n+\n OPTIONS\n -------\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 12c0af7..bf80111 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -13,8 +13,8 @@\n \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+\tN_(\"git worktree prune [<options>]\"),\n \tNULL\n };\n \n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286061","messageId":"20160510141553.22967-4-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141553.22967-1-pclouds@gmail.com","subject":"[PATCH 4/7] worktree.c: use is_dot_or_dotdot()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:15:50Z","receivedAt":"2016-05-10T14:15:50Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 2 +-\n worktree.c         | 2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex bf80111..aaee0e2 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -95,7 +95,7 @@ static void prune_worktrees(void)\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\tif (is_dot_or_dotdot(d->d_name))\n \t\t\tcontinue;\n \t\tstrbuf_reset(&reason);\n \t\tif (!prune_worktree(d->d_name, &reason))\ndiff --git a/worktree.c b/worktree.c\nindex 6a11611..f4a4f38 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -187,7 +187,7 @@ struct worktree **get_worktrees(void)\n \tif (dir) {\n \t\twhile ((d = readdir(dir)) != NULL) {\n \t\t\tstruct worktree *linked = NULL;\n-\t\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\tif (is_dot_or_dotdot(d->d_name))\n \t\t\t\tcontinue;\n \n \t\t\tif ((linked = get_linked_worktree(d->d_name))) {\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286064","messageId":"20160510141553.22967-5-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141553.22967-1-pclouds@gmail.com","subject":"[PATCH 5/7] worktree.c: add clear_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:15:51Z","receivedAt":"2016-05-10T14:15:51Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The use case is keep some worktree and discard the rest of the worktree\nlist.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 14 +++++++++++---\n worktree.h |  5 +++++\n 2 files changed, 16 insertions(+), 3 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex f4a4f38..335c58f 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -5,14 +5,22 @@\n #include \"dir.h\"\n #include \"wt-status.h\"\n \n+void clear_worktree(struct worktree *wt)\n+{\n+\tif (!wt)\n+\t\treturn;\n+\tfree(wt->path);\n+\tfree(wt->id);\n+\tfree(wt->head_ref);\n+\tmemset(wt, 0, sizeof(*wt));\n+}\n+\n void free_worktrees(struct worktree **worktrees)\n {\n \tint i = 0;\n \n \tfor (i = 0; worktrees[i]; i++) {\n-\t\tfree(worktrees[i]->path);\n-\t\tfree(worktrees[i]->id);\n-\t\tfree(worktrees[i]->head_ref);\n+\t\tclear_worktree(worktrees[i]);\n \t\tfree(worktrees[i]);\n \t}\n \tfree (worktrees);\ndiff --git a/worktree.h b/worktree.h\nindex 1394909..7430a4e 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -29,6 +29,11 @@ extern struct worktree **get_worktrees(void);\n  */\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n+/*\n+ * Free up the memory for worktree\n+ */\n+extern void clear_worktree(struct worktree *);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286062","messageId":"20160510141553.22967-6-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141553.22967-1-pclouds@gmail.com","subject":"[PATCH 6/7] worktree: avoid 0{40}, too many zeroes, hard to read","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:15:52Z","receivedAt":"2016-05-10T14:15:52Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex aaee0e2..b53f802 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -262,7 +262,7 @@ static int add_worktree(const char *path, const char *refname,\n \t */\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n-\twrite_file(sb.buf, \"0000000000000000000000000000000000000000\");\n+\twrite_file(sb.buf, sha1_to_hex(null_sha1));\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n \twrite_file(sb.buf, \"../..\");\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286063","messageId":"20160510141553.22967-7-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141553.22967-1-pclouds@gmail.com","subject":"[PATCH 7/7] worktree: simplify prefixing paths","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:15:53Z","receivedAt":"2016-05-10T14:15:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex b53f802..f9dac37 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)\n \tif (ac < 1 || ac > 2)\n \t\tusage_with_options(worktree_usage, options);\n \n-\tpath = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];\n+\tpath = prefix_filename(prefix, strlen(prefix), av[0]);\n \tbranch = ac < 2 ? \"HEAD\" : av[1];\n \n \topts.force_new_branch = !!new_branch_force;\n@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \n \tif (ac < 2)\n \t\tusage_with_options(worktree_usage, options);\n+\tif (!prefix)\n+\t\tprefix = \"\";\n \tif (!strcmp(av[1], \"add\"))\n \t\treturn add(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"prune\"))\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286065","messageId":"20160510141729.23063-1-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141416.GA22672@lanh","subject":"[PATCH 1/5] worktree.c: add find_worktree_by_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:17:25Z","receivedAt":"2016-05-10T14:17:25Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"So far we haven't needed to identify an existing worktree from command\nline. Future commands such as lock or move will need it. There are of\ncourse other options for identifying a worktree, for example by branch\nor even by internal id. They may be added later if proved useful.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 16 ++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 22 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 335c58f..a6d1ad1 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -222,6 +222,22 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n+struct worktree *find_worktree_by_path(struct worktree **list,\n+\t\t\t\t       const char *path_)\n+{\n+\tchar *path = xstrdup(real_path(path_));\n+\tstruct worktree *wt = NULL;\n+\n+\twhile (*list) {\n+\t\twt = *list++;\n+\t\tif (!fspathcmp(path, real_path(wt->path)))\n+\t\t\tbreak;\n+\t\twt = NULL;\n+\t}\n+\tfree(path);\n+\treturn wt;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 7430a4e..7775d1b 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -29,6 +29,12 @@ extern struct worktree **get_worktrees(void);\n  */\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n+/*\n+ * Search a worktree by its path. Paths are normalized internally.\n+ */\n+extern struct worktree *find_worktree_by_path(struct worktree **list,\n+\t\t\t\t\t      const char *path_);\n+\n /*\n  * Free up the memory for worktree\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286066","messageId":"20160510141729.23063-2-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141729.23063-1-pclouds@gmail.com","subject":"[PATCH 2/5] worktree.c: add is_main_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:17:26Z","receivedAt":"2016-05-10T14:17:26Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Main worktree _is_ different. You can lock a linked worktree but not the\nmain one, for example. Provide an API for checking that.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 5 +++++\n worktree.h | 5 +++++\n 2 files changed, 10 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex a6d1ad1..37fea3d 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -238,6 +238,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,\n \treturn wt;\n }\n \n+int is_main_worktree(const struct worktree *wt)\n+{\n+\treturn wt && !wt->id;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 7775d1b..77a9e09 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -35,6 +35,11 @@ extern const char *get_worktree_git_dir(const struct worktree *wt);\n extern struct worktree *find_worktree_by_path(struct worktree **list,\n \t\t\t\t\t      const char *path_);\n \n+/*\n+ * Return true if the given worktree is the main one.\n+ */\n+extern int is_main_worktree(const struct worktree *wt);\n+\n /*\n  * Free up the memory for worktree\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286067","messageId":"20160510141729.23063-3-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141729.23063-1-pclouds@gmail.com","subject":"[PATCH 3/5] worktree.c: add is_worktree_locked()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:17:27Z","receivedAt":"2016-05-10T14:17:27Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This provides an API for checking if a worktree is locked. We need to\ncheck this to avoid double locking a worktree, or try to unlock one when\nit's not even locked.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 18 ++++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 24 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 37fea3d..6805deb 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -243,6 +243,24 @@ int is_main_worktree(const struct worktree *wt)\n \treturn wt && !wt->id;\n }\n \n+const char *is_worktree_locked(const struct worktree *wt)\n+{\n+\tstatic struct strbuf sb = STRBUF_INIT;\n+\n+\tif (!file_exists(git_common_path(\"worktrees/%s/locked\", wt->id)))\n+\t\treturn NULL;\n+\n+\tstrbuf_reset(&sb);\n+\tif (strbuf_read_file(&sb,\n+\t\t\t     git_common_path(\"worktrees/%s/locked\", wt->id),\n+\t\t\t     0) < 0)\n+\t\tdie_errno(_(\"failed to read '%s'\"),\n+\t\t\t  git_common_path(\"worktrees/%s/locked\", wt->id));\n+\n+\tstrbuf_rtrim(&sb);\n+\treturn sb.buf;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 77a9e09..12906be 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -40,6 +40,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,\n  */\n extern int is_main_worktree(const struct worktree *wt);\n \n+/*\n+ * Return the reason string if the given worktree is locked. Return\n+ * NULL otherwise.\n+ */\n+extern const char *is_worktree_locked(const struct worktree *wt);\n+\n /*\n  * Free up the memory for worktree\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286069","messageId":"20160510141729.23063-4-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141729.23063-1-pclouds@gmail.com","subject":"[PATCH 4/5] worktree: add \"lock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:17:28Z","receivedAt":"2016-05-10T14:17:28Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         | 12 ++++++++--\n builtin/worktree.c                     | 41 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 ++++-\n t/t2028-worktree-move.sh (new +x)      | 34 ++++++++++++++++++++++++++++\n 4 files changed, 89 insertions(+), 3 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 27feff6..74583c1 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] [--checkout] [-b <new-branch>] <path> [<branch>]\n 'git worktree list' [--porcelain]\n+'git worktree lock' [--reason <string>] <path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n \n DESCRIPTION\n@@ -61,6 +62,12 @@ each of the linked worktrees.  The output details include if the worktree is\n bare, the revision currently checked out, and the branch currently checked out\n (or 'detached HEAD' if none).\n \n+lock::\n+\n+When a worktree is locked, it cannot be pruned, moved or deleted. For\n+example, if the worktree is on portable device that is not available\n+when \"git worktree <command>\" is executed.\n+\n prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n@@ -110,6 +117,9 @@ OPTIONS\n --expire <time>::\n \tWith `prune`, only expire unused working trees older than <time>.\n \n+--reason <string>:\n+\tAn explanation why the worktree is locked.\n+\n DETAILS\n -------\n Each linked working tree has a private sub-directory in the repository's\n@@ -226,8 +236,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-- `lock` to prevent automatic pruning of administrative files (for instance,\n-  for a working tree on a portable device)\n \n GIT\n ---\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex f9dac37..51fa103 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -14,6 +14,7 @@\n static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> [<branch>]\"),\n \tN_(\"git worktree list [<options>]\"),\n+\tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n \tNULL\n };\n@@ -459,6 +460,44 @@ static int list(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int lock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tconst char *reason = \"\", *old_reason;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"reason\", &reason, N_(\"string\"),\n+\t\t\t   N_(\"reason for locking\")),\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tstrbuf_addstr(&dst, prefix_filename(prefix,\n+\t\t\t\t\t    strlen(prefix),\n+\t\t\t\t\t    av[0]));\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree_by_path(worktrees, dst.buf);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\n+\told_reason = is_worktree_locked(wt);\n+\tif (old_reason) {\n+\t\tif (*old_reason)\n+\t\t\tdie(_(\"already locked, reason: %s\"), old_reason);\n+\t\tdie(_(\"already locked, no reason\"));\n+\t}\n+\n+\twrite_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n+\t\t   \"%s\", reason);\n+\treturn 0;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -475,5 +514,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\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+\tif (!strcmp(av[1], \"lock\"))\n+\t\treturn lock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex d3ac391..6c321aa 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2597,7 +2597,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list prune\"\n+\tlocal subcommands=\"add list lock prune\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\n@@ -2609,6 +2609,9 @@ _git_worktree ()\n \t\tlist,--*)\n \t\t\t__gitcomp \"--porcelain\"\n \t\t\t;;\n+\t\tlock,--*)\n+\t\t\t__gitcomp \"--reason\"\n+\t\t\t;;\n \t\tprune,--*)\n \t\t\t__gitcomp \"--dry-run --expire --verbose\"\n \t\t\t;;\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nnew file mode 100755\nindex 0000000..97434be\n--- /dev/null\n+++ b/t/t2028-worktree-move.sh\n@@ -0,0 +1,34 @@\n+#!/bin/sh\n+\n+test_description='test git worktree move, remove, lock and unlock'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit init &&\n+\tgit worktree add source &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/source\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'lock main worktree' '\n+\ttest_must_fail git worktree lock .\n+'\n+\n+test_expect_success 'lock linked worktree' '\n+\tgit worktree lock --reason hahaha source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock worktree twice' '\n+\ttest_must_fail git worktree lock source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_done\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286068","messageId":"20160510141729.23063-5-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141729.23063-1-pclouds@gmail.com","subject":"[PATCH 5/5] worktree: add \"unlock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-10T14:17:29Z","receivedAt":"2016-05-10T14:17:29Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         |  5 +++++\n builtin/worktree.c                     | 31 +++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  2 +-\n t/t2028-worktree-move.sh               | 14 ++++++++++++++\n 4 files changed, 51 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 74583c1..9ac1129 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n 'git worktree list' [--porcelain]\n 'git worktree lock' [--reason <string>] <path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n+'git worktree unlock' <path>\n \n DESCRIPTION\n -----------\n@@ -72,6 +73,10 @@ prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n \n+unlock::\n+\n+Unlock a worktree, allowing it to be pruned, moved or deleted.\n+\n OPTIONS\n -------\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 51fa103..53e5f5a 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {\n \tN_(\"git worktree list [<options>]\"),\n \tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n+\tN_(\"git worktree unlock <path>\"),\n \tNULL\n };\n \n@@ -498,6 +499,34 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int unlock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tstrbuf_addstr(&dst, prefix_filename(prefix,\n+\t\t\t\t\t    strlen(prefix),\n+\t\t\t\t\t    av[0]));\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree_by_path(worktrees, dst.buf);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\tif (!is_worktree_locked(wt))\n+\t\tdie(_(\"not locked\"));\n+\n+\treturn unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -516,5 +545,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn list(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"lock\"))\n \t\treturn lock_worktree(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"unlock\"))\n+\t\treturn unlock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 6c321aa..db4b0e7 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2597,7 +2597,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list lock prune\"\n+\tlocal subcommands=\"add list lock prune unlock\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex 97434be..f4b2816 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -31,4 +31,18 @@ test_expect_success 'lock worktree twice' '\n \ttest_cmp expected .git/worktrees/source/locked\n '\n \n+test_expect_success 'unlock main worktree' '\n+\ttest_must_fail git worktree unlock .\n+'\n+\n+test_expect_success 'unlock linked worktree' '\n+\tgit worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'unlock worktree twice' '\n+\ttest_must_fail git worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n test_done\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"286084","messageId":"xmqq8tzhrf7b.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160510141416.GA22672@lanh","subject":"Re: [PATCH v3 05/13] worktree.c: mark current worktree","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-10T17:32:56Z","receivedAt":"2016-05-10T17:32:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> On second thought, why hold patches back, lengthen the worktree-move\n> series and make it a pain to review? I moved a few patches from\n> worktree-move into this series and I took two other out to create\n> nd/error-errno. So I'm going to take more patches out of it, creating\n> two bite-sized series, to be sent shortly.\n>\n> The first one is purely cleanup (ok, 1/7 is not exactly cleanup)\n>\n>   [1/7] completion: support git-worktree\n>   [2/7] worktree.c: rewrite mark_current_worktree() to avoid\n>   [3/7] git-worktree.txt: keep subcommand listing in alphabetical\n>   [4/7] worktree.c: use is_dot_or_dotdot()\n>   [5/7] worktree.c: add clear_worktree()\n>   [6/7] worktree: avoid 0{40}, too many zeroes, hard to read\n>   [7/7] worktree: simplify prefixing paths\n>\n> And the second one adds \"git worktree lock\" and \"git worktree\n> unlock\". This series is built on top of the first one, it needs 1/7.\n>\n>   [1/5] worktree.c: add find_worktree_by_path()\n>   [2/5] worktree.c: add is_main_worktree()\n>   [3/5] worktree.c: add is_worktree_locked()\n>   [4/5] worktree: add \"lock\" command\n>   [5/5] worktree: add \"unlock\" command\n>\n> After this, worktree-move becomes ~10 patch series.\n\nYay.  Thanks; will take a look.\n"},{"id":"286127","messageId":"xmqqzirxldn9.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160510141416.GA22672@lanh","subject":"Re: [PATCH v3 05/13] worktree.c: mark current worktree","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-10T23:03:06Z","receivedAt":"2016-05-10T23:03:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> On second thought, why hold patches back, lengthen the worktree-move\n> series and make it a pain to review? I moved a few patches from\n> worktree-move into this series and I took two other out to create\n> nd/error-errno. So I'm going to take more patches out of it, creating\n> two bite-sized series, to be sent shortly.\n>\n> The first one is purely cleanup (ok, 1/7 is not exactly cleanup)\n>\n>   [1/7] completion: support git-worktree\n>   [2/7] worktree.c: rewrite mark_current_worktree() to avoid\n>   [3/7] git-worktree.txt: keep subcommand listing in alphabetical\n>   [4/7] worktree.c: use is_dot_or_dotdot()\n>   [5/7] worktree.c: add clear_worktree()\n>   [6/7] worktree: avoid 0{40}, too many zeroes, hard to read\n>   [7/7] worktree: simplify prefixing paths\n\nWhere are these patches designed to apply?\n\nIt appears that this depends on something in 'next' (probably\nnd/worktree-various-heads topic?)\n\n>\n> And the second one adds \"git worktree lock\" and \"git worktree\n> unlock\". This series is built on top of the first one, it needs 1/7.\n>\n>   [1/5] worktree.c: add find_worktree_by_path()\n>   [2/5] worktree.c: add is_main_worktree()\n>   [3/5] worktree.c: add is_worktree_locked()\n>   [4/5] worktree: add \"lock\" command\n>   [5/5] worktree: add \"unlock\" command\n>\n> After this, worktree-move becomes ~10 patch series.\n"},{"id":"286128","messageId":"xmqqvb2lldgg.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160510141553.22967-7-pclouds@gmail.com","subject":"Re: [PATCH 7/7] worktree: simplify prefixing paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-10T23:07:11Z","receivedAt":"2016-05-10T23:07:11Z","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> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n\nThis changes semantics, doesn't it?  prefix_filename() seems to do a\nlot more than just strbuf_vadd(\"%s%s\", prefix, filename); would do.\n\nIt may be a good change (e.g. turn '\\' into '/' on Windows), but\nthis is way more than \"simplify prefixing\".  It is something else\nwhose effect needs to be explained.\n\n>  builtin/worktree.c | 4 +++-\n>  1 file changed, 3 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> index b53f802..f9dac37 100644\n> --- a/builtin/worktree.c\n> +++ b/builtin/worktree.c\n> @@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)\n>  \tif (ac < 1 || ac > 2)\n>  \t\tusage_with_options(worktree_usage, options);\n>  \n> -\tpath = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];\n> +\tpath = prefix_filename(prefix, strlen(prefix), av[0]);\n>  \tbranch = ac < 2 ? \"HEAD\" : av[1];\n>  \n>  \topts.force_new_branch = !!new_branch_force;\n> @@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n>  \n>  \tif (ac < 2)\n>  \t\tusage_with_options(worktree_usage, options);\n> +\tif (!prefix)\n> +\t\tprefix = \"\";\n>  \tif (!strcmp(av[1], \"add\"))\n>  \t\treturn add(ac - 1, av + 1, prefix);\n>  \tif (!strcmp(av[1], \"prune\"))\n"},{"id":"286129","messageId":"CACsJy8BE6U=yecxPew9GKr-M_eYYtuag440njK1R+n3OxuJ_UQ@mail.gmail.com","threadId":"42034","inReplyTo":"xmqqzirxldn9.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 05/13] worktree.c: mark current worktree","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-11T00:33:12Z","receivedAt":"2016-05-11T00:33:12Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, May 11, 2016 at 6:03 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Duy Nguyen <pclouds@gmail.com> writes:\n>\n>> On second thought, why hold patches back, lengthen the worktree-move\n>> series and make it a pain to review? I moved a few patches from\n>> worktree-move into this series and I took two other out to create\n>> nd/error-errno. So I'm going to take more patches out of it, creating\n>> two bite-sized series, to be sent shortly.\n>>\n>> The first one is purely cleanup (ok, 1/7 is not exactly cleanup)\n>>\n>>   [1/7] completion: support git-worktree\n>>   [2/7] worktree.c: rewrite mark_current_worktree() to avoid\n>>   [3/7] git-worktree.txt: keep subcommand listing in alphabetical\n>>   [4/7] worktree.c: use is_dot_or_dotdot()\n>>   [5/7] worktree.c: add clear_worktree()\n>>   [6/7] worktree: avoid 0{40}, too many zeroes, hard to read\n>>   [7/7] worktree: simplify prefixing paths\n>\n> Where are these patches designed to apply?\n>\n> It appears that this depends on something in 'next' (probably\n> nd/worktree-various-heads topic?)\n\nYes. Sorry I forgot to mention that. Though if you move 2/7 to\nnd/worktree-various-heads (and deal with some conflicts in worktree.c)\nthen it may become independent.\n-- \nDuy\n"},{"id":"286130","messageId":"CACsJy8CiUBryRqbBHGYcMR7VSBtys3SD10T79_Dq9H6JQ=NiFw@mail.gmail.com","threadId":"42034","inReplyTo":"xmqqvb2lldgg.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 7/7] worktree: simplify prefixing paths","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-11T00:46:13Z","receivedAt":"2016-05-11T00:46:13Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, May 11, 2016 at 6:07 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>\n> This changes semantics, doesn't it?  prefix_filename() seems to do a\n> lot more than just strbuf_vadd(\"%s%s\", prefix, filename); would do.\n>\n> It may be a good change (e.g. turn '\\' into '/' on Windows), but\n> this is way more than \"simplify prefixing\".  It is something else\n> whose effect needs to be explained.\n\nOn Wed, May 11, 2016 at 6:07 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>\n> This changes semantics, doesn't it?  prefix_filename() seems to do a\n> lot more than just strbuf_vadd(\"%s%s\", prefix, filename); would do.\n>\n> It may be a good change (e.g. turn '\\' into '/' on Windows), but\n> this is way more than \"simplify prefixing\".  It is something else\n> whose effect needs to be explained.\n\nI admit I forgot about Windows part in prefix_filename(). For\nnon-Windows code, it's exactly the same behavior. Maybe we should do\nthis to emphasize that prefix_filename() is no-op when pfx_len is\nzero? The same pattern is used elsewhere too (or I'm spreading it in\nmy local tree..) Unless of course if convert_slashes() is a good thing\nto always do, then I need to update my commit message (+Johannes for\nthis question).\n\ndiff --git a/abspath.c b/abspath.c\nindex 2825de8..bf454e0 100644\n--- a/abspath.c\n+++ b/abspath.c\n@@ -160,8 +160,11 @@ const char *absolute_path(const char *path)\n const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)\n {\n  static struct strbuf path = STRBUF_INIT;\n+\n+ if (!pfx_len)\n+ return arg;\n #ifndef GIT_WINDOWS_NATIVE\n- if (!pfx_len || is_absolute_path(arg))\n+ if (is_absolute_path(arg))\n  return arg;\n  strbuf_reset(&path);\n  strbuf_add(&path, pfx, pfx_len);\n\n>\n>>  builtin/worktree.c | 4 +++-\n>>  1 file changed, 3 insertions(+), 1 deletion(-)\n>>\n>> diff --git a/builtin/worktree.c b/builtin/worktree.c\n>> index b53f802..f9dac37 100644\n>> --- a/builtin/worktree.c\n>> +++ b/builtin/worktree.c\n>> @@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)\n>>       if (ac < 1 || ac > 2)\n>>               usage_with_options(worktree_usage, options);\n>>\n>> -     path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];\n>> +     path = prefix_filename(prefix, strlen(prefix), av[0]);\n>>       branch = ac < 2 ? \"HEAD\" : av[1];\n>>\n>>       opts.force_new_branch = !!new_branch_force;\n>> @@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n>>\n>>       if (ac < 2)\n>>               usage_with_options(worktree_usage, options);\n>> +     if (!prefix)\n>> +             prefix = \"\";\n>>       if (!strcmp(av[1], \"add\"))\n>>               return add(ac - 1, av + 1, prefix);\n>>       if (!strcmp(av[1], \"prune\"))\n-- \nDuy\n"},{"id":"286149","messageId":"CAPig+cTor7CaYDomyocA+GMGc9Jpye1MRfN_Mhtv1JALsn+w0Q@mail.gmail.com","threadId":"42034","inReplyTo":"1461330096-21783-13-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3 12/13] worktree.c: check whether branch is bisected in another worktree","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-11T05:45:05Z","receivedAt":"2016-05-11T05:45:05Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Apr 22, 2016 at 9:01 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Similar to the rebase case, we want to detect if \"HEAD\" in some worktree\n> is being bisected because\n>\n> 1) we do not want to checkout this branch in another worktree, after\n>    bisect is done it will want to go back to this branch\n>\n> 2) we do not want to delete the branch is either or git bisect will\n>    fail to return to the (long gone) branch\n\nI'm very far behind with my reviews and I see that this series is\nalready in 'next', so perhaps these comments are too late...\n\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh\n> @@ -263,4 +263,17 @@ test_expect_success 'check out from current worktree branch ok' '\n> +test_expect_success 'checkout a branch under bisect' '\n> +       git worktree add under-bisect &&\n> +       (\n> +               cd under-bisect &&\n> +               git bisect start &&\n> +               git bisect bad &&\n> +               git bisect good HEAD~2 &&\n> +               git worktree list | grep \"under-bisect.*detached HEAD\" &&\n> +               test_must_fail git worktree add new-bisect under-bisect &&\n\nNit: I realize that the checking 'worktree add' is sufficient, but\nit's a bit confusing to read in the commit message about how deleting\nthe branch would be bad, but then see it testing only 'add'.\n\n> +               ! test -d new-bisect\n> +       )\n> +'\n> diff --git a/worktree.c b/worktree.c\n> @@ -234,6 +234,21 @@ static int is_worktree_being_rebased(const struct worktree *wt,\n> +static int is_worktree_being_bisected(const struct worktree *wt,\n> +                                     const char *target)\n> +{\n> +       struct wt_status_state state;\n> +       int found_rebase;\n\ns/rebase/bisect/ perhaps?\n\n> +       memset(&state, 0, sizeof(state));\n> +       found_rebase = wt_status_check_bisect(wt, &state) &&\n> +               state.branch &&\n> +               starts_with(target, \"refs/heads/\") &&\n> +               !strcmp(state.branch, target + strlen(\"refs/heads/\"));\n> +       free(state.branch);\n> +       return found_rebase;\n> +}\n"},{"id":"286150","messageId":"CAPig+cQYbMq08_HuKqC6rwhEZjN+1E8YBk1775k9Xgi+GJ_SRw@mail.gmail.com","threadId":"42034","inReplyTo":"20160510141553.22967-1-pclouds@gmail.com","subject":"Re: [PATCH 1/7] completion: support git-worktree","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-11T06:12:34Z","receivedAt":"2016-05-11T06:12:34Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, May 10, 2016 at 10:15 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> completion: support git-worktree\n\nSee [1] and, especially, [2] for previous attempts...\n\n[1]: http://thread.gmane.org/gmane.comp.version-control.git/274526\n[2]: http://thread.gmane.org/gmane.comp.version-control.git/274526/focus=276333\n\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index 3402475..d3ac391 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -2595,6 +2595,29 @@ _git_whatchanged ()\n>         _git_log\n>  }\n>\n> +_git_worktree ()\n> +{\n> +       local subcommands=\"add list prune\"\n> +       local subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n> +       if [ -z \"$subcommand\" ]; then\n> +               __gitcomp \"$subcommands\"\n> +       else\n> +               case \"$subcommand,$cur\" in\n> +               add,--*)\n> +                       __gitcomp \"--detach --force\"\n> +                       ;;\n> +               list,--*)\n> +                       __gitcomp \"--porcelain\"\n> +                       ;;\n> +               prune,--*)\n> +                       __gitcomp \"--dry-run --expire --verbose\"\n> +                       ;;\n> +               *)\n> +                       ;;\n> +               esac\n> +       fi\n> +}\n> +\n>  __git_main ()\n>  {\n>         local i c=1 command __git_dir\n> --\n> 2.8.2.524.g6ff3d78\n"},{"id":"286151","messageId":"CAPig+cSB4LQn=a7=+8cdEC=_N7J=v2YyBULn-QFwgDj3+1zm6w@mail.gmail.com","threadId":"42034","inReplyTo":"20160510141553.22967-2-pclouds@gmail.com","subject":"Re: [PATCH 2/7] worktree.c: rewrite mark_current_worktree() to avoid strbuf","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-11T06:16:44Z","receivedAt":"2016-05-11T06:16:44Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, May 10, 2016 at 10:15 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> strbuf is a bit overkill for this function. What we need is call\n\ns/is call/is to call/\n\n> absolute_path() twice and make sure the second call does not destroy the\n> result of the first. One buffer allocation is enough.\n\nThe patch itself looks good.\n\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/worktree.c b/worktree.c\n> @@ -153,21 +153,19 @@ done:\n>  static void mark_current_worktree(struct worktree **worktrees)\n>  {\n> -       struct strbuf git_dir = STRBUF_INIT;\n> -       struct strbuf path = STRBUF_INIT;\n> +       char *git_dir = xstrdup(absolute_path(get_git_dir()));\n>         int i;\n>\n> -       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n>         for (i = 0; worktrees[i]; i++) {\n>                 struct worktree *wt = worktrees[i];\n> -               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n> -               wt->is_current = !fspathcmp(git_dir.buf, path.buf);\n> -               strbuf_reset(&path);\n> -               if (wt->is_current)\n> +               const char *wt_git_dir = get_worktree_git_dir(wt);\n> +\n> +               if (!fspathcmp(git_dir, absolute_path(wt_git_dir))) {\n> +                       wt->is_current = 1;\n>                         break;\n> +               }\n>         }\n> -       strbuf_release(&git_dir);\n> -       strbuf_release(&path);\n> +       free(git_dir);\n>  }\n"},{"id":"286152","messageId":"CAPig+cS-tb_hD3k2XsTioJrkzaB3FxSnrxM6JMq5-og1ADnoRg@mail.gmail.com","threadId":"42034","inReplyTo":"20160510141553.22967-5-pclouds@gmail.com","subject":"Re: [PATCH 5/7] worktree.c: add clear_worktree()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-11T06:36:37Z","receivedAt":"2016-05-11T06:36:37Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, May 10, 2016 at 10:15 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> The use case is keep some worktree and discard the rest of the worktree\n> list.\n\nSo, you're saying that rather than a client freeing the entire\nworktree list like this:\n\n    free_worktrees(worktrees);\n\nit might instead want to keep some element ('n') and free all others\nplus the list itself, like this:\n\n    struct worktree *keep = worktrees[n];\n    struct worktree **p = worktrees;\n    for (; *p; p++)\n        if (*p != keep)\n            clear_worktree(*p);\n    free(worktrees);\n\nIs that correct?\n\nIf so, then doesn't this require the client to have far too intimate\nknowledge of the internals of free_worktrees(). Or, was your idea that\nthe client would simply leak the 'worktrees' array after freeing the\nunwanted elements?\n\nIn either case, a cleaner approach might be to provide a function for\ncopying a worktree element. Perhaps:\n\n    struct worktree *copy_worktree(const struct worktree *);\n\nor something?\n\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/worktree.c b/worktree.c\n> index f4a4f38..335c58f 100644\n> --- a/worktree.c\n> +++ b/worktree.c\n> @@ -5,14 +5,22 @@\n>  #include \"dir.h\"\n>  #include \"wt-status.h\"\n>\n> +void clear_worktree(struct worktree *wt)\n> +{\n> +       if (!wt)\n> +               return;\n> +       free(wt->path);\n> +       free(wt->id);\n> +       free(wt->head_ref);\n> +       memset(wt, 0, sizeof(*wt));\n> +}\n> +\n>  void free_worktrees(struct worktree **worktrees)\n>  {\n>         int i = 0;\n>\n>         for (i = 0; worktrees[i]; i++) {\n> -               free(worktrees[i]->path);\n> -               free(worktrees[i]->id);\n> -               free(worktrees[i]->head_ref);\n> +               clear_worktree(worktrees[i]);\n>                 free(worktrees[i]);\n>         }\n>         free (worktrees);\n> diff --git a/worktree.h b/worktree.h\n> index 1394909..7430a4e 100644\n> --- a/worktree.h\n> +++ b/worktree.h\n> @@ -29,6 +29,11 @@ extern struct worktree **get_worktrees(void);\n>   */\n>  extern const char *get_worktree_git_dir(const struct worktree *wt);\n>\n> +/*\n> + * Free up the memory for worktree\n> + */\n> +extern void clear_worktree(struct worktree *);\n> +\n>  /*\n>   * Free up the memory for worktree(s)\n>   */\n> --\n> 2.8.2.524.g6ff3d78\n"},{"id":"286153","messageId":"CAPig+cSNvsJH5Ax3CD=z2VR5tUOY2CyzP-MgRHAH7CvQc51qmA@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cS-tb_hD3k2XsTioJrkzaB3FxSnrxM6JMq5-og1ADnoRg@mail.gmail.com","subject":"Re: [PATCH 5/7] worktree.c: add clear_worktree()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-11T06:42:17Z","receivedAt":"2016-05-11T06:42:17Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, May 11, 2016 at 2:36 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Tue, May 10, 2016 at 10:15 AM, Nguyễn Thái Ngọc Duy\n> <pclouds@gmail.com> wrote:\n>> The use case is keep some worktree and discard the rest of the worktree\n>> list.\n>\n> So, you're saying that rather than a client freeing the entire\n> worktree list like this:\n>\n>     free_worktrees(worktrees);\n>\n> it might instead want to keep some element ('n') and free all others\n> plus the list itself, like this:\n>\n>     struct worktree *keep = worktrees[n];\n>     struct worktree **p = worktrees;\n>     for (; *p; p++)\n>         if (*p != keep)\n>             clear_worktree(*p);\n>     free(worktrees);\n>\n> Is that correct?\n>\n> If so, then doesn't this require the client to have far too intimate\n> knowledge of the internals of free_worktrees(). Or, was your idea that\n> the client would simply leak the 'worktrees' array after freeing the\n> unwanted elements?\n>\n> In either case, a cleaner approach might be to provide a function for\n> copying a worktree element. Perhaps:\n>\n>     struct worktree *copy_worktree(const struct worktree *);\n\nOf course, you'd still need a clear_worktree() or free_worktree()\nfunction to dispose of the copy.\n"},{"id":"286462","messageId":"CAPig+cSu08BaEu22iGouM_7q6yrKbVtqNrfJsWo6aYuPAeWq0w@mail.gmail.com","threadId":"42034","inReplyTo":"20160510141729.23063-1-pclouds@gmail.com","subject":"Re: [PATCH 1/5] worktree.c: add find_worktree_by_path()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-13T16:29:59Z","receivedAt":"2016-05-13T16:29:59Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, May 10, 2016 at 10:17 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> So far we haven't needed to identify an existing worktree from command\n> line. Future commands such as lock or move will need it. There are of\n> course other options for identifying a worktree, for example by branch\n> or even by internal id. They may be added later if proved useful.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/worktree.c b/worktree.c\n> @@ -222,6 +222,22 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n> +struct worktree *find_worktree_by_path(struct worktree **list,\n> +                                      const char *path_)\n> +{\n> +       char *path = xstrdup(real_path(path_));\n> +       struct worktree *wt = NULL;\n> +\n> +       while (*list) {\n> +               wt = *list++;\n> +               if (!fspathcmp(path, real_path(wt->path)))\n> +                       break;\n> +               wt = NULL;\n> +       }\n> +       free(path);\n> +       return wt;\n> +}\n\nVery slightly shorter and perhaps idiomatic (but not at all worth a re-roll):\n\n    for (; *list; list++)\n        if (!fspathcmp(path, real_path((*list)->path)))\n            break;\n    free(path);\n    return *list;\n"},{"id":"286463","messageId":"CAPig+cRFv_dkpEBGOHE-tUPPc82YNugzXAf14rh6fSiTK_6Oyw@mail.gmail.com","threadId":"42034","inReplyTo":"20160510141729.23063-2-pclouds@gmail.com","subject":"Re: [PATCH 2/5] worktree.c: add is_main_worktree()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-13T16:32:52Z","receivedAt":"2016-05-13T16:32:52Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, May 10, 2016 at 10:17 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> Main worktree _is_ different. You can lock a linked worktree but not the\n> main one, for example. Provide an API for checking that.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/worktree.c b/worktree.c\n> @@ -238,6 +238,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,\n> +int is_main_worktree(const struct worktree *wt)\n> +{\n> +       return wt && !wt->id;\n> +}\n\nIs or will there ever be a case when it is valid for 'wt' to be NULL?\nIf not, perhaps assert() or die() if given NULL?\n"},{"id":"286467","messageId":"CAPig+cQR40u4nMEP1wz74ubA=wW5m+KiCZnpZwuWAyYvXsPHrw@mail.gmail.com","threadId":"42034","inReplyTo":"20160510141729.23063-3-pclouds@gmail.com","subject":"Re: [PATCH 3/5] worktree.c: add is_worktree_locked()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-13T16:52:06Z","receivedAt":"2016-05-13T16:52:06Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, May 10, 2016 at 10:17 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> This provides an API for checking if a worktree is locked. We need to\n> check this to avoid double locking a worktree, or try to unlock one when\n> it's not even locked.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/worktree.c b/worktree.c\n> @@ -243,6 +243,24 @@ int is_main_worktree(const struct worktree *wt)\n> +const char *is_worktree_locked(const struct worktree *wt)\n> +{\n> +       static struct strbuf sb = STRBUF_INIT;\n> +\n> +       if (!file_exists(git_common_path(\"worktrees/%s/locked\", wt->id)))\n> +               return NULL;\n\nThe git_common_path(...) invocation is textually lengthy and repeated\nthree times in this function. If you instead assign the result to a\nvariable (possibly xstrdup'ing it if needed), then the below\nstrbuf_read_file() would likely fit on one line, thus be easier to\nread.\n\n> +\n> +       strbuf_reset(&sb);\n> +       if (strbuf_read_file(&sb,\n> +                            git_common_path(\"worktrees/%s/locked\", wt->id),\n> +                            0) < 0)\n\nIt's too bad that strbuf_read_file() doesn't guarantee anything about\n'errno', such as indicating that the file did not exist, in which case\nthe !file_exists() check would not be needed, and a bit of raciness\neliminated, but that's outside the scope of this series.\n\n> +               die_errno(_(\"failed to read '%s'\"),\n> +                         git_common_path(\"worktrees/%s/locked\", wt->id));\n> +\n> +       strbuf_rtrim(&sb);\n\nSince this file is presumably human-editable (historically and at this\npoint in the series) in order to specify the lock reason, should this\nbe doing a full trim() rather than only rtrim()?\n\n> +       return sb.buf;\n> +}\n> diff --git a/worktree.h b/worktree.h\n> @@ -40,6 +40,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,\n> +/*\n> + * Return the reason string if the given worktree is locked. Return\n> + * NULL otherwise.\n> + */\n\nDoes this need to mention that the returned \"locked reason\" string is\nonly guaranteed valid until the next invocation?\n\nActually, I recall that when I suggested the idea of 'struct worktree'\nand get_worktrees() to Mike that it would be natural for the structure\nto have a 'locked' (or 'locked_reason') field, in which case the\nreason could be stored there instead of in this static strbuf. In\nfact, my idea at the time was that get_worktrees() would populate that\nfield automatically, rather than having to do so via a separate\non-demand function call as in this patch.\n\n> +extern const char *is_worktree_locked(const struct worktree *wt);\n\nI was wondering if builtin/worktree.c:prune_worktree() should be\nupdated to invoke this new function instead of consulting\n\"worktrees/<id>/locked\" manually, but I see that the entire \"prune\nworktrees\" functionality in builting/worktree.c first needs to be\nupdated to the get_worktrees() API for that to happen.\n"},{"id":"286629","messageId":"CAPig+cRMxYGx_gR9vei=6bcoXkhm2ntQSdqA3Vq9rtTzDd1RSQ@mail.gmail.com","threadId":"42034","inReplyTo":"20160510141729.23063-4-pclouds@gmail.com","subject":"Re: [PATCH 4/5] worktree: add \"lock\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-16T00:09:00Z","receivedAt":"2016-05-16T00:09:00Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, May 10, 2016 at 10:17 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\n> @@ -11,6 +11,7 @@ SYNOPSIS\n>  [verse]\n>  'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]\n>  'git worktree list' [--porcelain]\n> +'git worktree lock' [--reason <string>] <path>\n>  'git worktree prune' [-n] [-v] [--expire <expire>]\n>\n>  DESCRIPTION\n\nThis forgets to update the paragraph in DESCRIPTION which currently\ntalks about manually creating the 'locked' file. Perhaps it can be\nrewritten like this:\n\n    If a linked working tree is stored on a portable device or\n    network share which is not always mounted, you can prevent its\n    administrative files from being pruned by issuing the `git\n    worktree lock` command, optionally specifying `--reason` to\n    explain why the working tree is locked.\n\nThe DETAILS section might also need a small update. It currently says:\n\n    ...add a file named 'locked'...\n\nbut perhaps it should instead say:\n\n    ...use the `git worktree lock` command, which adds a\n    file named `locked`,...\n\n> @@ -61,6 +62,12 @@ each of the linked worktrees.  The output details include if the worktree is\n> +lock::\n> +\n> +When a worktree is locked, it cannot be pruned, moved or deleted. For\n> +example, if the worktree is on portable device that is not available\n> +when \"git worktree <command>\" is executed.\n\nThe really important reason for locking a worktree is to prevent its\nadministrative files from being pruned *automatically*. This\ndescription doesn't properly emphasize that point.\n\nAlso, bc48328 (Documentation/git-worktree: consistently use term\n\"linked working tree\", 2015-07-20) comprehensively replaced the term\n\"worktree\" with \"working tree\" and, although some new instances\nslipped in with bb9c03b (worktree: add 'list' command, 2015-10-08), we\nshould probably avoid adding more.\n\nPerhaps the description of \"lock\" can be rewritten like this:\n\n    If a working tree is on a portable device or network share which\n    is not always mounted, lock it to prevent its administrative\n    files from being pruned automatically. This also prevents it from\n    being moved or deleted. Optionally, specify a reason for the lock\n    with `--reason`.\n\nI guess preventing it from being deleted when locked is a safety measure?\n\n> @@ -110,6 +117,9 @@ OPTIONS\n>  --expire <time>::\n>         With `prune`, only expire unused working trees older than <time>.\n>\n> +--reason <string>:\n> +       An explanation why the worktree is locked.\n\nFor consistency with other option descriptions, perhaps:\n\n    With `lock`, an explanation ...\n\nthough I don't feel strongly about it.\n\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> @@ -459,6 +460,44 @@ static int list(int ac, const char **av, const char *prefix)\n> +static int lock_worktree(int ac, const char **av, const char *prefix)\n> +{\n> +       const char *reason = \"\", *old_reason;\n> +       struct option options[] = {\n> +               OPT_STRING(0, \"reason\", &reason, N_(\"string\"),\n> +                          N_(\"reason for locking\")),\n> +               OPT_END()\n> +       };\n> +       struct worktree **worktrees, *wt;\n> +       struct strbuf dst = STRBUF_INIT;\n> +\n> +       ac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n> +       if (ac != 1)\n> +               usage_with_options(worktree_usage, options);\n> +\n> +       strbuf_addstr(&dst, prefix_filename(prefix,\n> +                                           strlen(prefix),\n> +                                           av[0]));\n> +\n> +       worktrees = get_worktrees();\n> +       wt = find_worktree_by_path(worktrees, dst.buf);\n> +       if (!wt)\n> +               die(_(\"'%s' is not a working directory\"), av[0]);\n> +       if (is_main_worktree(wt))\n> +               die(_(\"'%s' is a main working directory\"), av[0]);\n> +\n> +       old_reason = is_worktree_locked(wt);\n> +       if (old_reason) {\n> +               if (*old_reason)\n> +                       die(_(\"already locked, reason: %s\"), old_reason);\n> +               die(_(\"already locked, no reason\"));\n> +       }\n\nAs a convenience, would it make sense to allow a worktree to be\nre-locked with a different reason rather than erroring out?\n\nAlso, the \"no reason\" in the error message might be confusing. Perhaps\nit would be better to say merely \"already locked\" in this case.\n\n> +       write_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n> +                  \"%s\", reason);\n> +       return 0;\n> +}\n\nThis is a tangent, but it would be nice for the \"git worktree list\"\ncommand to show whether a worktree is locked, and the reason (if\navailable), both in pretty and porcelain formats. (That was another\nreason why I suggested to Mike, back when he was adding the \"list\"\ncommand, that 'struct worktree' should have a 'locked' field and\nget_worktrees() should populate it automatically.)\n\n> diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\n> @@ -0,0 +1,34 @@\n> +#!/bin/sh\n> +\n> +test_description='test git worktree move, remove, lock and unlock'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'setup' '\n> +       test_commit init &&\n> +       git worktree add source &&\n> +       git worktree list --porcelain | grep \"^worktree\" >actual &&\n> +       cat <<-EOF >expected &&\n> +       worktree $TRASH_DIRECTORY\n> +       worktree $TRASH_DIRECTORY/source\n> +       EOF\n> +       test_cmp expected actual\n> +'\n> +\n> +test_expect_success 'lock main worktree' '\n> +       test_must_fail git worktree lock .\n> +'\n> +\n> +test_expect_success 'lock linked worktree' '\n> +       git worktree lock --reason hahaha source &&\n> +       echo hahaha >expected &&\n> +       test_cmp expected .git/worktrees/source/locked\n> +'\n\nWould it make sense to also add tests of locking from within the\nworktree being locked, and from within a worktree other than the one\nbeing locked (and other than the main one)?\n\n> +test_expect_success 'lock worktree twice' '\n> +       test_must_fail git worktree lock source &&\n> +       echo hahaha >expected &&\n> +       test_cmp expected .git/worktrees/source/locked\n> +'\n> +\n> +test_done\n"},{"id":"286631","messageId":"CAPig+cQ-weDTCnKuxapwaBp8W2T_VNuJSXPE_mRWPbjnO1krxQ@mail.gmail.com","threadId":"42034","inReplyTo":"20160510141729.23063-4-pclouds@gmail.com","subject":"Re: [PATCH 4/5] worktree: add \"lock\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-16T00:40:19Z","receivedAt":"2016-05-16T00:40:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, May 10, 2016 at 10:17 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> +static int lock_worktree(int ac, const char **av, const char *prefix)\n> +{\n> +       const char *reason = \"\", *old_reason;\n> +       struct option options[] = {\n> +               OPT_STRING(0, \"reason\", &reason, N_(\"string\"),\n> +                          N_(\"reason for locking\")),\n> +               OPT_END()\n> +       };\n> +       struct worktree **worktrees, *wt;\n> +       struct strbuf dst = STRBUF_INIT;\n> +\n> +       ac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n> +       if (ac != 1)\n> +               usage_with_options(worktree_usage, options);\n> +\n> +       strbuf_addstr(&dst, prefix_filename(prefix,\n> +                                           strlen(prefix),\n> +                                           av[0]));\n> +\n> +       worktrees = get_worktrees();\n> +       wt = find_worktree_by_path(worktrees, dst.buf);\n\nOh, I forgot to mention in my previous email that this is leaking\n'strbuf dst'. It could be released right here at its point of final\nuse.\n\n> +       if (!wt)\n> +               die(_(\"'%s' is not a working directory\"), av[0]);\n> +       if (is_main_worktree(wt))\n> +               die(_(\"'%s' is a main working directory\"), av[0]);\n> +\n> +       old_reason = is_worktree_locked(wt);\n> +       if (old_reason) {\n> +               if (*old_reason)\n> +                       die(_(\"already locked, reason: %s\"), old_reason);\n> +               die(_(\"already locked, no reason\"));\n> +       }\n> +\n> +       write_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n> +                  \"%s\", reason);\n> +       return 0;\n> +}\n"},{"id":"286632","messageId":"CAPig+cSN284FS6P_hS0FRe5+JfQ9+=XYPezEKxL18BVwwifJfg@mail.gmail.com","threadId":"42034","inReplyTo":"20160510141729.23063-5-pclouds@gmail.com","subject":"Re: [PATCH 5/5] worktree: add \"unlock\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-16T00:42:55Z","receivedAt":"2016-05-16T00:42:55Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, May 10, 2016 at 10:17 AM, Nguyễn Thái Ngọc Duy\n<pclouds@gmail.com> wrote:\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> @@ -498,6 +499,34 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n> +static int unlock_worktree(int ac, const char **av, const char *prefix)\n> +{\n> +       struct option options[] = {\n> +               OPT_END()\n> +       };\n> +       struct worktree **worktrees, *wt;\n> +       struct strbuf dst = STRBUF_INIT;\n> +\n> +       ac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n> +       if (ac != 1)\n> +               usage_with_options(worktree_usage, options);\n> +\n> +       strbuf_addstr(&dst, prefix_filename(prefix,\n> +                                           strlen(prefix),\n> +                                           av[0]));\n> +\n> +       worktrees = get_worktrees();\n> +       wt = find_worktree_by_path(worktrees, dst.buf);\n\nstrbuf_release(&dst);\n\n> +       if (!wt)\n> +               die(_(\"'%s' is not a working directory\"), av[0]);\n> +       if (is_main_worktree(wt))\n> +               die(_(\"'%s' is a main working directory\"), av[0]);\n> +       if (!is_worktree_locked(wt))\n> +               die(_(\"not locked\"));\n> +\n> +       return unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n> +}\n"},{"id":"287180","messageId":"CACsJy8Dkfm91g9ez2PYXHjPevPcQaeEXTPteEQgFExYt6SVd5A@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cS-tb_hD3k2XsTioJrkzaB3FxSnrxM6JMq5-og1ADnoRg@mail.gmail.com","subject":"Re: [PATCH 5/7] worktree.c: add clear_worktree()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T08:58:45Z","receivedAt":"2016-05-22T08:58:45Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, May 11, 2016 at 1:36 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Tue, May 10, 2016 at 10:15 AM, Nguyễn Thái Ngọc Duy\n> <pclouds@gmail.com> wrote:\n>> The use case is keep some worktree and discard the rest of the worktree\n>> list.\n>\n> So, you're saying that rather than a client freeing the entire\n> worktree list like this:\n>\n>     free_worktrees(worktrees);\n>\n> it might instead want to keep some element ('n') and free all others\n> plus the list itself, like this:\n>\n>     struct worktree *keep = worktrees[n];\n>     struct worktree **p = worktrees;\n>     for (; *p; p++)\n>         if (*p != keep)\n>             clear_worktree(*p);\n>     free(worktrees);\n>\n> Is that correct?\n>\n> If so, then doesn't this require the client to have far too intimate\n> knowledge of the internals of free_worktrees(). Or, was your idea that\n> the client would simply leak the 'worktrees' array after freeing the\n> unwanted elements?\n\nI looked back in my trees to see why I needed it in the first place.\nIt turns out my explanation was a lie. I needed it for refactoring\nbuiltin/worktree.c:add_worktree(), where I initialized struct worktree\nseparately, not from get_worktrees() [1]. Because it's not clear of\nthe exact use case for this commit, and because that commit I\nmentioned may take even longer time to be here (it adds \"git worktree\nmove --repository\", a lot more complex operation than a \"git worktree\nmove\"), I'll take this commit out of the series for now.\n\n[1] https://github.com/pclouds/git/commit/528d81ce5609b5fbbe1ba193389f7aeecc83d992\n\n> In either case, a cleaner approach might be to provide a function for\n> copying a worktree element. Perhaps:\n>\n>     struct worktree *copy_worktree(const struct worktree *);\n>\n> or something?\n-- \nDuy\n"},{"id":"287181","messageId":"20160522093356.22389-1-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141416.GA22672@lanh","subject":"[PATCH v2 0/6] nd/worktree-cleanup-post-head-protection update","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T09:33:50Z","receivedAt":"2016-05-22T09:33:50Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is mostly commit message update plus\n\n - dropping \"--force\" from the completion patch (a comment from Szeder\n   Gabor in a previous attempt of worktree completion support).\n \n - the removal of clear_worktree() patch.\n\nI keep the completion patch anyway even though two versions of it were\nposted by Eric and Szeder (and didn't get discussed much) because I\nneeded this for new commands and because I didn't aim high. It's meant\nto provide very basic completion support. Fancy stuff like ref\ncompletion can be added later by people who are more experienced in\nbash completion stuff.\n\nAnyway, interdiff\n\n-- 8< --\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex d3ac391..951a186 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2604,7 +2604,7 @@ _git_worktree ()\n \telse\n \t\tcase \"$subcommand,$cur\" in\n \t\tadd,--*)\n-\t\t\t__gitcomp \"--detach --force\"\n+\t\t\t__gitcomp \"--detach\"\n \t\t\t;;\n \t\tlist,--*)\n \t\t\t__gitcomp \"--porcelain\"\ndiff --git a/worktree.c b/worktree.c\nindex 335c58f..f4a4f38 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -5,22 +5,14 @@\n #include \"dir.h\"\n #include \"wt-status.h\"\n \n-void clear_worktree(struct worktree *wt)\n-{\n-\tif (!wt)\n-\t\treturn;\n-\tfree(wt->path);\n-\tfree(wt->id);\n-\tfree(wt->head_ref);\n-\tmemset(wt, 0, sizeof(*wt));\n-}\n-\n void free_worktrees(struct worktree **worktrees)\n {\n \tint i = 0;\n \n \tfor (i = 0; worktrees[i]; i++) {\n-\t\tclear_worktree(worktrees[i]);\n+\t\tfree(worktrees[i]->path);\n+\t\tfree(worktrees[i]->id);\n+\t\tfree(worktrees[i]->head_ref);\n \t\tfree(worktrees[i]);\n \t}\n \tfree (worktrees);\ndiff --git a/worktree.h b/worktree.h\nindex 7430a4e..1394909 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -29,11 +29,6 @@ extern struct worktree **get_worktrees(void);\n  */\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n-/*\n- * Free up the memory for worktree\n- */\n-extern void clear_worktree(struct worktree *);\n-\n /*\n  * Free up the memory for worktree(s)\n  */\n-- 8< --\n\nNguyễn Thái Ngọc Duy (6):\n  completion: support git-worktree\n  worktree.c: rewrite mark_current_worktree() to avoid strbuf\n  git-worktree.txt: keep subcommand listing in alphabetical order\n  worktree.c: use is_dot_or_dotdot()\n  worktree: avoid 0{40}, too many zeroes, hard to read\n  worktree: simplify prefixing paths\n\n Documentation/git-worktree.txt         | 10 +++++-----\n builtin/worktree.c                     | 10 ++++++----\n contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++\n worktree.c                             | 18 ++++++++----------\n 4 files changed, 42 insertions(+), 19 deletions(-)\n\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287182","messageId":"20160522093356.22389-2-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522093356.22389-1-pclouds@gmail.com","subject":"[PATCH v2 1/6] completion: support git-worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T09:33:51Z","receivedAt":"2016-05-22T09:33:51Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This adds bare-bone completion support for git-worktree. More advanced\ncompletion (e.g. ref completion in git-worktree-add) can be added later.\n\n--force completion in \"worktree add\" is left out because that option\nshould be handled with care.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++\n 1 file changed, 23 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 3402475..951a186 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2595,6 +2595,29 @@ _git_whatchanged ()\n \t_git_log\n }\n \n+_git_worktree ()\n+{\n+\tlocal subcommands=\"add list prune\"\n+\tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n+\tif [ -z \"$subcommand\" ]; then\n+\t\t__gitcomp \"$subcommands\"\n+\telse\n+\t\tcase \"$subcommand,$cur\" in\n+\t\tadd,--*)\n+\t\t\t__gitcomp \"--detach\"\n+\t\t\t;;\n+\t\tlist,--*)\n+\t\t\t__gitcomp \"--porcelain\"\n+\t\t\t;;\n+\t\tprune,--*)\n+\t\t\t__gitcomp \"--dry-run --expire --verbose\"\n+\t\t\t;;\n+\t\t*)\n+\t\t\t;;\n+\t\tesac\n+\tfi\n+}\n+\n __git_main ()\n {\n \tlocal i c=1 command __git_dir\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287183","messageId":"20160522093356.22389-3-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522093356.22389-1-pclouds@gmail.com","subject":"[PATCH v2 2/6] worktree.c: rewrite mark_current_worktree() to avoid strbuf","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T09:33:52Z","receivedAt":"2016-05-22T09:33:52Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"strbuf is a bit overkill for this function. What we need is to call\nabsolute_path() twice and make sure the second call does not destroy the\nresult of the first. One buffer allocation is enough.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 16 +++++++---------\n 1 file changed, 7 insertions(+), 9 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex 4817d60..6a11611 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -153,21 +153,19 @@ done:\n \n static void mark_current_worktree(struct worktree **worktrees)\n {\n-\tstruct strbuf git_dir = STRBUF_INIT;\n-\tstruct strbuf path = STRBUF_INIT;\n+\tchar *git_dir = xstrdup(absolute_path(get_git_dir()));\n \tint i;\n \n-\tstrbuf_addstr(&git_dir, absolute_path(get_git_dir()));\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstruct worktree *wt = worktrees[i];\n-\t\tstrbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));\n-\t\twt->is_current = !fspathcmp(git_dir.buf, path.buf);\n-\t\tstrbuf_reset(&path);\n-\t\tif (wt->is_current)\n+\t\tconst char *wt_git_dir = get_worktree_git_dir(wt);\n+\n+\t\tif (!fspathcmp(git_dir, absolute_path(wt_git_dir))) {\n+\t\t\twt->is_current = 1;\n \t\t\tbreak;\n+\t\t}\n \t}\n-\tstrbuf_release(&git_dir);\n-\tstrbuf_release(&path);\n+\tfree(git_dir);\n }\n \n struct worktree **get_worktrees(void)\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287184","messageId":"20160522093356.22389-4-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522093356.22389-1-pclouds@gmail.com","subject":"[PATCH v2 3/6] git-worktree.txt: keep subcommand listing in alphabetical order","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T09:33:53Z","receivedAt":"2016-05-22T09:33:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is probably not the best order. But it makes it no-brainer to know\nwhere to insert new commands. At some point we might want to reorder at\nleast the synopsis part again, grouping commonly use subcommands together.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt | 10 +++++-----\n builtin/worktree.c             |  2 +-\n 2 files changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex c622345..27feff6 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -10,8 +10,8 @@ SYNOPSIS\n --------\n [verse]\n 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]\n-'git worktree prune' [-n] [-v] [--expire <expire>]\n 'git worktree list' [--porcelain]\n+'git worktree prune' [-n] [-v] [--expire <expire>]\n \n DESCRIPTION\n -----------\n@@ -54,10 +54,6 @@ If `<branch>` is omitted and neither `-b` nor `-B` nor `--detached` used,\n then, as a convenience, a new branch based at HEAD is created automatically,\n as if `-b $(basename <path>)` was specified.\n \n-prune::\n-\n-Prune working tree information in $GIT_DIR/worktrees.\n-\n list::\n \n List details of each worktree.  The main worktree is listed first, followed by\n@@ -65,6 +61,10 @@ each of the linked worktrees.  The output details include if the worktree is\n bare, the revision currently checked out, and the branch currently checked out\n (or 'detached HEAD' if none).\n \n+prune::\n+\n+Prune working tree information in $GIT_DIR/worktrees.\n+\n OPTIONS\n -------\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 12c0af7..bf80111 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -13,8 +13,8 @@\n \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+\tN_(\"git worktree prune [<options>]\"),\n \tNULL\n };\n \n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287185","messageId":"20160522093356.22389-5-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522093356.22389-1-pclouds@gmail.com","subject":"[PATCH v2 4/6] worktree.c: use is_dot_or_dotdot()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T09:33:54Z","receivedAt":"2016-05-22T09:33:54Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 2 +-\n worktree.c         | 2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex bf80111..aaee0e2 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -95,7 +95,7 @@ static void prune_worktrees(void)\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\tif (is_dot_or_dotdot(d->d_name))\n \t\t\tcontinue;\n \t\tstrbuf_reset(&reason);\n \t\tif (!prune_worktree(d->d_name, &reason))\ndiff --git a/worktree.c b/worktree.c\nindex 6a11611..f4a4f38 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -187,7 +187,7 @@ struct worktree **get_worktrees(void)\n \tif (dir) {\n \t\twhile ((d = readdir(dir)) != NULL) {\n \t\t\tstruct worktree *linked = NULL;\n-\t\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\tif (is_dot_or_dotdot(d->d_name))\n \t\t\t\tcontinue;\n \n \t\t\tif ((linked = get_linked_worktree(d->d_name))) {\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287186","messageId":"20160522093356.22389-6-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522093356.22389-1-pclouds@gmail.com","subject":"[PATCH v2 5/6] worktree: avoid 0{40}, too many zeroes, hard to read","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T09:33:55Z","receivedAt":"2016-05-22T09:33:55Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex aaee0e2..b53f802 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -262,7 +262,7 @@ static int add_worktree(const char *path, const char *refname,\n \t */\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n-\twrite_file(sb.buf, \"0000000000000000000000000000000000000000\");\n+\twrite_file(sb.buf, sha1_to_hex(null_sha1));\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n \twrite_file(sb.buf, \"../..\");\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287187","messageId":"20160522093356.22389-7-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522093356.22389-1-pclouds@gmail.com","subject":"[PATCH v2 6/6] worktree: simplify prefixing paths","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T09:33:56Z","receivedAt":"2016-05-22T09:33:56Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This also makes slash conversion always happen on Windows (a side effect\nof prefix_filename). Which is a good thing.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex b53f802..f9dac37 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)\n \tif (ac < 1 || ac > 2)\n \t\tusage_with_options(worktree_usage, options);\n \n-\tpath = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];\n+\tpath = prefix_filename(prefix, strlen(prefix), av[0]);\n \tbranch = ac < 2 ? \"HEAD\" : av[1];\n \n \topts.force_new_branch = !!new_branch_force;\n@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \n \tif (ac < 2)\n \t\tusage_with_options(worktree_usage, options);\n+\tif (!prefix)\n+\t\tprefix = \"\";\n \tif (!strcmp(av[1], \"add\"))\n \t\treturn add(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"prune\"))\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287188","messageId":"CACsJy8ChM99n6skQCv-GmFiod19mnwwH4j-6R+cfZSiVFAxjgA@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cQR40u4nMEP1wz74ubA=wW5m+KiCZnpZwuWAyYvXsPHrw@mail.gmail.com","subject":"Re: [PATCH 3/5] worktree.c: add is_worktree_locked()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T09:53:51Z","receivedAt":"2016-05-22T09:53:51Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, May 13, 2016 at 11:52 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> Actually, I recall that when I suggested the idea of 'struct worktree'\n> and get_worktrees() to Mike that it would be natural for the structure\n> to have a 'locked' (or 'locked_reason') field, in which case the\n> reason could be stored there instead of in this static strbuf. In\n> fact, my idea at the time was that get_worktrees() would populate that\n> field automatically, rather than having to do so via a separate\n> on-demand function call as in this patch.\n\nI'm keeping this as a separate function for now. I don't like\nget_worktrees() doing extra work unless it has to. We soon will see\nthe complete picture of \"git worktree\" then we can merge it back to\nget_worktrees() if it turns out checking \"locked\" is the common\noperation. is_worktree_locked() then may become a thin wrapper to\naccess this \"locked\" field.\n\n>> +extern const char *is_worktree_locked(const struct worktree *wt);\n>\n> I was wondering if builtin/worktree.c:prune_worktree() should be\n> updated to invoke this new function instead of consulting\n> \"worktrees/<id>/locked\" manually, but I see that the entire \"prune\n> worktrees\" functionality in builting/worktree.c first needs to be\n> updated to the get_worktrees() API for that to happen.\n\nI thought about updating prune too. But it is in a bit special\nsituation where it may need to consider invalid (or partly invalid)\nworktrees as well. So far worktree api is about valid worktrees only\nif I'm not mistaken and we probably should keep it that way, otherwise\nall callers may need to check \"is this worktree valid\" all over the\nplace.\n-- \nDuy\n"},{"id":"287190","messageId":"CACsJy8CpsW+Jp=A8swDzR0+udGq8H-ampztyZwyXiK2mbcjqPA@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cRMxYGx_gR9vei=6bcoXkhm2ntQSdqA3Vq9rtTzDd1RSQ@mail.gmail.com","subject":"Re: [PATCH 4/5] worktree: add \"lock\" command","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T10:31:08Z","receivedAt":"2016-05-22T10:31:08Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"(the answer to rest of your questions is \"yes you're right, will fix\"\nor something along that line so I will not quote them here)\n\nOn Mon, May 16, 2016 at 7:09 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> +       old_reason = is_worktree_locked(wt);\n>> +       if (old_reason) {\n>> +               if (*old_reason)\n>> +                       die(_(\"already locked, reason: %s\"), old_reason);\n>> +               die(_(\"already locked, no reason\"));\n>> +       }\n>\n> As a convenience, would it make sense to allow a worktree to be\n> re-locked with a different reason rather than erroring out?\n\nFor now I think the user can just unlock then relock, after examining\nthe previous reason and maybe incorporating it to the new reason. Yes\nthere is a race but for these operations I don't think it really\nmatters. If it's done often enough, we can lift this restriction.\n\n>> +       write_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n>> +                  \"%s\", reason);\n>> +       return 0;\n>> +}\n>\n> This is a tangent, but it would be nice for the \"git worktree list\"\n> command to show whether a worktree is locked, and the reason (if\n> available), both in pretty and porcelain formats. (That was another\n> reason why I suggested to Mike, back when he was adding the \"list\"\n> command, that 'struct worktree' should have a 'locked' field and\n> get_worktrees() should populate it automatically.)\n\nYes. And a good reason for get_worktrees() to read lock status\nautomatically too. I will not do it right away though or at least\nuntil after I'm done with move/remove and the shared config file\nproblem.\n-- \nDuy\n"},{"id":"287191","messageId":"20160522104341.656-1-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160510141416.GA22672@lanh","subject":"[PATCH v2 0/5] worktree lock/unlock","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T10:43:36Z","receivedAt":"2016-05-22T10:43:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This should address all of Eric's comments (thanks!). An extra change\nI made is free_worktrees() at the end of {,un}lock_worktree() to avoid\nleaking. This series depends on nd/worktree-cleanup-post-head-protection.\n\nInterdiff\n-- 8< --\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 9ac1129..e0f2605 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -40,9 +40,8 @@ section \"DETAILS\" for more information.\n \n If a linked working tree is stored on a portable device or network share\n which is not always mounted, you can prevent its administrative files from\n-being pruned by creating a file named 'locked' alongside the other\n-administrative files, optionally containing a plain text reason that\n-pruning should be suppressed. See section \"DETAILS\" for more information.\n+being pruned by issuing the `git worktree lock` command, optionally\n+specifying `--reason` to explain why the working tree is locked.\n \n COMMANDS\n --------\n@@ -65,9 +64,11 @@ bare, the revision currently checked out, and the branch currently checked out\n \n lock::\n \n-When a worktree is locked, it cannot be pruned, moved or deleted. For\n-example, if the worktree is on portable device that is not available\n-when \"git worktree <command>\" is executed.\n+If a working tree is on a portable device or network share which\n+is not always mounted, lock it to prevent its administrative\n+files from being pruned automatically. This also prevents it from\n+being moved or deleted. Optionally, specify a reason for the lock\n+with `--reason`.\n \n prune::\n \n@@ -123,7 +124,7 @@ OPTIONS\n \tWith `prune`, only expire unused working trees older than <time>.\n \n --reason <string>:\n-\tAn explanation why the worktree is locked.\n+\tWith `lock`, an explanation why the worktree is locked.\n \n DETAILS\n -------\n@@ -165,7 +166,8 @@ instead.\n \n To prevent a $GIT_DIR/worktrees entry from being pruned (which\n can be useful in some situations, such as when the\n-entry's working tree is stored on a portable device), add a file named\n+entry's working tree is stored on a portable device), use the\n+`git worktree lock` comamnd, which adds a file named\n 'locked' to the entry's directory. The file contains the reason in\n plain text. For example, if a linked working tree's `.git` file points\n to `/path/main/.git/worktrees/test-next` then a file named\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 53e5f5a..da9f771 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -482,6 +482,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n \n \tworktrees = get_worktrees();\n \twt = find_worktree_by_path(worktrees, dst.buf);\n+\tstrbuf_release(&dst);\n \tif (!wt)\n \t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n \tif (is_main_worktree(wt))\n@@ -491,11 +492,12 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n \tif (old_reason) {\n \t\tif (*old_reason)\n \t\t\tdie(_(\"already locked, reason: %s\"), old_reason);\n-\t\tdie(_(\"already locked, no reason\"));\n+\t\tdie(_(\"already locked\"));\n \t}\n \n \twrite_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n \t\t   \"%s\", reason);\n+\tfree_worktrees(worktrees);\n \treturn 0;\n }\n \n@@ -506,6 +508,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)\n \t};\n \tstruct worktree **worktrees, *wt;\n \tstruct strbuf dst = STRBUF_INIT;\n+\tint ret;\n \n \tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n \tif (ac != 1)\n@@ -517,14 +520,16 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)\n \n \tworktrees = get_worktrees();\n \twt = find_worktree_by_path(worktrees, dst.buf);\n+\tstrbuf_release(&dst);\n \tif (!wt)\n \t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n \tif (is_main_worktree(wt))\n \t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n \tif (!is_worktree_locked(wt))\n \t\tdie(_(\"not locked\"));\n-\n-\treturn unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n+\tret = unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n+\tfree_worktrees(worktrees);\n+\treturn ret;\n }\n \n int cmd_worktree(int ac, const char **av, const char *prefix)\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex f4b2816..1927537 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -25,12 +25,32 @@ test_expect_success 'lock linked worktree' '\n \ttest_cmp expected .git/worktrees/source/locked\n '\n \n+test_expect_success 'lock linked worktree from another worktree' '\n+\trm .git/worktrees/source/locked &&\n+\tgit worktree add elsewhere &&\n+\t(\n+\t\tcd elsewhere &&\n+\t\tgit worktree lock --reason hahaha ../source\n+\t) &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n test_expect_success 'lock worktree twice' '\n \ttest_must_fail git worktree lock source &&\n \techo hahaha >expected &&\n \ttest_cmp expected .git/worktrees/source/locked\n '\n \n+test_expect_success 'lock worktree twice (from the locked worktree)' '\n+\t(\n+\t\tcd source &&\n+\t\ttest_must_fail git worktree lock .\n+\t) &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n test_expect_success 'unlock main worktree' '\n \ttest_must_fail git worktree unlock .\n '\ndiff --git a/worktree.c b/worktree.c\nindex a9cfbb3..cb30af3 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -218,38 +218,37 @@ struct worktree *find_worktree_by_path(struct worktree **list,\n \t\t\t\t       const char *path_)\n {\n \tchar *path = xstrdup(real_path(path_));\n-\tstruct worktree *wt = NULL;\n \n-\twhile (*list) {\n-\t\twt = *list++;\n-\t\tif (!fspathcmp(path, real_path(wt->path)))\n+\tfor (; *list; list++)\n+\t\tif (!fspathcmp(path, real_path((*list)->path)))\n \t\t\tbreak;\n-\t\twt = NULL;\n-\t}\n \tfree(path);\n-\treturn wt;\n+\treturn *list;\n }\n \n int is_main_worktree(const struct worktree *wt)\n {\n-\treturn wt && !wt->id;\n+\treturn !wt->id;\n }\n \n const char *is_worktree_locked(const struct worktree *wt)\n {\n \tstatic struct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n+\n+\tstrbuf_git_common_path(&path, \"worktrees/%s/locked\", wt->id);\n \n-\tif (!file_exists(git_common_path(\"worktrees/%s/locked\", wt->id)))\n+\tif (!file_exists(path.buf)) {\n+\t\tstrbuf_release(&path);\n \t\treturn NULL;\n+\t}\n \n \tstrbuf_reset(&sb);\n-\tif (strbuf_read_file(&sb,\n-\t\t\t     git_common_path(\"worktrees/%s/locked\", wt->id),\n-\t\t\t     0) < 0)\n-\t\tdie_errno(_(\"failed to read '%s'\"),\n-\t\t\t  git_common_path(\"worktrees/%s/locked\", wt->id));\n+\tif (strbuf_read_file(&sb, path.buf, 0) < 0)\n+\t\tdie_errno(_(\"failed to read '%s'\"), path.buf);\n+\tstrbuf_release(&path);\n \n-\tstrbuf_rtrim(&sb);\n+\tstrbuf_trim(&sb);\n \treturn sb.buf;\n }\n \ndiff --git a/worktree.h b/worktree.h\nindex 19a6790..3a780c2 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -42,7 +42,7 @@ extern int is_main_worktree(const struct worktree *wt);\n \n /*\n  * Return the reason string if the given worktree is locked. Return\n- * NULL otherwise.\n+ * NULL otherwise. Return value is only valid until the next invocation.\n  */\n extern const char *is_worktree_locked(const struct worktree *wt);\n-- 8< --\n\nNguyễn Thái Ngọc Duy (5):\n  worktree.c: add find_worktree_by_path()\n  worktree.c: add is_main_worktree()\n  worktree.c: add is_worktree_locked()\n  worktree: add \"lock\" command\n  worktree: add \"unlock\" command\n\n Documentation/git-worktree.txt         | 27 +++++++++---\n builtin/worktree.c                     | 77 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 ++-\n t/t2028-worktree-move.sh (new +x)      | 68 ++++++++++++++++++++++++++++++\n worktree.c                             | 38 +++++++++++++++++\n worktree.h                             | 17 ++++++++\n 6 files changed, 225 insertions(+), 7 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287192","messageId":"20160522104341.656-2-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-1-pclouds@gmail.com","subject":"[PATCH v2 1/5] worktree.c: add find_worktree_by_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T10:43:37Z","receivedAt":"2016-05-22T10:43:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"So far we haven't needed to identify an existing worktree from command\nline. Future commands such as lock or move will need it. There are of\ncourse other options for identifying a worktree, for example by branch\nor even by internal id. They may be added later if proved useful.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 12 ++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 18 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex f4a4f38..c5d45a6 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -214,6 +214,18 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n+struct worktree *find_worktree_by_path(struct worktree **list,\n+\t\t\t\t       const char *path_)\n+{\n+\tchar *path = xstrdup(real_path(path_));\n+\n+\tfor (; *list; list++)\n+\t\tif (!fspathcmp(path, real_path((*list)->path)))\n+\t\t\tbreak;\n+\tfree(path);\n+\treturn *list;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 1394909..91627fd 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -29,6 +29,12 @@ extern struct worktree **get_worktrees(void);\n  */\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n+/*\n+ * Search a worktree by its path. Paths are normalized internally.\n+ */\n+extern struct worktree *find_worktree_by_path(struct worktree **list,\n+\t\t\t\t\t      const char *path_);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287193","messageId":"20160522104341.656-3-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-1-pclouds@gmail.com","subject":"[PATCH v2 2/5] worktree.c: add is_main_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T10:43:38Z","receivedAt":"2016-05-22T10:43:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Main worktree _is_ different. You can lock a linked worktree but not the\nmain one, for example. Provide an API for checking that.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 5 +++++\n worktree.h | 5 +++++\n 2 files changed, 10 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex c5d45a6..6b3015b 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -226,6 +226,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,\n \treturn *list;\n }\n \n+int is_main_worktree(const struct worktree *wt)\n+{\n+\treturn !wt->id;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 91627fd..39f1aa2 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -35,6 +35,11 @@ extern const char *get_worktree_git_dir(const struct worktree *wt);\n extern struct worktree *find_worktree_by_path(struct worktree **list,\n \t\t\t\t\t      const char *path_);\n \n+/*\n+ * Return true if the given worktree is the main one.\n+ */\n+extern int is_main_worktree(const struct worktree *wt);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287194","messageId":"20160522104341.656-4-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-1-pclouds@gmail.com","subject":"[PATCH v2 3/5] worktree.c: add is_worktree_locked()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T10:43:39Z","receivedAt":"2016-05-22T10:43:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This provides an API for checking if a worktree is locked. We need to\ncheck this to avoid double locking a worktree, or try to unlock one when\nit's not even locked.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 21 +++++++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 27 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 6b3015b..cb30af3 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -231,6 +231,27 @@ int is_main_worktree(const struct worktree *wt)\n \treturn !wt->id;\n }\n \n+const char *is_worktree_locked(const struct worktree *wt)\n+{\n+\tstatic struct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n+\n+\tstrbuf_git_common_path(&path, \"worktrees/%s/locked\", wt->id);\n+\n+\tif (!file_exists(path.buf)) {\n+\t\tstrbuf_release(&path);\n+\t\treturn NULL;\n+\t}\n+\n+\tstrbuf_reset(&sb);\n+\tif (strbuf_read_file(&sb, path.buf, 0) < 0)\n+\t\tdie_errno(_(\"failed to read '%s'\"), path.buf);\n+\tstrbuf_release(&path);\n+\n+\tstrbuf_trim(&sb);\n+\treturn sb.buf;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 39f1aa2..3a780c2 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -40,6 +40,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,\n  */\n extern int is_main_worktree(const struct worktree *wt);\n \n+/*\n+ * Return the reason string if the given worktree is locked. Return\n+ * NULL otherwise. Return value is only valid until the next invocation.\n+ */\n+extern const char *is_worktree_locked(const struct worktree *wt);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287195","messageId":"20160522104341.656-5-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-1-pclouds@gmail.com","subject":"[PATCH v2 4/5] worktree: add \"lock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T10:43:40Z","receivedAt":"2016-05-22T10:43:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Helped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         | 22 ++++++++++----\n builtin/worktree.c                     | 43 +++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 +++-\n t/t2028-worktree-move.sh (new +x)      | 54 ++++++++++++++++++++++++++++++++++\n 4 files changed, 117 insertions(+), 7 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 27feff6..004d770 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] [--checkout] [-b <new-branch>] <path> [<branch>]\n 'git worktree list' [--porcelain]\n+'git worktree lock' [--reason <string>] <path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n \n DESCRIPTION\n@@ -38,9 +39,8 @@ section \"DETAILS\" for more information.\n \n If a linked working tree is stored on a portable device or network share\n which is not always mounted, you can prevent its administrative files from\n-being pruned by creating a file named 'locked' alongside the other\n-administrative files, optionally containing a plain text reason that\n-pruning should be suppressed. See section \"DETAILS\" for more information.\n+being pruned by issuing the `git worktree lock` command, optionally\n+specifying `--reason` to explain why the working tree is locked.\n \n COMMANDS\n --------\n@@ -61,6 +61,14 @@ each of the linked worktrees.  The output details include if the worktree is\n bare, the revision currently checked out, and the branch currently checked out\n (or 'detached HEAD' if none).\n \n+lock::\n+\n+If a working tree is on a portable device or network share which\n+is not always mounted, lock it to prevent its administrative\n+files from being pruned automatically. This also prevents it from\n+being moved or deleted. Optionally, specify a reason for the lock\n+with `--reason`.\n+\n prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n@@ -110,6 +118,9 @@ OPTIONS\n --expire <time>::\n \tWith `prune`, only expire unused working trees older than <time>.\n \n+--reason <string>:\n+\tWith `lock`, an explanation why the worktree is locked.\n+\n DETAILS\n -------\n Each linked working tree has a private sub-directory in the repository's\n@@ -150,7 +161,8 @@ instead.\n \n To prevent a $GIT_DIR/worktrees entry from being pruned (which\n can be useful in some situations, such as when the\n-entry's working tree is stored on a portable device), add a file named\n+entry's working tree is stored on a portable device), use the\n+`git worktree lock` comamnd, which adds a file named\n 'locked' to the entry's directory. The file contains the reason in\n plain text. For example, if a linked working tree's `.git` file points\n to `/path/main/.git/worktrees/test-next` then a file named\n@@ -226,8 +238,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-- `lock` to prevent automatic pruning of administrative files (for instance,\n-  for a working tree on a portable device)\n \n GIT\n ---\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex f9dac37..9008dcd 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -14,6 +14,7 @@\n static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> [<branch>]\"),\n \tN_(\"git worktree list [<options>]\"),\n+\tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n \tNULL\n };\n@@ -459,6 +460,46 @@ static int list(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int lock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tconst char *reason = \"\", *old_reason;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"reason\", &reason, N_(\"string\"),\n+\t\t\t   N_(\"reason for locking\")),\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tstrbuf_addstr(&dst, prefix_filename(prefix,\n+\t\t\t\t\t    strlen(prefix),\n+\t\t\t\t\t    av[0]));\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree_by_path(worktrees, dst.buf);\n+\tstrbuf_release(&dst);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\n+\told_reason = is_worktree_locked(wt);\n+\tif (old_reason) {\n+\t\tif (*old_reason)\n+\t\t\tdie(_(\"already locked, reason: %s\"), old_reason);\n+\t\tdie(_(\"already locked\"));\n+\t}\n+\n+\twrite_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n+\t\t   \"%s\", reason);\n+\tfree_worktrees(worktrees);\n+\treturn 0;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -475,5 +516,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\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+\tif (!strcmp(av[1], \"lock\"))\n+\t\treturn lock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 951a186..f88727d 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2597,7 +2597,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list prune\"\n+\tlocal subcommands=\"add list lock prune\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\n@@ -2609,6 +2609,9 @@ _git_worktree ()\n \t\tlist,--*)\n \t\t\t__gitcomp \"--porcelain\"\n \t\t\t;;\n+\t\tlock,--*)\n+\t\t\t__gitcomp \"--reason\"\n+\t\t\t;;\n \t\tprune,--*)\n \t\t\t__gitcomp \"--dry-run --expire --verbose\"\n \t\t\t;;\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nnew file mode 100755\nindex 0000000..aeac848\n--- /dev/null\n+++ b/t/t2028-worktree-move.sh\n@@ -0,0 +1,54 @@\n+#!/bin/sh\n+\n+test_description='test git worktree move, remove, lock and unlock'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit init &&\n+\tgit worktree add source &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/source\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'lock main worktree' '\n+\ttest_must_fail git worktree lock .\n+'\n+\n+test_expect_success 'lock linked worktree' '\n+\tgit worktree lock --reason hahaha source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock linked worktree from another worktree' '\n+\trm .git/worktrees/source/locked &&\n+\tgit worktree add elsewhere &&\n+\t(\n+\t\tcd elsewhere &&\n+\t\tgit worktree lock --reason hahaha ../source\n+\t) &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock worktree twice' '\n+\ttest_must_fail git worktree lock source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock worktree twice (from the locked worktree)' '\n+\t(\n+\t\tcd source &&\n+\t\ttest_must_fail git worktree lock .\n+\t) &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_done\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287196","messageId":"20160522104341.656-6-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-1-pclouds@gmail.com","subject":"[PATCH v2 5/5] worktree: add \"unlock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-22T10:43:41Z","receivedAt":"2016-05-22T10:43:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         |  5 +++++\n builtin/worktree.c                     | 34 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  2 +-\n t/t2028-worktree-move.sh               | 14 ++++++++++++++\n 4 files changed, 54 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 004d770..e0f2605 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n 'git worktree list' [--porcelain]\n 'git worktree lock' [--reason <string>] <path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n+'git worktree unlock' <path>\n \n DESCRIPTION\n -----------\n@@ -73,6 +74,10 @@ prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n \n+unlock::\n+\n+Unlock a worktree, allowing it to be pruned, moved or deleted.\n+\n OPTIONS\n -------\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 9008dcd..da9f771 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {\n \tN_(\"git worktree list [<options>]\"),\n \tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n+\tN_(\"git worktree unlock <path>\"),\n \tNULL\n };\n \n@@ -500,6 +501,37 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int unlock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\tint ret;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tstrbuf_addstr(&dst, prefix_filename(prefix,\n+\t\t\t\t\t    strlen(prefix),\n+\t\t\t\t\t    av[0]));\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree_by_path(worktrees, dst.buf);\n+\tstrbuf_release(&dst);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\tif (!is_worktree_locked(wt))\n+\t\tdie(_(\"not locked\"));\n+\tret = unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n+\tfree_worktrees(worktrees);\n+\treturn ret;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -518,5 +550,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn list(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"lock\"))\n \t\treturn lock_worktree(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"unlock\"))\n+\t\treturn unlock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex f88727d..0e3841d 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2597,7 +2597,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list lock prune\"\n+\tlocal subcommands=\"add list lock prune unlock\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex aeac848..1927537 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -51,4 +51,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' '\n \ttest_cmp expected .git/worktrees/source/locked\n '\n \n+test_expect_success 'unlock main worktree' '\n+\ttest_must_fail git worktree unlock .\n+'\n+\n+test_expect_success 'unlock linked worktree' '\n+\tgit worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'unlock worktree twice' '\n+\ttest_must_fail git worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n test_done\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287214","messageId":"CAPig+cSpoi0eMKQPqKG6+skc87dgDLZiLJZJLUk-iyd13P3E3g@mail.gmail.com","threadId":"42034","inReplyTo":"20160522093356.22389-7-pclouds@gmail.com","subject":"Re: [PATCH v2 6/6] worktree: simplify prefixing paths","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-22T23:32:19Z","receivedAt":"2016-05-22T23:32:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 22, 2016 at 5:33 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> This also makes slash conversion always happen on Windows (a side effect\n> of prefix_filename). Which is a good thing.\n\nSelling this patch as a mere simplification seems misguided\n(especially as it's subjective whether this is indeed simpler). I'd\nfind it more easy to buy this change if it was described primarily as\nbenefiting Windows, in the same vein as[1].\n\nMore below...\n\n[1]: http://git.661346.n2.nabble.com/PATCH-Do-not-skip-prefix-filename-when-there-is-no-prefix-tp7657038.html\n\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> @@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)\n>         if (ac < 1 || ac > 2)\n>                 usage_with_options(worktree_usage, options);\n>\n> -       path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];\n> +       path = prefix_filename(prefix, strlen(prefix), av[0]);\n>         branch = ac < 2 ? \"HEAD\" : av[1];\n>\n>         opts.force_new_branch = !!new_branch_force;\n> @@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n>\n>         if (ac < 2)\n>                 usage_with_options(worktree_usage, options);\n> +       if (!prefix)\n> +               prefix = \"\";\n\nConsidering that all the other existing git-worktree subcommands\nalready handle NULL prefix acceptably, it makes me a bit uncomfortable\nthat this \"fix\", which could be local to add(), leaks into all the\nother subcommands, thus making the assumption that they (or other new\nsubcommands) will never care about the distinction between NULL and\n\"\".\n\nNot a big objection; just a minor niggle, probably not worth a re-roll.\n\n>         if (!strcmp(av[1], \"add\"))\n>                 return add(ac - 1, av + 1, prefix);\n>         if (!strcmp(av[1], \"prune\"))\n"},{"id":"287219","messageId":"CAPig+cQ2NRO4yaFkcGmUpY3TZcWkdg-vu6d7Fq7JgHzYSkcRgg@mail.gmail.com","threadId":"42034","inReplyTo":"20160522093356.22389-1-pclouds@gmail.com","subject":"Re: [PATCH v2 0/6] nd/worktree-cleanup-post-head-protection update","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-23T01:09:23Z","receivedAt":"2016-05-23T01:09:23Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 22, 2016 at 5:33 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> This is mostly commit message update plus\n>\n>  - dropping \"--force\" from the completion patch (a comment from Szeder\n>    Gabor in a previous attempt of worktree completion support).\n>\n>  - the removal of clear_worktree() patch.\n>\n> I keep the completion patch anyway even though two versions of it were\n> posted by Eric and Szeder (and didn't get discussed much) because I\n> needed this for new commands and because I didn't aim high. It's meant\n> to provide very basic completion support. Fancy stuff like ref\n> completion can be added later by people who are more experienced in\n> bash completion stuff.\n\nThanks. Dropping the clear_worktree() patch does indeed seem like a\ngood idea. I've re-read the entire series, and aside from my minor\ncomments on 6/6, everything looks fine and the series is:\n\n    Reviewed by: Eric Sunshine <sunshine@sunshineco.com>\n"},{"id":"287221","messageId":"CAPig+cQTWK_yR4hCdMDj35Rwx6tAUXdfzrOkALJU3j6qCBxoew@mail.gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-2-pclouds@gmail.com","subject":"Re: [PATCH v2 1/5] worktree.c: add find_worktree_by_path()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-23T01:41:11Z","receivedAt":"2016-05-23T01:41:11Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 22, 2016 at 6:43 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> So far we haven't needed to identify an existing worktree from command\n> line. Future commands such as lock or move will need it. There are of\n> course other options for identifying a worktree, for example by branch\n> or even by internal id. They may be added later if proved useful.\n>\n> Helped-by: Eric Sunshine <sunshine@sunshineco.com>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/worktree.h b/worktree.h\n> @@ -29,6 +29,12 @@ extern struct worktree **get_worktrees(void);\n> +/*\n> + * Search a worktree by its path. Paths are normalized internally.\n> + */\n> +extern struct worktree *find_worktree_by_path(struct worktree **list,\n> +                                             const char *path_);\n> +\n\nSorry for not noticing last time, but I'd probably have named this\nargument 'path' rather than 'path_' here in the header.\n\nNot worth a re-roll, of course.\n"},{"id":"287223","messageId":"CAPig+cTVbOy5S57W2x0C6ngmECJz8mjDVtA+NPde5-Ci2rOt6g@mail.gmail.com","threadId":"42034","inReplyTo":"CACsJy8ChM99n6skQCv-GmFiod19mnwwH4j-6R+cfZSiVFAxjgA@mail.gmail.com","subject":"Re: [PATCH 3/5] worktree.c: add is_worktree_locked()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-23T02:04:29Z","receivedAt":"2016-05-23T02:04:29Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 22, 2016 at 5:53 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Fri, May 13, 2016 at 11:52 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> Actually, I recall that when I suggested the idea of 'struct worktree'\n>> and get_worktrees() to Mike that it would be natural for the structure\n>> to have a 'locked' (or 'locked_reason') field, in which case the\n>> reason could be stored there instead of in this static strbuf. In\n>> fact, my idea at the time was that get_worktrees() would populate that\n>> field automatically, rather than having to do so via a separate\n>> on-demand function call as in this patch.\n>\n> I'm keeping this as a separate function for now. I don't like\n> get_worktrees() doing extra work unless it has to. We soon will see\n> the complete picture of \"git worktree\" then we can merge it back to\n> get_worktrees() if it turns out checking \"locked\" is the common\n> operation. is_worktree_locked() then may become a thin wrapper to\n> access this \"locked\" field.\n\nis_worktree_locked() could also just go away since the 'struct\nworktree::locked' field would be non-NULL for locked, else NULL.\n\n>>> +extern const char *is_worktree_locked(const struct worktree *wt);\n>>\n>> I was wondering if builtin/worktree.c:prune_worktree() should be\n>> updated to invoke this new function instead of consulting\n>> \"worktrees/<id>/locked\" manually, but I see that the entire \"prune\n>> worktrees\" functionality in builting/worktree.c first needs to be\n>> updated to the get_worktrees() API for that to happen.\n>\n> I thought about updating prune too. But it is in a bit special\n> situation where it may need to consider invalid (or partly invalid)\n> worktrees as well. So far worktree api is about valid worktrees only\n> if I'm not mistaken and we probably should keep it that way, otherwise\n> all callers may need to check \"is this worktree valid\" all over the\n> place.\n\nYes and no. In addition to suggesting to Mike that 'struct worktree'\nshould have a 'locked' field, I also suggested a 'prunable' field\nwhich would indicate whether a worktree was a candidate for pruning.\nIn fact, the field would be a 'const char *' where non-NULL would mean\nprunable and give a reason (one of the reasons already determined by\nbuiltin/worktree.c:prune_worktree()). Alternately it could be an enum.\nLike the 'locked' (or 'lock_reason') field, 'prunable' (or\n'prune_reason') is likely the sort of information which should be\npresented by \"git worktree list\". And, as with 'locked', I think it\nmakes sense for the 'prunable' field to be populated automatically by\nget_worktrees().\n\nAs for your concern about clients having to take care with valid vs.\ninvalid worktrees, get_worktrees() could be updated to return all\nworktrees or only valid worktrees (for some definition of \"valid\").\n\"git worktree list\" is an example of a client which would want to see\nall worktrees assuming its updated to show 'locked' and 'prunable'\nstatus.\n"},{"id":"287224","messageId":"CAPig+cRnt4ELACEitEkiZxbVT9Uk8hzdj0Ts7cwZwh2qPoa1cA@mail.gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-2-pclouds@gmail.com","subject":"Re: [PATCH v2 1/5] worktree.c: add find_worktree_by_path()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-23T04:11:39Z","receivedAt":"2016-05-23T04:11:39Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 22, 2016 at 6:43 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> So far we haven't needed to identify an existing worktree from command\n> line. Future commands such as lock or move will need it. There are of\n> course other options for identifying a worktree, for example by branch\n> or even by internal id. They may be added later if proved useful.\n\nBeyond the above methods for specifying a worktree, [1] adds\n$(basename $path) as a possibility if not ambiguous. Excerpt:\n\n    For git-worktree commands such as \"lock\", \"mv\", \"remove\", it\n    likely will be nice to allow people to specify the linked\n    worktree not only by path, but also by tag, and possibly even by\n    $(basename $path) if not ambiguous.\n\n[1]: http://article.gmane.org/gmane.comp.version-control.git/275528\n\n> Helped-by: Eric Sunshine <sunshine@sunshineco.com>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n"},{"id":"287225","messageId":"CAPig+cRx+HjDMhqSN+aff7Qrvb9pgninuw4ZMxC-pzE=2UJmLA@mail.gmail.com","threadId":"42034","inReplyTo":"CACsJy8CpsW+Jp=A8swDzR0+udGq8H-ampztyZwyXiK2mbcjqPA@mail.gmail.com","subject":"Re: [PATCH 4/5] worktree: add \"lock\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-23T04:21:45Z","receivedAt":"2016-05-23T04:21:45Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 22, 2016 at 6:31 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Mon, May 16, 2016 at 7:09 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>>> +       old_reason = is_worktree_locked(wt);\n>>> +       if (old_reason) {\n>>> +               if (*old_reason)\n>>> +                       die(_(\"already locked, reason: %s\"), old_reason);\n>>> +               die(_(\"already locked, no reason\"));\n>>> +       }\n>>\n>> As a convenience, would it make sense to allow a worktree to be\n>> re-locked with a different reason rather than erroring out?\n>\n> For now I think the user can just unlock then relock, after examining\n> the previous reason and maybe incorporating it to the new reason. Yes\n> there is a race but for these operations I don't think it really\n> matters. If it's done often enough, we can lift this restriction.\n\nAgreed. It's easy to imagine someone wanting to change the lock\nmessage to correct a typo, but that's likely a rare occurrence, and\nother conceivable reasons for wanting to change the message are\nprobably even more unusual, thus not worth worrying about now.\n\n>>> +       write_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n>>> +                  \"%s\", reason);\n>>> +       return 0;\n>>> +}\n>>\n>> This is a tangent, but it would be nice for the \"git worktree list\"\n>> command to show whether a worktree is locked, and the reason (if\n>> available), both in pretty and porcelain formats. (That was another\n>> reason why I suggested to Mike, back when he was adding the \"list\"\n>> command, that 'struct worktree' should have a 'locked' field and\n>> get_worktrees() should populate it automatically.)\n>\n> Yes. And a good reason for get_worktrees() to read lock status\n> automatically too. I will not do it right away though or at least\n> until after I'm done with move/remove and the shared config file\n> problem.\n\nFor the record (for future readers of this thread), [1] itemized\nvarious useful bits of information that would be handy as fields in\n'struct worktree' and populated automatically by get_worktrees(), all\nof which (I think) could be nice to have in the output of \"git\nworktree list\". Excerpt:\n\n    For git-worktree commands such as \"lock\", \"mv\", \"remove\", it\n    likely will be nice to allow people to specify the linked\n    worktree not only by path, but also by tag, and possibly even by\n    $(basename $path) if not ambiguous. Therefore, to support such\n    usage, at minimum, I think you also want to show the worktree's\n    tag (d->d_name) in addition to the path.\n\n    Other information which would be nice to display for each\n    worktree (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\n    The prune reason could be obtained by factoring out the\n    reason-determination code from worktree.c:prune_worktree() to\n    make it re-usable.\n\n[1]: http://article.gmane.org/gmane.comp.version-control.git/275528\n"},{"id":"287228","messageId":"CAPig+cTYZORwm-Wc3JkzHaaasOxJytYiFVgayb21fWrvXnf_Bw@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cSpoi0eMKQPqKG6+skc87dgDLZiLJZJLUk-iyd13P3E3g@mail.gmail.com","subject":"Re: [PATCH v2 6/6] worktree: simplify prefixing paths","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-23T04:31:08Z","receivedAt":"2016-05-23T04:31:08Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 22, 2016 at 7:32 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Sun, May 22, 2016 at 5:33 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> @@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n>>         if (ac < 2)\n>>                 usage_with_options(worktree_usage, options);\n>> +       if (!prefix)\n>> +               prefix = \"\";\n>\n> Considering that all the other existing git-worktree subcommands\n> already handle NULL prefix acceptably, it makes me a bit uncomfortable\n> that this \"fix\", which could be local to add(), leaks into all the\n> other subcommands, thus making the assumption that they (or other new\n> subcommands) will never care about the distinction between NULL and\n> \"\".\n>\n> Not a big objection; just a minor niggle, probably not worth a re-roll.\n\nOkay, I see that you're taking advantage of the prefix=\"\" in patch\n5/6, as well, so it's not benefitting only add().\n"},{"id":"287227","messageId":"CAPig+cRSROJw74K0cu1qApNnuVJY3=YphgVVCYfU1_nh+STMAg@mail.gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-5-pclouds@gmail.com","subject":"Re: [PATCH v2 4/5] worktree: add \"lock\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-23T04:36:05Z","receivedAt":"2016-05-23T04:36:05Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 22, 2016 at 6:43 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Helped-by: Eric Sunshine <sunshine@sunshineco.com>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\n> @@ -150,7 +161,8 @@ instead.\n>\n>  To prevent a $GIT_DIR/worktrees entry from being pruned (which\n>  can be useful in some situations, such as when the\n> -entry's working tree is stored on a portable device), add a file named\n> +entry's working tree is stored on a portable device), use the\n> +`git worktree lock` comamnd, which adds a file named\n\ns/comamnd/command/\n\n>  'locked' to the entry's directory. The file contains the reason in\n>  plain text. For example, if a linked working tree's `.git` file points\n>  to `/path/main/.git/worktrees/test-next` then a file named\n> diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\n> @@ -0,0 +1,54 @@\n> +test_expect_success 'lock linked worktree from another worktree' '\n> +       rm .git/worktrees/source/locked &&\n> +       git worktree add elsewhere &&\n> +       (\n> +               cd elsewhere &&\n> +               git worktree lock --reason hahaha ../source\n\nCould use \"git -C elsewhere worktree lock ...\" and drop the subshell,\nbut not worth a re-roll.\n\n> +       ) &&\n> +       echo hahaha >expected &&\n> +       test_cmp expected .git/worktrees/source/locked\n> +'\n> +\n> +test_expect_success 'lock worktree twice (from the locked worktree)' '\n> +       (\n> +               cd source &&\n> +               test_must_fail git worktree lock .\n\nLikewise, -C.\n\n> +       ) &&\n> +       echo hahaha >expected &&\n> +       test_cmp expected .git/worktrees/source/locked\n> +'\n"},{"id":"287229","messageId":"CAPig+cRFx8eAB4LgcCO2efB5qLSJT=u+_QBrJF7uu+BQg3U_jg@mail.gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-6-pclouds@gmail.com","subject":"Re: [PATCH v2 5/5] worktree: add \"unlock\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-23T04:39:42Z","receivedAt":"2016-05-23T04:39:42Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 22, 2016 at 6:43 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> @@ -500,6 +501,37 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n> +static int unlock_worktree(int ac, const char **av, const char *prefix)\n> +{\n> +       [...]\n> +       wt = find_worktree_by_path(worktrees, dst.buf);\n> +       strbuf_release(&dst);\n> +       if (!wt)\n> +               die(_(\"'%s' is not a working directory\"), av[0]);\n> +       if (is_main_worktree(wt))\n> +               die(_(\"'%s' is a main working directory\"), av[0]);\n> +       if (!is_worktree_locked(wt))\n> +               die(_(\"not locked\"));\n\nCould be _(\"'%s' is not locked\"), but not worth a re-roll.\n\n> +       ret = unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n> +       free_worktrees(worktrees);\n> +       return ret;\n> +}\n"},{"id":"287230","messageId":"CAPig+cQVaMOKtbUCWdZqYDO8ZUZkVcSJH14S=2xrZiDVJ59Xdg@mail.gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-1-pclouds@gmail.com","subject":"Re: [PATCH v2 0/5] worktree lock/unlock","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-23T04:51:09Z","receivedAt":"2016-05-23T04:51:09Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 22, 2016 at 6:43 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> This should address all of Eric's comments (thanks!). An extra change\n> I made is free_worktrees() at the end of {,un}lock_worktree() to avoid\n> leaking. This series depends on nd/worktree-cleanup-post-head-protection.\n\nThanks, this addresses all my comments from the previous round (aside\nfrom the suggestion to add a 'locked' field to 'struct worktree' and\npopulate it automatically, which you elected to defer for the\npresent).\n\nI re-read the entire series and, aside from the typo in the\ndocumentation update of patch 4/5, everything looks fine, and the\nseries is:\n\n    Reviewed-by: Eric Sunshine <sunshine@sunshineco.com>\n"},{"id":"287234","messageId":"CACsJy8BTHg8bkZqpaJ5XtZBxK5ZR4kJu+oNwd8sRULBeEdDW=A@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cQVaMOKtbUCWdZqYDO8ZUZkVcSJH14S=2xrZiDVJ59Xdg@mail.gmail.com","subject":"Re: [PATCH v2 0/5] worktree lock/unlock","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-23T09:23:41Z","receivedAt":"2016-05-23T09:23:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, May 23, 2016 at 11:51 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Sun, May 22, 2016 at 6:43 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> This should address all of Eric's comments (thanks!). An extra change\n>> I made is free_worktrees() at the end of {,un}lock_worktree() to avoid\n>> leaking. This series depends on nd/worktree-cleanup-post-head-protection.\n>\n> Thanks, this addresses all my comments from the previous round (aside\n> from the suggestion to add a 'locked' field to 'struct worktree' and\n> populate it automatically, which you elected to defer for the\n> present).\n\nIf there's another re-roll (likely so), I'm tempted to do that too as\nit's quite clear now that \"locked\" belongs in struct worktree.\n-- \nDuy\n"},{"id":"287235","messageId":"CACsJy8B6L97-07xgzduGRY5wbTCotLphzgmHDuzd6ZH81YFE3g@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cTYZORwm-Wc3JkzHaaasOxJytYiFVgayb21fWrvXnf_Bw@mail.gmail.com","subject":"Re: [PATCH v2 6/6] worktree: simplify prefixing paths","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-23T09:26:33Z","receivedAt":"2016-05-23T09:26:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, May 23, 2016 at 11:31 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Sun, May 22, 2016 at 7:32 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> On Sun, May 22, 2016 at 5:33 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>>> @@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n>>>         if (ac < 2)\n>>>                 usage_with_options(worktree_usage, options);\n>>> +       if (!prefix)\n>>> +               prefix = \"\";\n>>\n>> Considering that all the other existing git-worktree subcommands\n>> already handle NULL prefix acceptably, it makes me a bit uncomfortable\n>> that this \"fix\", which could be local to add(), leaks into all the\n>> other subcommands, thus making the assumption that they (or other new\n>> subcommands) will never care about the distinction between NULL and\n>> \"\".\n>>\n>> Not a big objection; just a minor niggle, probably not worth a re-roll.\n>\n> Okay, I see that you're taking advantage of the prefix=\"\" in patch\n> 5/6, as well, so it's not benefitting only add().\n\nYep. 'add' so far is the only command taking paths. The next four,\nlock, unlock, move and delete, all deal with paths and need to deal\nwith NULL prefix otherwise.\n-- \nDuy\n"},{"id":"287795","messageId":"CACsJy8D1WsrA9723FR_m8jR3W4RaiUtu7KHK-37-qLcj9+NfhQ@mail.gmail.com","threadId":"42034","inReplyTo":"CAPig+cRnt4ELACEitEkiZxbVT9Uk8hzdj0Ts7cwZwh2qPoa1cA@mail.gmail.com","subject":"Re: [PATCH v2 1/5] worktree.c: add find_worktree_by_path()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-30T09:56:28Z","receivedAt":"2016-05-30T09:56:28Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Mon, May 23, 2016 at 11:11 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Sun, May 22, 2016 at 6:43 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> So far we haven't needed to identify an existing worktree from command\n>> line. Future commands such as lock or move will need it. There are of\n>> course other options for identifying a worktree, for example by branch\n>> or even by internal id. They may be added later if proved useful.\n>\n> Beyond the above methods for specifying a worktree, [1] adds\n> $(basename $path) as a possibility if not ambiguous. Excerpt:\n>\n>     For git-worktree commands such as \"lock\", \"mv\", \"remove\", it\n>     likely will be nice to allow people to specify the linked\n>     worktree not only by path, but also by tag, and possibly even by\n>     $(basename $path) if not ambiguous.\n\nThanks. This changes things. Turns out my \"find_by_path\" API  cannot\nbe easily extended to support identifying worktrees some other way. I\nwill need to pass the command line argument as-is, so that this\n\"find_worktree\" (by any means) has enough info to decide (especially\nabout ambiguation).\n-- \nDuy\n"},{"id":"287800","messageId":"20160530104939.28407-1-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160522104341.656-1-pclouds@gmail.com","subject":"[PATCH v3 0/6] worktree lock/unlock","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-30T10:49:33Z","receivedAt":"2016-05-30T10:49:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"v3 changes almost all patches so I didn't bother with interdiff\n\n  [1/6] worktree.c: add find_worktree()\n\n    This replaces the old find_wortree_by_path(). It now takes both prefix\n    and command line argument so it can do some intelligent things with\n    them if needed.\n\n  [2/6] worktree.c: find_worktree() learns to identify worktrees by basename\n\n    This is from Eric's suggestion. Here we only care about the \"argument\"\n    argument and ignore the \"prefix\" argument from 1/6\n\n  [3/6] worktree.c: add is_main_worktree()\n \n    This is the only unchanged patch since v2!\n\n  [4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()\n\n    This used to be is_worktree_locked()\n\n  [5/6] worktree: add \"lock\" command\n\n    Besides Eric's comments in v2, the syntax line \"worktree lock <path>\"\n    is changed to \"worktree lock <worktree>\", where <worktree> could be\n    either a path, or a base name, or something more in future. I'm aware\n    that we don't like showing \"worktrees\" in user documents, favoring\n    working trees. But I think <worktree> is ok in this case.\n\n  [6/6] worktree: add \"unlock\" command\n\n    Pretty much addressing comments from v2 and some more changes because\n    of the new 1/6 and 4/6.\n\n Documentation/git-worktree.txt         | 33 +++++++++++++----\n builtin/worktree.c                     | 66 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 ++-\n t/t2028-worktree-move.sh (new +x)      | 62 ++++++++++++++++++++++++++++++++\n worktree.c                             | 53 +++++++++++++++++++++++++++\n worktree.h                             | 14 ++++++++\n 6 files changed, 226 insertions(+), 7 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287801","messageId":"20160530104939.28407-2-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160530104939.28407-1-pclouds@gmail.com","subject":"[PATCH v3 1/6] worktree.c: add find_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-30T10:49:34Z","receivedAt":"2016-05-30T10:49:34Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"So far we haven't needed to identify an existing worktree from command\nline. Future commands such as lock or move will need it. The current\nimplementation identifies worktrees by path (*). In future, the function\ncould learn to identify by $(basename $path) or tags...\n\n(*) We could probably go cheaper with comparing inode number (and\nprobably more reliable than paths when unicode enters the game). But not\nall systems have good inode that so let's stick to something simple for\nnow.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 15 +++++++++++++++\n worktree.h |  8 ++++++++\n 2 files changed, 23 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex f4a4f38..0782e00 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n+struct worktree *find_worktree(struct worktree **list,\n+\t\t\t       const char *prefix,\n+\t\t\t       const char *arg)\n+{\n+\tchar *path;\n+\n+\targ = prefix_filename(prefix, strlen(prefix), arg);\n+\tpath = xstrdup(real_path(arg));\n+\tfor (; *list; list++)\n+\t\tif (!fspathcmp(path, real_path((*list)->path)))\n+\t\t\tbreak;\n+\tfree(path);\n+\treturn *list;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 1394909..7ad15da 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -29,6 +29,14 @@ extern struct worktree **get_worktrees(void);\n  */\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n+/*\n+ * Search a worktree that can be unambiguously identified by\n+ * \"arg\". \"prefix\" must not be NULL.\n+ */\n+extern struct worktree *find_worktree(struct worktree **list,\n+\t\t\t\t      const char *prefix,\n+\t\t\t\t      const char *arg);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287802","messageId":"20160530104939.28407-3-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160530104939.28407-1-pclouds@gmail.com","subject":"[PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-30T10:49:35Z","receivedAt":"2016-05-30T10:49:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This allows the user to do something like \"worktree lock foo\" instead of\n\"worktree lock <path/to/foo>\". With completion support it could be quite\nconvenient. While this base name search can be done in the same worktree\niteration loop, the code is split into a separate function for clarity.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 21 +++++++++++++++++++++\n 1 file changed, 21 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 0782e00..4dd7b77 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -214,12 +214,33 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n+static struct worktree *find_worktree_by_basename(struct worktree **list,\n+\t\t\t\t\t\t  const char *base_name)\n+{\n+\tstruct worktree *found = NULL;\n+\tint nr_found = 0;\n+\n+\tfor (; *list && nr_found < 2; list++) {\n+\t\tchar *path = xstrdup((*list)->path);\n+\t\tif (!fspathcmp(base_name, basename(path))) {\n+\t\t\tfound = *list;\n+\t\t\tnr_found++;\n+\t\t}\n+\t\tfree(path);\n+\t}\n+\treturn nr_found == 1 ? found : NULL;\n+}\n+\n struct worktree *find_worktree(struct worktree **list,\n \t\t\t       const char *prefix,\n \t\t\t       const char *arg)\n {\n+\tstruct worktree *wt;\n \tchar *path;\n \n+\tif ((wt = find_worktree_by_basename(list, arg)))\n+\t\treturn wt;\n+\n \targ = prefix_filename(prefix, strlen(prefix), arg);\n \tpath = xstrdup(real_path(arg));\n \tfor (; *list; list++)\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287803","messageId":"20160530104939.28407-4-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160530104939.28407-1-pclouds@gmail.com","subject":"[PATCH v3 3/6] worktree.c: add is_main_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-30T10:49:36Z","receivedAt":"2016-05-30T10:49:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Main worktree _is_ different. You can lock a linked worktree but not the\nmain one, for example. Provide an API for checking that.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 5 +++++\n worktree.h | 5 +++++\n 2 files changed, 10 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 4dd7b77..6432eec 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -250,6 +250,11 @@ struct worktree *find_worktree(struct worktree **list,\n \treturn *list;\n }\n \n+int is_main_worktree(const struct worktree *wt)\n+{\n+\treturn !wt->id;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 7ad15da..e1c4715 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,\n \t\t\t\t      const char *prefix,\n \t\t\t\t      const char *arg);\n \n+/*\n+ * Return true if the given worktree is the main one.\n+ */\n+extern int is_main_worktree(const struct worktree *wt);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287804","messageId":"20160530104939.28407-5-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160530104939.28407-1-pclouds@gmail.com","subject":"[PATCH v3 4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-30T10:49:37Z","receivedAt":"2016-05-30T10:49:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We need this later to avoid double locking a worktree, or unlocking one\nwhen it's not even locked.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 12 ++++++++++++\n worktree.h |  1 +\n 2 files changed, 13 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 6432eec..c6a64b1 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -98,6 +98,7 @@ static struct worktree *get_main_worktree(void)\n \tworktree->is_detached = is_detached;\n \tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n+\tworktree->lock_reason = NULL;\n \n done:\n \tstrbuf_release(&path);\n@@ -144,6 +145,17 @@ static struct worktree *get_linked_worktree(const char *id)\n \tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n \n+\tstrbuf_reset(&path);\n+\tstrbuf_addf(&path, \"%s/worktrees/%s/locked\", get_git_common_dir(), id);\n+\tif (file_exists(path.buf)) {\n+\t\tstruct strbuf lock_reason = STRBUF_INIT;\n+\t\tif (strbuf_read_file(&lock_reason, path.buf, 0) < 0)\n+\t\t\tdie_errno(_(\"failed to read '%s'\"), path.buf);\n+\t\tstrbuf_trim(&lock_reason);\n+\t\tworktree->lock_reason = strbuf_detach(&lock_reason, NULL);\n+\t} else\n+\t\tworktree->lock_reason = NULL;\n+\n done:\n \tstrbuf_release(&path);\n \tstrbuf_release(&worktree_path);\ndiff --git a/worktree.h b/worktree.h\nindex e1c4715..9932710 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -5,6 +5,7 @@ struct worktree {\n \tchar *path;\n \tchar *id;\n \tchar *head_ref;\n+\tchar *lock_reason;\t/* NULL means not locked */\n \tunsigned char head_sha1[20];\n \tint is_detached;\n \tint is_bare;\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287806","messageId":"20160530104939.28407-6-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160530104939.28407-1-pclouds@gmail.com","subject":"[PATCH v3 5/6] worktree: add \"lock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-30T10:49:38Z","receivedAt":"2016-05-30T10:49:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Helped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         | 28 +++++++++++++++-----\n builtin/worktree.c                     | 38 +++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 +++-\n t/t2028-worktree-move.sh (new +x)      | 48 ++++++++++++++++++++++++++++++++++\n 4 files changed, 112 insertions(+), 7 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 27feff6..6fca6cf 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] [--checkout] [-b <new-branch>] <path> [<branch>]\n 'git worktree list' [--porcelain]\n+'git worktree lock' [--reason <string>] <worktree>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n \n DESCRIPTION\n@@ -38,9 +39,8 @@ section \"DETAILS\" for more information.\n \n If a linked working tree is stored on a portable device or network share\n which is not always mounted, you can prevent its administrative files from\n-being pruned by creating a file named 'locked' alongside the other\n-administrative files, optionally containing a plain text reason that\n-pruning should be suppressed. See section \"DETAILS\" for more information.\n+being pruned by issuing the `git worktree lock` command, optionally\n+specifying `--reason` to explain why the working tree is locked.\n \n COMMANDS\n --------\n@@ -61,6 +61,14 @@ each of the linked worktrees.  The output details include if the worktree is\n bare, the revision currently checked out, and the branch currently checked out\n (or 'detached HEAD' if none).\n \n+lock::\n+\n+If a working tree is on a portable device or network share which\n+is not always mounted, lock it to prevent its administrative\n+files from being pruned automatically. This also prevents it from\n+being moved or deleted. Optionally, specify a reason for the lock\n+with `--reason`.\n+\n prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n@@ -110,6 +118,15 @@ OPTIONS\n --expire <time>::\n \tWith `prune`, only expire unused working trees older than <time>.\n \n+--reason <string>:\n+\tWith `lock`, an explanation why the worktree is locked.\n+\n+<worktree>::\n+\tWorking trees can be identified by path, either relative or\n+\tabsolute. Alternatively, if the last component in the\n+\tworktree's path is unique among working trees, it can be used\n+\tas well.\n+\n DETAILS\n -------\n Each linked working tree has a private sub-directory in the repository's\n@@ -150,7 +167,8 @@ instead.\n \n To prevent a $GIT_DIR/worktrees entry from being pruned (which\n can be useful in some situations, such as when the\n-entry's working tree is stored on a portable device), add a file named\n+entry's working tree is stored on a portable device), use the\n+`git worktree lock` command, which adds a file named\n 'locked' to the entry's directory. The file contains the reason in\n plain text. For example, if a linked working tree's `.git` file points\n to `/path/main/.git/worktrees/test-next` then a file named\n@@ -226,8 +244,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-- `lock` to prevent automatic pruning of administrative files (for instance,\n-  for a working tree on a portable device)\n \n GIT\n ---\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex f9dac37..84fe63b 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -14,6 +14,7 @@\n static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> [<branch>]\"),\n \tN_(\"git worktree list [<options>]\"),\n+\tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n \tNULL\n };\n@@ -459,6 +460,41 @@ static int list(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int lock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tconst char *reason = \"\", *old_reason;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"reason\", &reason, N_(\"string\"),\n+\t\t\t   N_(\"reason for locking\")),\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree(worktrees, prefix, av[0]);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\n+\told_reason = wt->lock_reason;\n+\tif (old_reason) {\n+\t\tif (*old_reason)\n+\t\t\tdie(_(\"'%s' is already locked, reason: %s\"),\n+\t\t\t    av[0], old_reason);\n+\t\tdie(_(\"'%s' is already locked\"), av[0]);\n+\t}\n+\n+\twrite_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n+\t\t   \"%s\", reason);\n+\tfree_worktrees(worktrees);\n+\treturn 0;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -475,5 +511,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\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+\tif (!strcmp(av[1], \"lock\"))\n+\t\treturn lock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 951a186..f88727d 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2597,7 +2597,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list prune\"\n+\tlocal subcommands=\"add list lock prune\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\n@@ -2609,6 +2609,9 @@ _git_worktree ()\n \t\tlist,--*)\n \t\t\t__gitcomp \"--porcelain\"\n \t\t\t;;\n+\t\tlock,--*)\n+\t\t\t__gitcomp \"--reason\"\n+\t\t\t;;\n \t\tprune,--*)\n \t\t\t__gitcomp \"--dry-run --expire --verbose\"\n \t\t\t;;\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nnew file mode 100755\nindex 0000000..87afc2e\n--- /dev/null\n+++ b/t/t2028-worktree-move.sh\n@@ -0,0 +1,48 @@\n+#!/bin/sh\n+\n+test_description='test git worktree move, remove, lock and unlock'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit init &&\n+\tgit worktree add source &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/source\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'lock main worktree' '\n+\ttest_must_fail git worktree lock .\n+'\n+\n+test_expect_success 'lock linked worktree' '\n+\tgit worktree lock --reason hahaha source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock linked worktree from another worktree' '\n+\trm .git/worktrees/source/locked &&\n+\tgit worktree add elsewhere &&\n+\tgit -C elsewhere worktree lock --reason hahaha ../source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock worktree twice' '\n+\ttest_must_fail git worktree lock source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock worktree twice (from the locked worktree)' '\n+\ttest_must_fail git -C source worktree lock . &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_done\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287805","messageId":"20160530104939.28407-7-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160530104939.28407-1-pclouds@gmail.com","subject":"[PATCH v3 6/6] worktree: add \"unlock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-30T10:49:39Z","receivedAt":"2016-05-30T10:49:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         |  5 +++++\n builtin/worktree.c                     | 28 ++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  2 +-\n t/t2028-worktree-move.sh               | 14 ++++++++++++++\n 4 files changed, 48 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 6fca6cf..6a5ff47 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n 'git worktree list' [--porcelain]\n 'git worktree lock' [--reason <string>] <worktree>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n+'git worktree unlock' <worktree>\n \n DESCRIPTION\n -----------\n@@ -73,6 +74,10 @@ prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n \n+unlock::\n+\n+Unlock a worktree, allowing it to be pruned, moved or deleted.\n+\n OPTIONS\n -------\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 84fe63b..5fe9323 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {\n \tN_(\"git worktree list [<options>]\"),\n \tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n+\tN_(\"git worktree unlock <path>\"),\n \tNULL\n };\n \n@@ -495,6 +496,31 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int unlock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tint ret;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree(worktrees, prefix, av[0]);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\tif (!wt->lock_reason)\n+\t\tdie(_(\"'%s' is not locked\"), av[0]);\n+\tret = unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n+\tfree_worktrees(worktrees);\n+\treturn ret;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -513,5 +539,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn list(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"lock\"))\n \t\treturn lock_worktree(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"unlock\"))\n+\t\treturn unlock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex f88727d..0e3841d 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2597,7 +2597,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list lock prune\"\n+\tlocal subcommands=\"add list lock prune unlock\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex 87afc2e..68d3fe8 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -45,4 +45,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' '\n \ttest_cmp expected .git/worktrees/source/locked\n '\n \n+test_expect_success 'unlock main worktree' '\n+\ttest_must_fail git worktree unlock .\n+'\n+\n+test_expect_success 'unlock linked worktree' '\n+\tgit worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'unlock worktree twice' '\n+\ttest_must_fail git worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n test_done\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"287919","messageId":"xmqqh9de5d6e.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160530104939.28407-3-pclouds@gmail.com","subject":"Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-31T17:51:05Z","receivedAt":"2016-05-31T17:51:05Z","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> This allows the user to do something like \"worktree lock foo\" instead of\n> \"worktree lock <path/to/foo>\". With completion support it could be quite\n> convenient. While this base name search can be done in the same worktree\n> iteration loop, the code is split into a separate function for clarity.\n\nMakes me wonder if you want to do this without calling basename(3)\nfunction at all.  I do not think such a feature would cost that much\nover what we see in this patch.\n\nThat is, wouldn't you rather see \"worktree lock to/foo\" work when\n'foo' is ambiguous but 'to/foo' is not?\n\n>\n> Suggested-by: Eric Sunshine <sunshine@sunshineco.com>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  worktree.c | 21 +++++++++++++++++++++\n>  1 file changed, 21 insertions(+)\n>\n> diff --git a/worktree.c b/worktree.c\n> index 0782e00..4dd7b77 100644\n> --- a/worktree.c\n> +++ b/worktree.c\n> @@ -214,12 +214,33 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n>  \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n>  }\n>  \n> +static struct worktree *find_worktree_by_basename(struct worktree **list,\n> +\t\t\t\t\t\t  const char *base_name)\n> +{\n> +\tstruct worktree *found = NULL;\n> +\tint nr_found = 0;\n> +\n> +\tfor (; *list && nr_found < 2; list++) {\n> +\t\tchar *path = xstrdup((*list)->path);\n> +\t\tif (!fspathcmp(base_name, basename(path))) {\n> +\t\t\tfound = *list;\n> +\t\t\tnr_found++;\n> +\t\t}\n> +\t\tfree(path);\n> +\t}\n> +\treturn nr_found == 1 ? found : NULL;\n> +}\n> +\n>  struct worktree *find_worktree(struct worktree **list,\n>  \t\t\t       const char *prefix,\n>  \t\t\t       const char *arg)\n>  {\n> +\tstruct worktree *wt;\n>  \tchar *path;\n>  \n> +\tif ((wt = find_worktree_by_basename(list, arg)))\n> +\t\treturn wt;\n> +\n>  \targ = prefix_filename(prefix, strlen(prefix), arg);\n>  \tpath = xstrdup(real_path(arg));\n>  \tfor (; *list; list++)\n"},{"id":"287920","messageId":"xmqqd1o25d3k.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160530104939.28407-4-pclouds@gmail.com","subject":"Re: [PATCH v3 3/6] worktree.c: add is_main_worktree()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-31T17:52:47Z","receivedAt":"2016-05-31T17:52:47Z","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> Main worktree _is_ different. You can lock a linked worktree but not the\n> main one, for example. Provide an API for checking that.\n\nYou haven't defined what it means to \"lock\" a worktree, so that\nexample does not sound convincing at all, until the reader sees what\nhappens later in the series.  I'll just take your word for it and\nkeep reading for now ;-)\n\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  worktree.c | 5 +++++\n>  worktree.h | 5 +++++\n>  2 files changed, 10 insertions(+)\n>\n> diff --git a/worktree.c b/worktree.c\n> index 4dd7b77..6432eec 100644\n> --- a/worktree.c\n> +++ b/worktree.c\n> @@ -250,6 +250,11 @@ struct worktree *find_worktree(struct worktree **list,\n>  \treturn *list;\n>  }\n>  \n> +int is_main_worktree(const struct worktree *wt)\n> +{\n> +\treturn !wt->id;\n> +}\n> +\n>  int is_worktree_being_rebased(const struct worktree *wt,\n>  \t\t\t      const char *target)\n>  {\n> diff --git a/worktree.h b/worktree.h\n> index 7ad15da..e1c4715 100644\n> --- a/worktree.h\n> +++ b/worktree.h\n> @@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,\n>  \t\t\t\t      const char *prefix,\n>  \t\t\t\t      const char *arg);\n>  \n> +/*\n> + * Return true if the given worktree is the main one.\n> + */\n> +extern int is_main_worktree(const struct worktree *wt);\n> +\n>  /*\n>   * Free up the memory for worktree(s)\n>   */\n"},{"id":"287921","messageId":"xmqq8tyq5czn.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160530104939.28407-5-pclouds@gmail.com","subject":"Re: [PATCH v3 4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-31T17:55:08Z","receivedAt":"2016-05-31T17:55:08Z","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> We need this later to avoid double locking a worktree, or unlocking one\n> when it's not even locked.\n\nShouldn't this be done lazily?\n\nIf a user is working in worktree B and is not doing anything funky,\nshe would not care why worktree A and C are locked, even though she\nmight care the fact that they are locked.\n\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  worktree.c | 12 ++++++++++++\n>  worktree.h |  1 +\n>  2 files changed, 13 insertions(+)\n>\n> diff --git a/worktree.c b/worktree.c\n> index 6432eec..c6a64b1 100644\n> --- a/worktree.c\n> +++ b/worktree.c\n> @@ -98,6 +98,7 @@ static struct worktree *get_main_worktree(void)\n>  \tworktree->is_detached = is_detached;\n>  \tworktree->is_current = 0;\n>  \tadd_head_info(&head_ref, worktree);\n> +\tworktree->lock_reason = NULL;\n>  \n>  done:\n>  \tstrbuf_release(&path);\n> @@ -144,6 +145,17 @@ static struct worktree *get_linked_worktree(const char *id)\n>  \tworktree->is_current = 0;\n>  \tadd_head_info(&head_ref, worktree);\n>  \n> +\tstrbuf_reset(&path);\n> +\tstrbuf_addf(&path, \"%s/worktrees/%s/locked\", get_git_common_dir(), id);\n> +\tif (file_exists(path.buf)) {\n> +\t\tstruct strbuf lock_reason = STRBUF_INIT;\n> +\t\tif (strbuf_read_file(&lock_reason, path.buf, 0) < 0)\n> +\t\t\tdie_errno(_(\"failed to read '%s'\"), path.buf);\n> +\t\tstrbuf_trim(&lock_reason);\n> +\t\tworktree->lock_reason = strbuf_detach(&lock_reason, NULL);\n> +\t} else\n> +\t\tworktree->lock_reason = NULL;\n> +\n>  done:\n>  \tstrbuf_release(&path);\n>  \tstrbuf_release(&worktree_path);\n> diff --git a/worktree.h b/worktree.h\n> index e1c4715..9932710 100644\n> --- a/worktree.h\n> +++ b/worktree.h\n> @@ -5,6 +5,7 @@ struct worktree {\n>  \tchar *path;\n>  \tchar *id;\n>  \tchar *head_ref;\n> +\tchar *lock_reason;\t/* NULL means not locked */\n>  \tunsigned char head_sha1[20];\n>  \tint is_detached;\n>  \tint is_bare;\n"},{"id":"287923","messageId":"xmqq4m9e5cae.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160530104939.28407-6-pclouds@gmail.com","subject":"Re: [PATCH v3 5/6] worktree: add \"lock\" command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-31T18:10:17Z","receivedAt":"2016-05-31T18:10:17Z","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> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> index f9dac37..84fe63b 100644\n> --- a/builtin/worktree.c\n> +++ b/builtin/worktree.c\n> @@ -14,6 +14,7 @@\n>  static const char * const worktree_usage[] = {\n>  \tN_(\"git worktree add [<options>] <path> [<branch>]\"),\n>  \tN_(\"git worktree list [<options>]\"),\n> +\tN_(\"git worktree lock [<options>] <path>\"),\n>  \tN_(\"git worktree prune [<options>]\"),\n>  \tNULL\n>  };\n> @@ -459,6 +460,41 @@ static int list(int ac, const char **av, const char *prefix)\n>  \treturn 0;\n>  }\n>  \n> +static int lock_worktree(int ac, const char **av, const char *prefix)\n> +{\n> +\tconst char *reason = \"\", *old_reason;\n> +\tstruct option options[] = {\n> +\t\tOPT_STRING(0, \"reason\", &reason, N_(\"string\"),\n> +\t\t\t   N_(\"reason for locking\")),\n> +\t\tOPT_END()\n> +\t};\n> +\tstruct worktree **worktrees, *wt;\n> +\n> +\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n> +\tif (ac != 1)\n> +\t\tusage_with_options(worktree_usage, options);\n> +\n> +\tworktrees = get_worktrees();\n> +\twt = find_worktree(worktrees, prefix, av[0]);\n> +\tif (!wt)\n> +\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n> +\tif (is_main_worktree(wt))\n> +\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n> +\n> +\told_reason = wt->lock_reason;\n\nThis use pattern suggests that the reading of lock_reason should be\ndone lazily, doesn't it?  The other user of the \"is it locked?\"\ninformation, which is builtin/worktree.c::prune_worktree(), does not\neven use the get_worktrees() interface and instead it just checks if\nthe lock marker git_path(\"worktrees/*/locked\") exists.\n\nPerhaps you want one of these as a public interface:\n\n\tint worktree_is_locked(const char *id, const char **reason);\n\tint worktree_is_locked(struct worktree *wt, const char **reason);\n\nwhere the caller can learn if a worktree is locked, and optionally why?\n"},{"id":"287925","messageId":"xmqqzir63xlg.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160530104939.28407-7-pclouds@gmail.com","subject":"Re: [PATCH v3 6/6] worktree: add \"unlock\" command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-31T18:12:59Z","receivedAt":"2016-05-31T18:12:59Z","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> +\tif (is_main_worktree(wt))\n> +\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n> +\tif (!wt->lock_reason)\n> +\t\tdie(_(\"'%s' is not locked\"), av[0]);\n\nExactly the same comment about the lack of need for lock_reason\nfield as 5/6 appiles here.  Also, as \"lock\" does not allow you to\nlock the primary tree, do you even need is_main_worktree() check?\n\nThat is:\n\n\tif (!worktree_is_locked(wt, &reason))\n        \tdie(_(\"'%s' is not locked), av[0]);\n\nshould be sufficient, no?\n"},{"id":"287926","messageId":"xmqqr3ci3wv9.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160530104939.28407-6-pclouds@gmail.com","subject":"Re: [PATCH v3 5/6] worktree: add \"lock\" command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-31T18:28:42Z","receivedAt":"2016-05-31T18:28:42Z","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> @@ -11,6 +11,7 @@ SYNOPSIS\n>  [verse]\n>  'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]\n>  'git worktree list' [--porcelain]\n> +'git worktree lock' [--reason <string>] <worktree>\n>  'git worktree prune' [-n] [-v] [--expire <expire>]\n\nThis seems to be based on an unseen version that lists 'list' before\n'prune' here and other places.  I'd appreciate it if you either make\nsure a series that introduces a new feature applies cleanly to my\ntree before sending it out, or clarify to what state the series is\ndesigned to apply, when you send a patch series next time.\n\n> @@ -110,6 +118,15 @@ OPTIONS\n>  --expire <time>::\n>  \tWith `prune`, only expire unused working trees older than <time>.\n>  \n> +--reason <string>:\n\nDouble-colon, I think (locally fixable).\n"},{"id":"287928","messageId":"xmqqmvn63wra.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160530104939.28407-7-pclouds@gmail.com","subject":"Re: [PATCH v3 6/6] worktree: add \"unlock\" command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-31T18:31:05Z","receivedAt":"2016-05-31T18:31:05Z","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> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  Documentation/git-worktree.txt         |  5 +++++\n>  builtin/worktree.c                     | 28 ++++++++++++++++++++++++++++\n>  contrib/completion/git-completion.bash |  2 +-\n>  t/t2028-worktree-move.sh               | 14 ++++++++++++++\n>  4 files changed, 48 insertions(+), 1 deletion(-)\n>\n> diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\n> index 6fca6cf..6a5ff47 100644\n> --- a/Documentation/git-worktree.txt\n> +++ b/Documentation/git-worktree.txt\n> @@ -13,6 +13,7 @@ SYNOPSIS\n>  'git worktree list' [--porcelain]\n>  'git worktree lock' [--reason <string>] <worktree>\n>  'git worktree prune' [-n] [-v] [--expire <expire>]\n> +'git worktree unlock' <worktree>\n\nAgain, this seems to be built on an unseen version.  I think I fixed\nthese up (and also the \"missing second colon in the doc\"), but please\ndouble check what I push out later today.\n\nThanks.\n"},{"id":"287929","messageId":"xmqqinxu3wjf.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"xmqqmvn63wra.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 6/6] worktree: add \"unlock\" command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-31T18:35:48Z","receivedAt":"2016-05-31T18:35:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Again, this seems to be built on an unseen version.  I think I fixed\n> these up (and also the \"missing second colon in the doc\"), but please\n> double check what I push out later today.\n\nAh, this comes on top of 0409e0b6 (worktree: simplify prefixing\npaths, 2016-05-22).  I do not necessarily agree with 7b722d9 (I\nthink \"add, list, prune followed by lock, unlock\" would be a more\nsensible order), but I'll leave them as-is.\n\nThanks.  Queued.\n"},{"id":"288003","messageId":"CACsJy8CjeqwKWbV==wSuwH3eVghSEMFv01tTd9dCE5m14RQo1w@mail.gmail.com","threadId":"42034","inReplyTo":"xmqq8tyq5czn.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-01T13:02:07Z","receivedAt":"2016-06-01T13:02:07Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Jun 1, 2016 at 12:55 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> We need this later to avoid double locking a worktree, or unlocking one\n>> when it's not even locked.\n>\n> Shouldn't this be done lazily?\n>\n> If a user is working in worktree B and is not doing anything funky,\n> she would not care why worktree A and C are locked, even though she\n> might care the fact that they are locked.\n\nYou and Eric will have to settle this. It was done lazily in v2, but\nEric convinced me this lock thing will be needed for many worktree\ncommands (list, lock, unlock, move, remove and maybe even prune) that\nit makes more sense to make it a field in struct worktree instead. For\na dozen worktrees, it does not really matter. But get_worktrees() is\nalso being used outside \"git worktree\" command, some of those new\ncallers may get picky. Maybe a common ground is adding a flag to\nget_worktrees() to select what fields to be filled?\n-- \nDuy\n"},{"id":"288004","messageId":"CACsJy8Bt=eqOu0sifpGmKkJqSkravoVmd07zFUNk1LDQjfaM2w@mail.gmail.com","threadId":"42034","inReplyTo":"xmqqzir63xlg.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 6/6] worktree: add \"unlock\" command","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-01T13:10:26Z","receivedAt":"2016-06-01T13:10:26Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Jun 1, 2016 at 1:12 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> +     if (is_main_worktree(wt))\n>> +             die(_(\"'%s' is a main working directory\"), av[0]);\n>> +     if (!wt->lock_reason)\n>> +             die(_(\"'%s' is not locked\"), av[0]);\n>\n> Exactly the same comment about the lack of need for lock_reason\n> field as 5/6 appiles here.  Also, as \"lock\" does not allow you to\n> lock the primary tree, do you even need is_main_worktree() check?\n>\n> That is:\n>\n>         if (!worktree_is_locked(wt, &reason))\n>                 die(_(\"'%s' is not locked), av[0]);\n>\n> should be sufficient, no?\n\nIt crossed my mind but I went for separate checks anyway because it\ngives better explanation.\n\nAssigning either lock or unlock status to main worktree does not work\nwell either. If you assume worktree is always unlocked, you still need\nto check main worktree in lock, move and remove as all of them would\nonly proceed if it's an unlocked linked worktree. If you assume the\nopposite, you need is_main_worktree() check here and \"worktree list\"\nmay print the confusing \"locked\" status on main worktree (I assume we\nmay add that later).\n-- \nDuy\n"},{"id":"288005","messageId":"CACsJy8CmdTapWsst-PuwFNH8Uy3Vgow+fKWzQ+tGYPSc=aZsXg@mail.gmail.com","threadId":"42034","inReplyTo":"xmqqh9de5d6e.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-01T13:22:14Z","receivedAt":"2016-06-01T13:22:14Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Jun 1, 2016 at 12:51 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> This allows the user to do something like \"worktree lock foo\" instead of\n>> \"worktree lock <path/to/foo>\". With completion support it could be quite\n>> convenient. While this base name search can be done in the same worktree\n>> iteration loop, the code is split into a separate function for clarity.\n>\n> Makes me wonder if you want to do this without calling basename(3)\n> function at all.  I do not think such a feature would cost that much\n> over what we see in this patch.\n>\n> That is, wouldn't you rather see \"worktree lock to/foo\" work when\n> 'foo' is ambiguous but 'to/foo' is not?\n\nI don't know. I suppose if people have to make `basename $path` the\nsame because of some weird build settings, e.g. abc/git and def/git,\nthen this basename selection becomes useless.\n\nI had similar thought though, if you only have a worktree named \"foo\"\nthen \"fo\" or even \"f\" should be unambiguous and can also identify a\nworktree, similar to short sha-1. But I discarded that idea because of\nthe higher chances that typos can select a wrong worktree. We would\nneed to convert or match both '/' and '\\' in \"to/foo\" case because of\nWindows, so it's not much easier than basename().\n-- \nDuy\n"},{"id":"288010","messageId":"xmqq4m9d0w6v.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"CACsJy8CjeqwKWbV==wSuwH3eVghSEMFv01tTd9dCE5m14RQo1w@mail.gmail.com","subject":"Re: [PATCH v3 4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-06-01T15:23:52Z","receivedAt":"2016-06-01T15:23:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> On Wed, Jun 1, 2016 at 12:55 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>>\n>>> We need this later to avoid double locking a worktree, or unlocking one\n>>> when it's not even locked.\n>>\n>> Shouldn't this be done lazily?\n>>\n>> If a user is working in worktree B and is not doing anything funky,\n>> she would not care why worktree A and C are locked, even though she\n>> might care the fact that they are locked.\n>\n> You and Eric will have to settle this. It was done lazily in v2, but\n> Eric convinced me this lock thing will be needed for many worktree\n> commands (list, lock, unlock, move, remove and maybe even prune) that\n> it makes more sense to make it a field in struct worktree instead. For\n> a dozen worktrees, it does not really matter. But get_worktrees() is\n> also being used outside \"git worktree\" command, some of those new\n> callers may get picky. Maybe a common ground is adding a flag to\n> get_worktrees() to select what fields to be filled?\n\nNO.\n\nWhen you tell callers that they have DIRECT access to fields, then\nthey need to keep track of what flag they (or their callers) passed\nwhen get_worktrees() was called, which is a road to insanity.\n\nBy \"lazily\", I didn't mean \"do not have a field there; instead every\ntime read from the file and return the value\".  A lazy interface would\ngive a function to read the \"is it locked and if so why\" information\nas the sole interface, whose implementation would _not_ do the reading\nwhen a worktree structure is instantiated, the first call would do\nthe reading, and it is up to the implementation to cache the result\nof that first call's work in the structure and answer the subsequent\ncalls from the cache, or do the same reading for subsequent calls.\n"},{"id":"288069","messageId":"xmqqr3cgycjl.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"CACsJy8CmdTapWsst-PuwFNH8Uy3Vgow+fKWzQ+tGYPSc=aZsXg@mail.gmail.com","subject":"Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-06-01T18:44:14Z","receivedAt":"2016-06-01T18:44:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n>> That is, wouldn't you rather see \"worktree lock to/foo\" work when\n>> 'foo' is ambiguous but 'to/foo' is not?\n>\n> I don't know. I suppose if people have to make `basename $path` the\n> same because of some weird build settings, e.g. abc/git and def/git,\n> then this basename selection becomes useless.\n>\n> I had similar thought though, if you only have a worktree named \"foo\"\n> then \"fo\" or even \"f\" should be unambiguous and can also identify a\n> worktree, similar to short sha-1. But I discarded that idea because of\n> the higher chances that typos can select a wrong worktree.\n\nI do not think abbreviation of \"foo\" down to \"fo\" is sensible at\nall.  I would instead suggest \"path-component-wise tail match\",\ni.e. \"to/foo\" would match \"path/to/foo\", \"path/TO/FOO\" (on fold-case\nfilesystems), but not \"pathto/foo\".\n\n> We would\n> need to convert or match both '/' and '\\' in \"to/foo\" case because of\n> Windows, so it's not much easier than basename().\n\nI never said \"easier to implement\".  But can this codepath get\nbackslashed paths in the first place?  I somehow thought that\nnormalization would happen a lot before the control reaches here.\n\nYou'll be calling into fspathcmp() anyway; shouldn't the function\nknow that '/' and '\\' are equivalent on some platforms, or is it\nlegal to only call fspathcmp() on a single path component without\ndirectory separator?\n"},{"id":"288112","messageId":"CACsJy8B+j2im7XOV==tBtki=tOCN4k3ZHz6Jp4fq4qjqarb+ew@mail.gmail.com","threadId":"42034","inReplyTo":"xmqqr3cgycjl.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-02T09:40:35Z","receivedAt":"2016-06-02T09:40:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Jun 2, 2016 at 1:44 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> We would\n>> need to convert or match both '/' and '\\' in \"to/foo\" case because of\n>> Windows, so it's not much easier than basename().\n>\n> I never said \"easier to implement\".  But can this codepath get\n> backslashed paths in the first place?  I somehow thought that\n> normalization would happen a lot before the control reaches here.\n>\n> You'll be calling into fspathcmp() anyway; shouldn't the function\n> know that '/' and '\\' are equivalent on some platforms, or is it\n> legal to only call fspathcmp() on a single path component without\n> directory separator?\n\nWe still need to calculate the length to compare, which could be\nproblematic when utf-8 is involved, or some other encoding. If we\nalways split at '/' boundary though (e.g. \"abc/def/ghi\", \"def/ghi\" or\n\"ghi\" but never \"ef/ghi\") then it should be ok.\n-- \nDuy\n"},{"id":"288135","messageId":"xmqqfusvv8lq.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"CACsJy8B+j2im7XOV==tBtki=tOCN4k3ZHz6Jp4fq4qjqarb+ew@mail.gmail.com","subject":"Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-06-02T16:49:53Z","receivedAt":"2016-06-02T16:49:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> On Thu, Jun 2, 2016 at 1:44 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>> We would\n>>> need to convert or match both '/' and '\\' in \"to/foo\" case because of\n>>> Windows, so it's not much easier than basename().\n>>\n>> I never said \"easier to implement\".  But can this codepath get\n>> backslashed paths in the first place?  I somehow thought that\n>> normalization would happen a lot before the control reaches here.\n>>\n>> You'll be calling into fspathcmp() anyway; shouldn't the function\n>> know that '/' and '\\' are equivalent on some platforms, or is it\n>> legal to only call fspathcmp() on a single path component without\n>> directory separator?\n>\n> We still need to calculate the length to compare, which could be\n> problematic when utf-8 is involved, or some other encoding. \n\nHmph.  I was unaware that fspathcmp() used here does more than\nbyte-for-byte processing, which would cause problems due to encoding\nissues when you hand code the comparison.\n\n+static struct worktree *find_worktree_by_basename(struct worktree **list,\n+\t\t\t\t\t\t  const char *base_name)\n+{\n+\tstruct worktree *found = NULL;\n+\tint nr_found = 0;\n+\n+\tfor (; *list && nr_found < 2; list++) {\n+\t\tchar *path = xstrdup((*list)->path);\n+\t\tif (!fspathcmp(base_name, basename(path))) {\n+\t\t\tfound = *list;\n+\t\t\tnr_found++;\n+\t\t}\n+\t\tfree(path);\n+\t}\n+\treturn nr_found == 1 ? found : NULL;\n+}\n\n\n> If we always split at '/' boundary though (e.g. \"abc/def/ghi\",\n> \"def/ghi\" or \"ghi\" but never \"ef/ghi\") then it should be ok.\n\nDoes \"basename()\" used here know '/' and '\\' can both be a directory\nseparator, or does worktree->path have a normalized representation\nof the path, i.e. '/' is the only directory separator?\n"},{"id":"288222","messageId":"CACsJy8BF_KKXdMNW_aOs522wBbW9BQhbrZ_0hx+f2MCW-VPUzg@mail.gmail.com","threadId":"42034","inReplyTo":"xmqqfusvv8lq.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-03T11:11:29Z","receivedAt":"2016-06-03T11:11:29Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Jun 2, 2016 at 11:49 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> +static struct worktree *find_worktree_by_basename(struct worktree **list,\n> +                                                 const char *base_name)\n> +{\n> +       struct worktree *found = NULL;\n> +       int nr_found = 0;\n> +\n> +       for (; *list && nr_found < 2; list++) {\n> +               char *path = xstrdup((*list)->path);\n> +               if (!fspathcmp(base_name, basename(path))) {\n> +                       found = *list;\n> +                       nr_found++;\n> +               }\n> +               free(path);\n> +       }\n> +       return nr_found == 1 ? found : NULL;\n> +}\n>\n>\n>> If we always split at '/' boundary though (e.g. \"abc/def/ghi\",\n>> \"def/ghi\" or \"ghi\" but never \"ef/ghi\") then it should be ok.\n>\n> Does \"basename()\" used here know '/' and '\\' can both be a directory\n> separator, or does worktree->path have a normalized representation\n> of the path, i.e. '/' is the only directory separator?\n\nbasename() does (or I think so because Windows has its own version).\nworktree->path always uses '/' but the command line option can come\nwith either '/' or '\\'. Probably safest to accept both.\n-- \nDuy\n"},{"id":"288228","messageId":"20160603121944.28980-1-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160530104939.28407-1-pclouds@gmail.com","subject":"[PATCH v4 0/6] worktree lock/unlock","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-03T12:19:38Z","receivedAt":"2016-06-03T12:19:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"v4\n\n - fixes some documentation bugs in v3\n\n - updates is_main_worktree commit message a bit\n\n - brings back is_worktree_locked() as API with value lazily obtained\n   and cached in worktree->lock_reason and fixes a memory leak\n\n - extends basename search to path suffix search. I moved this patch\n   to the bottom so that the relevant documentation part is included\n   in the patch (previously it was part of the \"add lock command\"\n   patch).\n\nThis series is based on nd/worktree-cleanup-post-head-protection\n\nNguyễn Thái Ngọc Duy (6):\n  worktree.c: add find_worktree()\n  worktree.c: add is_main_worktree()\n  worktree.c: add is_worktree_locked()\n  worktree: add \"lock\" command\n  worktree: add \"unlock\" command\n  worktree.c: find_worktree() search by path suffix\n\n Documentation/git-worktree.txt         | 36 +++++++++++++---\n builtin/worktree.c                     | 66 ++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 ++-\n t/t2028-worktree-move.sh (new +x)      | 62 ++++++++++++++++++++++++++++\n worktree.c                             | 75 ++++++++++++++++++++++++++++++++++\n worktree.h                             | 20 +++++++++\n 6 files changed, 257 insertions(+), 7 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"288229","messageId":"20160603121944.28980-2-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160603121944.28980-1-pclouds@gmail.com","subject":"[PATCH v4 1/6] worktree.c: add find_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-03T12:19:39Z","receivedAt":"2016-06-03T12:19:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"So far we haven't needed to identify an existing worktree from command\nline. Future commands such as lock or move will need it. The current\nimplementation identifies worktrees by path (*). In future, the function\ncould learn to identify by $(basename $path) or tags...\n\n(*) We could probably go cheaper with comparing inode number (and\nprobably more reliable than paths when unicode enters the game). But not\nall systems have good inode that so let's stick to something simple for\nnow.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 15 +++++++++++++++\n worktree.h |  8 ++++++++\n 2 files changed, 23 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex e2a94e0..554f566 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n+struct worktree *find_worktree(struct worktree **list,\n+\t\t\t       const char *prefix,\n+\t\t\t       const char *arg)\n+{\n+\tchar *path;\n+\n+\targ = prefix_filename(prefix, strlen(prefix), arg);\n+\tpath = xstrdup(real_path(arg));\n+\tfor (; *list; list++)\n+\t\tif (!fspathcmp(path, real_path((*list)->path)))\n+\t\t\tbreak;\n+\tfree(path);\n+\treturn *list;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 1394909..7ad15da 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -29,6 +29,14 @@ extern struct worktree **get_worktrees(void);\n  */\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n+/*\n+ * Search a worktree that can be unambiguously identified by\n+ * \"arg\". \"prefix\" must not be NULL.\n+ */\n+extern struct worktree *find_worktree(struct worktree **list,\n+\t\t\t\t      const char *prefix,\n+\t\t\t\t      const char *arg);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"288231","messageId":"20160603121944.28980-3-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160603121944.28980-1-pclouds@gmail.com","subject":"[PATCH v4 2/6] worktree.c: add is_main_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-03T12:19:40Z","receivedAt":"2016-06-03T12:19:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Main worktree _is_ different. You can lock (*) a linked worktree but not\nthe main one, for example. Provide an API for checking that.\n\n(*) Add the file $GIT_DIR/worktrees/xxx/locked to avoid worktree xxx\nfrom being removed or moved.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 5 +++++\n worktree.h | 5 +++++\n 2 files changed, 10 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 554f566..eb3aaaa 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -229,6 +229,11 @@ struct worktree *find_worktree(struct worktree **list,\n \treturn *list;\n }\n \n+int is_main_worktree(const struct worktree *wt)\n+{\n+\treturn !wt->id;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 7ad15da..e1c4715 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,\n \t\t\t\t      const char *prefix,\n \t\t\t\t      const char *arg);\n \n+/*\n+ * Return true if the given worktree is the main one.\n+ */\n+extern int is_main_worktree(const struct worktree *wt);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"288230","messageId":"20160603121944.28980-4-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160603121944.28980-1-pclouds@gmail.com","subject":"[PATCH v4 3/6] worktree.c: add is_worktree_locked()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-03T12:19:41Z","receivedAt":"2016-06-03T12:19:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We need this later to avoid double locking a worktree, or unlocking one\nwhen it's not even locked.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 26 ++++++++++++++++++++++++++\n worktree.h |  7 +++++++\n 2 files changed, 33 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex eb3aaaa..ee592a4 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -5,6 +5,8 @@\n #include \"dir.h\"\n #include \"wt-status.h\"\n \n+static const char *lock_field_uninitialized = \"value is not important\";\n+\n void free_worktrees(struct worktree **worktrees)\n {\n \tint i = 0;\n@@ -13,6 +15,8 @@ void free_worktrees(struct worktree **worktrees)\n \t\tfree(worktrees[i]->path);\n \t\tfree(worktrees[i]->id);\n \t\tfree(worktrees[i]->head_ref);\n+\t\tif (worktrees[i]->lock_reason != lock_field_uninitialized)\n+\t\t\tfree(worktrees[i]->lock_reason);\n \t\tfree(worktrees[i]);\n \t}\n \tfree (worktrees);\n@@ -98,6 +102,7 @@ static struct worktree *get_main_worktree(void)\n \tworktree->is_detached = is_detached;\n \tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n+\tworktree->lock_reason = (char *)lock_field_uninitialized;\n \n done:\n \tstrbuf_release(&path);\n@@ -143,6 +148,7 @@ static struct worktree *get_linked_worktree(const char *id)\n \tworktree->is_detached = is_detached;\n \tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n+\tworktree->lock_reason = (char *)lock_field_uninitialized;\n \n done:\n \tstrbuf_release(&path);\n@@ -234,6 +240,26 @@ int is_main_worktree(const struct worktree *wt)\n \treturn !wt->id;\n }\n \n+const char *is_worktree_locked(struct worktree *wt)\n+{\n+\tif (wt->lock_reason == lock_field_uninitialized) {\n+\t\tstruct strbuf path = STRBUF_INIT;\n+\n+\t\tstrbuf_addstr(&path, worktree_git_path(wt, \"locked\"));\n+\t\tif (file_exists(path.buf)) {\n+\t\t\tstruct strbuf lock_reason = STRBUF_INIT;\n+\t\t\tif (strbuf_read_file(&lock_reason, path.buf, 0) < 0)\n+\t\t\t\tdie_errno(_(\"failed to read '%s'\"), path.buf);\n+\t\t\tstrbuf_trim(&lock_reason);\n+\t\t\twt->lock_reason = strbuf_detach(&lock_reason, NULL);\n+\t\t} else\n+\t\t\twt->lock_reason = NULL;\n+\t\tstrbuf_release(&path);\n+\t}\n+\n+\treturn wt->lock_reason;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex e1c4715..263b61d 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -5,6 +5,7 @@ struct worktree {\n \tchar *path;\n \tchar *id;\n \tchar *head_ref;\n+\tchar *lock_reason;\t/* internal use */\n \tunsigned char head_sha1[20];\n \tint is_detached;\n \tint is_bare;\n@@ -42,6 +43,12 @@ extern struct worktree *find_worktree(struct worktree **list,\n  */\n extern int is_main_worktree(const struct worktree *wt);\n \n+/*\n+ * Return the reason string if the given worktree is locked or NULL\n+ * otherwise.\n+ */\n+extern const char *is_worktree_locked(struct worktree *wt);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"288232","messageId":"20160603121944.28980-5-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160603121944.28980-1-pclouds@gmail.com","subject":"[PATCH v4 4/6] worktree: add \"lock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-03T12:19:42Z","receivedAt":"2016-06-03T12:19:42Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Helped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         | 26 +++++++++++++-----\n builtin/worktree.c                     | 38 +++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 +++-\n t/t2028-worktree-move.sh (new +x)      | 48 ++++++++++++++++++++++++++++++++++\n 4 files changed, 110 insertions(+), 7 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 7c4cfb0..d233d61 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] [--checkout] [-b <new-branch>] <path> [<branch>]\n 'git worktree list' [--porcelain]\n+'git worktree lock' [--reason <string>] <worktree>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n \n DESCRIPTION\n@@ -38,9 +39,8 @@ section \"DETAILS\" for more information.\n \n If a linked working tree is stored on a portable device or network share\n which is not always mounted, you can prevent its administrative files from\n-being pruned by creating a file named 'locked' alongside the other\n-administrative files, optionally containing a plain text reason that\n-pruning should be suppressed. See section \"DETAILS\" for more information.\n+being pruned by issuing the `git worktree lock` command, optionally\n+specifying `--reason` to explain why the working tree is locked.\n \n COMMANDS\n --------\n@@ -62,6 +62,14 @@ each of the linked worktrees.  The output details include if the worktree is\n bare, the revision currently checked out, and the branch currently checked out\n (or 'detached HEAD' if none).\n \n+lock::\n+\n+If a working tree is on a portable device or network share which\n+is not always mounted, lock it to prevent its administrative\n+files from being pruned automatically. This also prevents it from\n+being moved or deleted. Optionally, specify a reason for the lock\n+with `--reason`.\n+\n prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n@@ -111,6 +119,13 @@ OPTIONS\n --expire <time>::\n \tWith `prune`, only expire unused working trees older than <time>.\n \n+--reason <string>::\n+\tWith `lock`, an explanation why the working tree is locked.\n+\n+<worktree>::\n+\tWorking trees can be identified by path, either relative or\n+\tabsolute.\n+\n DETAILS\n -------\n Each linked working tree has a private sub-directory in the repository's\n@@ -151,7 +166,8 @@ instead.\n \n To prevent a $GIT_DIR/worktrees entry from being pruned (which\n can be useful in some situations, such as when the\n-entry's working tree is stored on a portable device), add a file named\n+entry's working tree is stored on a portable device), use the\n+`git worktree lock` command, which adds a file named\n 'locked' to the entry's directory. The file contains the reason in\n plain text. For example, if a linked working tree's `.git` file points\n to `/path/main/.git/worktrees/test-next` then a file named\n@@ -227,8 +243,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-- `lock` to prevent automatic pruning of administrative files (for instance,\n-  for a working tree on a portable device)\n \n GIT\n ---\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex e866844..b686c7f 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -14,6 +14,7 @@\n static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> [<branch>]\"),\n \tN_(\"git worktree list [<options>]\"),\n+\tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n \tNULL\n };\n@@ -462,6 +463,41 @@ static int list(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int lock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tconst char *reason = \"\", *old_reason;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"reason\", &reason, N_(\"string\"),\n+\t\t\t   N_(\"reason for locking\")),\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree(worktrees, prefix, av[0]);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\n+\told_reason = is_worktree_locked(wt);\n+\tif (old_reason) {\n+\t\tif (*old_reason)\n+\t\t\tdie(_(\"'%s' is already locked, reason: %s\"),\n+\t\t\t    av[0], old_reason);\n+\t\tdie(_(\"'%s' is already locked\"), av[0]);\n+\t}\n+\n+\twrite_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n+\t\t   \"%s\", reason);\n+\tfree_worktrees(worktrees);\n+\treturn 0;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -478,5 +514,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\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+\tif (!strcmp(av[1], \"lock\"))\n+\t\treturn lock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex fb0f6e3..61ef18d 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2598,7 +2598,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list prune\"\n+\tlocal subcommands=\"add list lock prune\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\n@@ -2610,6 +2610,9 @@ _git_worktree ()\n \t\tlist,--*)\n \t\t\t__gitcomp \"--porcelain\"\n \t\t\t;;\n+\t\tlock,--*)\n+\t\t\t__gitcomp \"--reason\"\n+\t\t\t;;\n \t\tprune,--*)\n \t\t\t__gitcomp \"--dry-run --expire --verbose\"\n \t\t\t;;\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nnew file mode 100755\nindex 0000000..87afc2e\n--- /dev/null\n+++ b/t/t2028-worktree-move.sh\n@@ -0,0 +1,48 @@\n+#!/bin/sh\n+\n+test_description='test git worktree move, remove, lock and unlock'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit init &&\n+\tgit worktree add source &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/source\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'lock main worktree' '\n+\ttest_must_fail git worktree lock .\n+'\n+\n+test_expect_success 'lock linked worktree' '\n+\tgit worktree lock --reason hahaha source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock linked worktree from another worktree' '\n+\trm .git/worktrees/source/locked &&\n+\tgit worktree add elsewhere &&\n+\tgit -C elsewhere worktree lock --reason hahaha ../source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock worktree twice' '\n+\ttest_must_fail git worktree lock source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock worktree twice (from the locked worktree)' '\n+\ttest_must_fail git -C source worktree lock . &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_done\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"288233","messageId":"20160603121944.28980-6-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160603121944.28980-1-pclouds@gmail.com","subject":"[PATCH v4 5/6] worktree: add \"unlock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-03T12:19:43Z","receivedAt":"2016-06-03T12:19:43Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         |  5 +++++\n builtin/worktree.c                     | 28 ++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  2 +-\n t/t2028-worktree-move.sh               | 14 ++++++++++++++\n 4 files changed, 48 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex d233d61..317c7d2 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n 'git worktree list' [--porcelain]\n 'git worktree lock' [--reason <string>] <worktree>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n+'git worktree unlock' <worktree>\n \n DESCRIPTION\n -----------\n@@ -74,6 +75,10 @@ prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n \n+unlock::\n+\n+Unlock a working tree, allowing it to be pruned, moved or deleted.\n+\n OPTIONS\n -------\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex b686c7f..6d30c55 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {\n \tN_(\"git worktree list [<options>]\"),\n \tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n+\tN_(\"git worktree unlock <path>\"),\n \tNULL\n };\n \n@@ -498,6 +499,31 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int unlock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tint ret;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree(worktrees, prefix, av[0]);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\tif (!is_worktree_locked(wt))\n+\t\tdie(_(\"'%s' is not locked\"), av[0]);\n+\tret = unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n+\tfree_worktrees(worktrees);\n+\treturn ret;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -516,5 +542,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn list(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"lock\"))\n \t\treturn lock_worktree(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"unlock\"))\n+\t\treturn unlock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 61ef18d..d90e571 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2598,7 +2598,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list lock prune\"\n+\tlocal subcommands=\"add list lock prune unlock\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex 87afc2e..68d3fe8 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -45,4 +45,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' '\n \ttest_cmp expected .git/worktrees/source/locked\n '\n \n+test_expect_success 'unlock main worktree' '\n+\ttest_must_fail git worktree unlock .\n+'\n+\n+test_expect_success 'unlock linked worktree' '\n+\tgit worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'unlock worktree twice' '\n+\ttest_must_fail git worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n test_done\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"288234","messageId":"20160603121944.28980-7-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160603121944.28980-1-pclouds@gmail.com","subject":"[PATCH v4 6/6] worktree.c: find_worktree() search by path suffix","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-03T12:19:44Z","receivedAt":"2016-06-03T12:19:44Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This allows the user to do something like \"worktree lock foo\" or\n\"worktree lock to/foo\" instead of \"worktree lock /long/path/to/foo\" if\nit's unambiguous. With completion support it could be quite convenient.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt |  5 +++++\n worktree.c                     | 29 +++++++++++++++++++++++++++++\n 2 files changed, 34 insertions(+)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 317c7d2..0aeb020 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -130,6 +130,11 @@ OPTIONS\n <worktree>::\n \tWorking trees can be identified by path, either relative or\n \tabsolute.\n++\n+If the last path components in the working tree's path is unique among\n+working trees, it can be used to identify worktrees. For example if\n+you only have to working trees at \"/abc/def/ghi\" and \"/abc/def/ggg\",\n+then \"ghi\" or \"def/ghi\" is enough to point to the former working tree.\n \n DETAILS\n -------\ndiff --git a/worktree.c b/worktree.c\nindex ee592a4..fd5c768 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -220,12 +220,41 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n+static struct worktree *find_worktree_by_suffix(struct worktree **list,\n+\t\t\t\t\t\tconst char *suffix)\n+{\n+\tstruct worktree *found = NULL;\n+\tint nr_found = 0, suffixlen;\n+\n+\tsuffixlen = strlen(suffix);\n+\tif (!suffixlen)\n+\t\treturn NULL;\n+\n+\tfor (; *list && nr_found < 2; list++) {\n+\t\tconst char\t*path\t = (*list)->path;\n+\t\tint\t\t pathlen = strlen(path);\n+\t\tint\t\t start\t = pathlen - suffixlen;\n+\n+\t\t/* suffix must start at directory boundary */\n+\t\tif ((!start || (start > 0 && is_dir_sep(path[start - 1]))) &&\n+\t\t    !fspathcmp(suffix, path + start)) {\n+\t\t\tfound = *list;\n+\t\t\tnr_found++;\n+\t\t}\n+\t}\n+\treturn nr_found == 1 ? found : NULL;\n+}\n+\n struct worktree *find_worktree(struct worktree **list,\n \t\t\t       const char *prefix,\n \t\t\t       const char *arg)\n {\n+\tstruct worktree *wt;\n \tchar *path;\n \n+\tif ((wt = find_worktree_by_suffix(list, arg)))\n+\t\treturn wt;\n+\n \targ = prefix_filename(prefix, strlen(prefix), arg);\n \tpath = xstrdup(real_path(arg));\n \tfor (; *list; list++)\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"288255","messageId":"57519BA3.207@ramsayjones.plus.com","threadId":"42034","inReplyTo":"20160603121944.28980-2-pclouds@gmail.com","subject":"Re: [PATCH v4 1/6] worktree.c: add find_worktree()","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2016-06-03T15:00:51Z","receivedAt":"2016-06-03T15:00:51Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 03/06/16 13:19, Nguyễn Thái Ngọc Duy wrote:\n> So far we haven't needed to identify an existing worktree from command\n> line. Future commands such as lock or move will need it. The current\n> implementation identifies worktrees by path (*). In future, the function\n> could learn to identify by $(basename $path) or tags...\n> \n> (*) We could probably go cheaper with comparing inode number (and\n> probably more reliable than paths when unicode enters the game). But not\n> all systems have good inode that so let's stick to something simple for\n> now.\n> \n> Helped-by: Eric Sunshine <sunshine@sunshineco.com>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  worktree.c | 15 +++++++++++++++\n>  worktree.h |  8 ++++++++\n>  2 files changed, 23 insertions(+)\n> \n> diff --git a/worktree.c b/worktree.c\n> index e2a94e0..554f566 100644\n> --- a/worktree.c\n> +++ b/worktree.c\n> @@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n>  \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n>  }\n>  \n> +struct worktree *find_worktree(struct worktree **list,\n> +\t\t\t       const char *prefix,\n> +\t\t\t       const char *arg)\n> +{\n> +\tchar *path;\n> +\n> +\targ = prefix_filename(prefix, strlen(prefix), arg);\n> +\tpath = xstrdup(real_path(arg));\n> +\tfor (; *list; list++)\n> +\t\tif (!fspathcmp(path, real_path((*list)->path)))\n\nThe results of the call to real_path() should probably be cached\nin the worktree structure, since real_path() is relatively expensive\n(it calls chdir(), lstat(), readlink() etc.), so you don't want to\nre-compute the same result time-after-time ...\n\n> +\t\t\tbreak;\n> +\tfree(path);\n> +\treturn *list;\n> +}\n> +\n\nATB,\nRamsay Jones\n"},{"id":"288257","messageId":"xmqqoa7iqoh5.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"CACsJy8BF_KKXdMNW_aOs522wBbW9BQhbrZ_0hx+f2MCW-VPUzg@mail.gmail.com","subject":"Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-06-03T15:30:30Z","receivedAt":"2016-06-03T15:30:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> basename() does (or I think so because Windows has its own version).\n> worktree->path always uses '/' but the command line option can come\n> with either '/' or '\\'. Probably safest to accept both.\n\nOK.  Going beyond basename() was merely \"This might be an easy\nnice-to-have enhancement\", not \"basename() is not sufficient because\nof such and such issues\", so the patch is fine as-is.\n\nThanks.\n"},{"id":"288362","messageId":"xmqqporxzgaq.fsf@gitster.mtv.corp.google.com","threadId":"42034","inReplyTo":"20160603121944.28980-1-pclouds@gmail.com","subject":"Re: [PATCH v4 0/6] worktree lock/unlock","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-06-04T05:14:37Z","receivedAt":"2016-06-04T05:14: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> v4\n>\n>  - fixes some documentation bugs in v3\n>\n>  - updates is_main_worktree commit message a bit\n>\n>  - brings back is_worktree_locked() as API with value lazily obtained\n>    and cached in worktree->lock_reason and fixes a memory leak\n>\n>  - extends basename search to path suffix search. I moved this patch\n>    to the bottom so that the relevant documentation part is included\n>    in the patch (previously it was part of the \"add lock command\"\n>    patch).\n>\n> This series is based on nd/worktree-cleanup-post-head-protection\n\nWill replace the one in 'pu'.  A few things I noticed:\n\n * Documentation and messages talk about \"working tree\" and \"working\n   directory\" when they refer to \"worktree\", which is not a normal\n   \"working tree\".  I think this was done as an attempt to avoid\n   being too precise and technical in phrasing, but I suspect that\n   is a false reader-friendliness.  At least, \"working directory\" in\n   messages should be \"worktree\", e.g.\n\n\twt = find_worktree(...);\n\tif (!wt)\n        \tdie(\"'%s' is not a working directory\", av[0]);\n\n   does not make much sense (imagine \"git wt lock .\" and being told\n   that '.' is not a working directory).\n\n * You are probably trying to be frugal by not introducing a\n   separate bit for is_lock_checked to struct worktre and instead\n   using a sentinel value for lock_reason, but I think that is a\n   false economy, after seeing that you have to cast away constness\n   in number of places and having to be extra careful not to free\n   that sentinel value.\n\nThanks.\n"},{"id":"288389","messageId":"alpine.DEB.2.20.1606050909240.27293@virtualbox","threadId":"42034","inReplyTo":"CACsJy8BF_KKXdMNW_aOs522wBbW9BQhbrZ_0hx+f2MCW-VPUzg@mail.gmail.com","subject":"Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-06-05T07:15:16Z","receivedAt":"2016-06-05T07:15:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 3 Jun 2016, Duy Nguyen wrote:\n\n> On Thu, Jun 2, 2016 at 11:49 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>\n> > Does \"basename()\" used here know '/' and '\\' can both be a directory\n> > separator, or does worktree->path have a normalized representation of\n> > the path, i.e. '/' is the only directory separator?\n> \n> basename() does (or I think so because Windows has its own version).\n> worktree->path always uses '/' but the command line option can come with\n> either '/' or '\\'. Probably safest to accept both.\n\nIt is very easy to see what the Windows version of basename() does by\nlooking at our very own test code:\n\n\thttps://github.com/git/git/blob/v2.8.3/test-path-utils.c#L71-L110\n\nShort answer: basename() knows that both '/' and '\\' are directory\nseparators.\n\nCiao,\nDscho\n"},{"id":"289102","messageId":"20160613121826.21631-2-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160613121826.21631-1-pclouds@gmail.com","subject":"[PATCH v5 1/6] worktree.c: add find_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-13T12:18:21Z","receivedAt":"2016-06-16T02:19:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"So far we haven't needed to identify an existing worktree from command\nline. Future commands such as lock or move will need it. The current\nimplementation identifies worktrees by path (*). In future, the function\ncould learn to identify by $(basename $path) or tags...\n\n(*) We could probably go cheaper with comparing inode number (and\nprobably more reliable than paths when unicode enters the game). But not\nall systems have good inode that so let's stick to something simple for\nnow.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 15 +++++++++++++++\n worktree.h |  8 ++++++++\n 2 files changed, 23 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex f4a4f38..0782e00 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n+struct worktree *find_worktree(struct worktree **list,\n+\t\t\t       const char *prefix,\n+\t\t\t       const char *arg)\n+{\n+\tchar *path;\n+\n+\targ = prefix_filename(prefix, strlen(prefix), arg);\n+\tpath = xstrdup(real_path(arg));\n+\tfor (; *list; list++)\n+\t\tif (!fspathcmp(path, real_path((*list)->path)))\n+\t\t\tbreak;\n+\tfree(path);\n+\treturn *list;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 1394909..7ad15da 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -29,6 +29,14 @@ extern struct worktree **get_worktrees(void);\n  */\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n+/*\n+ * Search a worktree that can be unambiguously identified by\n+ * \"arg\". \"prefix\" must not be NULL.\n+ */\n+extern struct worktree *find_worktree(struct worktree **list,\n+\t\t\t\t      const char *prefix,\n+\t\t\t\t      const char *arg);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"289103","messageId":"20160613121826.21631-3-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160613121826.21631-1-pclouds@gmail.com","subject":"[PATCH v5 2/6] worktree.c: add is_main_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-13T12:18:22Z","receivedAt":"2016-06-16T02:19:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Main worktree _is_ different. You can lock (*) a linked worktree but not\nthe main one, for example. Provide an API for checking that.\n\n(*) Add the file $GIT_DIR/worktrees/xxx/locked to avoid worktree xxx\nfrom being removed or moved.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 5 +++++\n worktree.h | 5 +++++\n 2 files changed, 10 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 0782e00..12a766a 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -229,6 +229,11 @@ struct worktree *find_worktree(struct worktree **list,\n \treturn *list;\n }\n \n+int is_main_worktree(const struct worktree *wt)\n+{\n+\treturn !wt->id;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 7ad15da..e1c4715 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,\n \t\t\t\t      const char *prefix,\n \t\t\t\t      const char *arg);\n \n+/*\n+ * Return true if the given worktree is the main one.\n+ */\n+extern int is_main_worktree(const struct worktree *wt);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"289104","messageId":"20160613121826.21631-4-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160613121826.21631-1-pclouds@gmail.com","subject":"[PATCH v5 3/6] worktree.c: add is_worktree_locked()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-13T12:18:23Z","receivedAt":"2016-06-16T02:19:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We need this later to avoid double locking a worktree, or unlocking one\nwhen it's not even locked.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 28 ++++++++++++++++++++++++++++\n worktree.h |  8 ++++++++\n 2 files changed, 36 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 12a766a..2bcfff3 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -13,6 +13,7 @@ void free_worktrees(struct worktree **worktrees)\n \t\tfree(worktrees[i]->path);\n \t\tfree(worktrees[i]->id);\n \t\tfree(worktrees[i]->head_ref);\n+\t\tfree(worktrees[i]->lock_reason);\n \t\tfree(worktrees[i]);\n \t}\n \tfree (worktrees);\n@@ -98,6 +99,8 @@ static struct worktree *get_main_worktree(void)\n \tworktree->is_detached = is_detached;\n \tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n+\tworktree->lock_reason = NULL;\n+\tworktree->lock_reason_valid = 0;\n \n done:\n \tstrbuf_release(&path);\n@@ -143,6 +146,8 @@ static struct worktree *get_linked_worktree(const char *id)\n \tworktree->is_detached = is_detached;\n \tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n+\tworktree->lock_reason = NULL;\n+\tworktree->lock_reason_valid = 0;\n \n done:\n \tstrbuf_release(&path);\n@@ -234,6 +239,29 @@ int is_main_worktree(const struct worktree *wt)\n \treturn !wt->id;\n }\n \n+const char *is_worktree_locked(struct worktree *wt)\n+{\n+\tassert(!is_main_worktree(wt));\n+\n+\tif (!wt->lock_reason_valid) {\n+\t\tstruct strbuf path = STRBUF_INIT;\n+\n+\t\tstrbuf_addstr(&path, worktree_git_path(wt, \"locked\"));\n+\t\tif (file_exists(path.buf)) {\n+\t\t\tstruct strbuf lock_reason = STRBUF_INIT;\n+\t\t\tif (strbuf_read_file(&lock_reason, path.buf, 0) < 0)\n+\t\t\t\tdie_errno(_(\"failed to read '%s'\"), path.buf);\n+\t\t\tstrbuf_trim(&lock_reason);\n+\t\t\twt->lock_reason = strbuf_detach(&lock_reason, NULL);\n+\t\t} else\n+\t\t\twt->lock_reason = NULL;\n+\t\twt->lock_reason_valid = 1;\n+\t\tstrbuf_release(&path);\n+\t}\n+\n+\treturn wt->lock_reason;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex e1c4715..90e1311 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -5,10 +5,12 @@ struct worktree {\n \tchar *path;\n \tchar *id;\n \tchar *head_ref;\n+\tchar *lock_reason;\t/* internal use */\n \tunsigned char head_sha1[20];\n \tint is_detached;\n \tint is_bare;\n \tint is_current;\n+\tint lock_reason_valid;\n };\n \n /* Functions for acting on the information about worktrees. */\n@@ -42,6 +44,12 @@ extern struct worktree *find_worktree(struct worktree **list,\n  */\n extern int is_main_worktree(const struct worktree *wt);\n \n+/*\n+ * Return the reason string if the given worktree is locked or NULL\n+ * otherwise.\n+ */\n+extern const char *is_worktree_locked(struct worktree *wt);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"289105","messageId":"20160613121826.21631-1-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160603121944.28980-1-pclouds@gmail.com","subject":"[PATCH v5 0/6] worktree lock/unlock","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-13T12:18:20Z","receivedAt":"2016-06-16T02:19:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"v5 fixes some error messages mentioning \"working directory\" instead of\n\"working tree\" and split the double use of \"lock_reason\" field in\n\"struct worktree\". This series depends on\nnd/worktree-cleanup-post-head-protection.\n\nDiff from v4\n\n-- 8< --\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex cb5026d..4877421 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -478,9 +478,9 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n \tworktrees = get_worktrees();\n \twt = find_worktree(worktrees, prefix, av[0]);\n \tif (!wt)\n-\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\t\tdie(_(\"'%s' is not a working tree\"), av[0]);\n \tif (is_main_worktree(wt))\n-\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\t\tdie(_(\"The main working tree cannot be locked or unlocked\"));\n \n \told_reason = is_worktree_locked(wt);\n \tif (old_reason) {\n@@ -511,9 +511,9 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)\n \tworktrees = get_worktrees();\n \twt = find_worktree(worktrees, prefix, av[0]);\n \tif (!wt)\n-\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\t\tdie(_(\"'%s' is not a working tree\"), av[0]);\n \tif (is_main_worktree(wt))\n-\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\t\tdie(_(\"The main working tree cannot be locked or unlocked\"));\n \tif (!is_worktree_locked(wt))\n \t\tdie(_(\"'%s' is not locked\"), av[0]);\n \tret = unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\ndiff --git a/worktree.c b/worktree.c\nindex b16262b..2107c06 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -5,8 +5,6 @@\n #include \"dir.h\"\n #include \"wt-status.h\"\n \n-static const char *lock_field_uninitialized = \"value is not important\";\n-\n void free_worktrees(struct worktree **worktrees)\n {\n \tint i = 0;\n@@ -15,8 +13,7 @@ void free_worktrees(struct worktree **worktrees)\n \t\tfree(worktrees[i]->path);\n \t\tfree(worktrees[i]->id);\n \t\tfree(worktrees[i]->head_ref);\n-\t\tif (worktrees[i]->lock_reason != lock_field_uninitialized)\n-\t\t\tfree(worktrees[i]->lock_reason);\n+\t\tfree(worktrees[i]->lock_reason);\n \t\tfree(worktrees[i]);\n \t}\n \tfree (worktrees);\n@@ -102,7 +99,8 @@ static struct worktree *get_main_worktree(void)\n \tworktree->is_detached = is_detached;\n \tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n-\tworktree->lock_reason = (char *)lock_field_uninitialized;\n+\tworktree->lock_reason = NULL;\n+\tworktree->lock_reason_valid = 0;\n \n done:\n \tstrbuf_release(&path);\n@@ -148,7 +146,8 @@ static struct worktree *get_linked_worktree(const char *id)\n \tworktree->is_detached = is_detached;\n \tworktree->is_current = 0;\n \tadd_head_info(&head_ref, worktree);\n-\tworktree->lock_reason = (char *)lock_field_uninitialized;\n+\tworktree->lock_reason = NULL;\n+\tworktree->lock_reason_valid = 0;\n \n done:\n \tstrbuf_release(&path);\n@@ -271,7 +270,9 @@ int is_main_worktree(const struct worktree *wt)\n \n const char *is_worktree_locked(struct worktree *wt)\n {\n-\tif (wt->lock_reason == lock_field_uninitialized) {\n+\tassert(!is_main_worktree(wt));\n+\n+\tif (!wt->lock_reason_valid) {\n \t\tstruct strbuf path = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&path, worktree_git_path(wt, \"locked\"));\n@@ -283,6 +284,7 @@ const char *is_worktree_locked(struct worktree *wt)\n \t\t\twt->lock_reason = strbuf_detach(&lock_reason, NULL);\n \t\t} else\n \t\t\twt->lock_reason = NULL;\n+\t\twt->lock_reason_valid = 1;\n \t\tstrbuf_release(&path);\n \t}\n \ndiff --git a/worktree.h b/worktree.h\nindex 263b61d..90e1311 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -10,6 +10,7 @@ struct worktree {\n \tint is_detached;\n \tint is_bare;\n \tint is_current;\n+\tint lock_reason_valid;\n };\n \n /* Functions for acting on the information about worktrees. */\n-- >8 --\n\nNguyễn Thái Ngọc Duy (6):\n  worktree.c: add find_worktree()\n  worktree.c: add is_main_worktree()\n  worktree.c: add is_worktree_locked()\n  worktree: add \"lock\" command\n  worktree: add \"unlock\" command\n  worktree.c: find_worktree() search by path suffix\n\n Documentation/git-worktree.txt         | 36 +++++++++++++---\n builtin/worktree.c                     | 66 +++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 ++-\n t/t2028-worktree-move.sh (new +x)      | 62 +++++++++++++++++++++++++++\n worktree.c                             | 77 ++++++++++++++++++++++++++++++++++\n worktree.h                             | 21 ++++++++++\n 6 files changed, 260 insertions(+), 7 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"289106","messageId":"20160613121826.21631-5-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160613121826.21631-1-pclouds@gmail.com","subject":"[PATCH v5 4/6] worktree: add \"lock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-13T12:18:24Z","receivedAt":"2016-06-16T02:19:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Helped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         | 26 +++++++++++++-----\n builtin/worktree.c                     | 38 +++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 +++-\n t/t2028-worktree-move.sh (new +x)      | 48 ++++++++++++++++++++++++++++++++++\n 4 files changed, 110 insertions(+), 7 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 27feff6..b49b25b 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] [--checkout] [-b <new-branch>] <path> [<branch>]\n 'git worktree list' [--porcelain]\n+'git worktree lock' [--reason <string>] <worktree>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n \n DESCRIPTION\n@@ -38,9 +39,8 @@ section \"DETAILS\" for more information.\n \n If a linked working tree is stored on a portable device or network share\n which is not always mounted, you can prevent its administrative files from\n-being pruned by creating a file named 'locked' alongside the other\n-administrative files, optionally containing a plain text reason that\n-pruning should be suppressed. See section \"DETAILS\" for more information.\n+being pruned by issuing the `git worktree lock` command, optionally\n+specifying `--reason` to explain why the working tree is locked.\n \n COMMANDS\n --------\n@@ -61,6 +61,14 @@ each of the linked worktrees.  The output details include if the worktree is\n bare, the revision currently checked out, and the branch currently checked out\n (or 'detached HEAD' if none).\n \n+lock::\n+\n+If a working tree is on a portable device or network share which\n+is not always mounted, lock it to prevent its administrative\n+files from being pruned automatically. This also prevents it from\n+being moved or deleted. Optionally, specify a reason for the lock\n+with `--reason`.\n+\n prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n@@ -110,6 +118,13 @@ OPTIONS\n --expire <time>::\n \tWith `prune`, only expire unused working trees older than <time>.\n \n+--reason <string>::\n+\tWith `lock`, an explanation why the working tree is locked.\n+\n+<worktree>::\n+\tWorking trees can be identified by path, either relative or\n+\tabsolute.\n+\n DETAILS\n -------\n Each linked working tree has a private sub-directory in the repository's\n@@ -150,7 +165,8 @@ instead.\n \n To prevent a $GIT_DIR/worktrees entry from being pruned (which\n can be useful in some situations, such as when the\n-entry's working tree is stored on a portable device), add a file named\n+entry's working tree is stored on a portable device), use the\n+`git worktree lock` command, which adds a file named\n 'locked' to the entry's directory. The file contains the reason in\n plain text. For example, if a linked working tree's `.git` file points\n to `/path/main/.git/worktrees/test-next` then a file named\n@@ -226,8 +242,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-- `lock` to prevent automatic pruning of administrative files (for instance,\n-  for a working tree on a portable device)\n \n GIT\n ---\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex f9dac37..3b9220f 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -14,6 +14,7 @@\n static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> [<branch>]\"),\n \tN_(\"git worktree list [<options>]\"),\n+\tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n \tNULL\n };\n@@ -459,6 +460,41 @@ static int list(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int lock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tconst char *reason = \"\", *old_reason;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"reason\", &reason, N_(\"string\"),\n+\t\t\t   N_(\"reason for locking\")),\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree(worktrees, prefix, av[0]);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working tree\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"The main working tree cannot be locked or unlocked\"));\n+\n+\told_reason = is_worktree_locked(wt);\n+\tif (old_reason) {\n+\t\tif (*old_reason)\n+\t\t\tdie(_(\"'%s' is already locked, reason: %s\"),\n+\t\t\t    av[0], old_reason);\n+\t\tdie(_(\"'%s' is already locked\"), av[0]);\n+\t}\n+\n+\twrite_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n+\t\t   \"%s\", reason);\n+\tfree_worktrees(worktrees);\n+\treturn 0;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -475,5 +511,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\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+\tif (!strcmp(av[1], \"lock\"))\n+\t\treturn lock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 951a186..f88727d 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2597,7 +2597,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list prune\"\n+\tlocal subcommands=\"add list lock prune\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\n@@ -2609,6 +2609,9 @@ _git_worktree ()\n \t\tlist,--*)\n \t\t\t__gitcomp \"--porcelain\"\n \t\t\t;;\n+\t\tlock,--*)\n+\t\t\t__gitcomp \"--reason\"\n+\t\t\t;;\n \t\tprune,--*)\n \t\t\t__gitcomp \"--dry-run --expire --verbose\"\n \t\t\t;;\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nnew file mode 100755\nindex 0000000..87afc2e\n--- /dev/null\n+++ b/t/t2028-worktree-move.sh\n@@ -0,0 +1,48 @@\n+#!/bin/sh\n+\n+test_description='test git worktree move, remove, lock and unlock'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit init &&\n+\tgit worktree add source &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/source\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'lock main worktree' '\n+\ttest_must_fail git worktree lock .\n+'\n+\n+test_expect_success 'lock linked worktree' '\n+\tgit worktree lock --reason hahaha source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock linked worktree from another worktree' '\n+\trm .git/worktrees/source/locked &&\n+\tgit worktree add elsewhere &&\n+\tgit -C elsewhere worktree lock --reason hahaha ../source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock worktree twice' '\n+\ttest_must_fail git worktree lock source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock worktree twice (from the locked worktree)' '\n+\ttest_must_fail git -C source worktree lock . &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_done\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"289107","messageId":"20160613121826.21631-6-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160613121826.21631-1-pclouds@gmail.com","subject":"[PATCH v5 5/6] worktree: add \"unlock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-13T12:18:25Z","receivedAt":"2016-06-16T02:19:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         |  5 +++++\n builtin/worktree.c                     | 28 ++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  2 +-\n t/t2028-worktree-move.sh               | 14 ++++++++++++++\n 4 files changed, 48 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex b49b25b..27330c5 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n 'git worktree list' [--porcelain]\n 'git worktree lock' [--reason <string>] <worktree>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n+'git worktree unlock' <worktree>\n \n DESCRIPTION\n -----------\n@@ -73,6 +74,10 @@ prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n \n+unlock::\n+\n+Unlock a working tree, allowing it to be pruned, moved or deleted.\n+\n OPTIONS\n -------\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 3b9220f..4877421 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {\n \tN_(\"git worktree list [<options>]\"),\n \tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n+\tN_(\"git worktree unlock <path>\"),\n \tNULL\n };\n \n@@ -495,6 +496,31 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int unlock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tint ret;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree(worktrees, prefix, av[0]);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working tree\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"The main working tree cannot be locked or unlocked\"));\n+\tif (!is_worktree_locked(wt))\n+\t\tdie(_(\"'%s' is not locked\"), av[0]);\n+\tret = unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n+\tfree_worktrees(worktrees);\n+\treturn ret;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -513,5 +539,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn list(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"lock\"))\n \t\treturn lock_worktree(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"unlock\"))\n+\t\treturn unlock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex f88727d..0e3841d 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2597,7 +2597,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list lock prune\"\n+\tlocal subcommands=\"add list lock prune unlock\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex 87afc2e..68d3fe8 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -45,4 +45,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' '\n \ttest_cmp expected .git/worktrees/source/locked\n '\n \n+test_expect_success 'unlock main worktree' '\n+\ttest_must_fail git worktree unlock .\n+'\n+\n+test_expect_success 'unlock linked worktree' '\n+\tgit worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'unlock worktree twice' '\n+\ttest_must_fail git worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n test_done\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"289108","messageId":"20160613121826.21631-7-pclouds@gmail.com","threadId":"42034","inReplyTo":"20160613121826.21631-1-pclouds@gmail.com","subject":"[PATCH v5 6/6] worktree.c: find_worktree() search by path suffix","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-13T12:18:26Z","receivedAt":"2016-06-16T02:19:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This allows the user to do something like \"worktree lock foo\" or\n\"worktree lock to/foo\" instead of \"worktree lock /long/path/to/foo\" if\nit's unambiguous.\n\nWith completion support it could be quite convenient. While this base\nname search can be done in the same worktree iteration loop, the code is\nsplit into a separate function for clarity.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt |  5 +++++\n worktree.c                     | 29 +++++++++++++++++++++++++++++\n 2 files changed, 34 insertions(+)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 27330c5..7850dee 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -129,6 +129,11 @@ OPTIONS\n <worktree>::\n \tWorking trees can be identified by path, either relative or\n \tabsolute.\n++\n+If the last path components in the working tree's path is unique among\n+working trees, it can be used to identify worktrees. For example if\n+you only have to working trees at \"/abc/def/ghi\" and \"/abc/def/ggg\",\n+then \"ghi\" or \"def/ghi\" is enough to point to the former working tree.\n \n DETAILS\n -------\ndiff --git a/worktree.c b/worktree.c\nindex 2bcfff3..2107c06 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -219,12 +219,41 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn git_common_path(\"worktrees/%s\", wt->id);\n }\n \n+static struct worktree *find_worktree_by_suffix(struct worktree **list,\n+\t\t\t\t\t\tconst char *suffix)\n+{\n+\tstruct worktree *found = NULL;\n+\tint nr_found = 0, suffixlen;\n+\n+\tsuffixlen = strlen(suffix);\n+\tif (!suffixlen)\n+\t\treturn NULL;\n+\n+\tfor (; *list && nr_found < 2; list++) {\n+\t\tconst char\t*path\t = (*list)->path;\n+\t\tint\t\t pathlen = strlen(path);\n+\t\tint\t\t start\t = pathlen - suffixlen;\n+\n+\t\t/* suffix must start at directory boundary */\n+\t\tif ((!start || (start > 0 && is_dir_sep(path[start - 1]))) &&\n+\t\t    !fspathcmp(suffix, path + start)) {\n+\t\t\tfound = *list;\n+\t\t\tnr_found++;\n+\t\t}\n+\t}\n+\treturn nr_found == 1 ? found : NULL;\n+}\n+\n struct worktree *find_worktree(struct worktree **list,\n \t\t\t       const char *prefix,\n \t\t\t       const char *arg)\n {\n+\tstruct worktree *wt;\n \tchar *path;\n \n+\tif ((wt = find_worktree_by_suffix(list, arg)))\n+\t\treturn wt;\n+\n \targ = prefix_filename(prefix, strlen(prefix), arg);\n \tpath = xstrdup(real_path(arg));\n \tfor (; *list; list++)\n-- \n2.8.2.524.g6ff3d78\n"},{"id":"289109","messageId":"CACsJy8BC=Fx7f6QAg_u_4HuwkV_-wvm9se5fU-kkp0mnhUpC=A@mail.gmail.com","threadId":"42034","inReplyTo":"57519BA3.207@ramsayjones.plus.com","subject":"Re: [PATCH v4 1/6] worktree.c: add find_worktree()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-13T12:22:29Z","receivedAt":"2016-06-16T02:19:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Jun 3, 2016 at 10:00 PM, Ramsay Jones\n<ramsay@ramsayjones.plus.com> wrote:\n>\n>\n> On 03/06/16 13:19, Nguyễn Thái Ngọc Duy wrote:\n>> So far we haven't needed to identify an existing worktree from command\n>> line. Future commands such as lock or move will need it. The current\n>> implementation identifies worktrees by path (*). In future, the function\n>> could learn to identify by $(basename $path) or tags...\n>>\n>> (*) We could probably go cheaper with comparing inode number (and\n>> probably more reliable than paths when unicode enters the game). But not\n>> all systems have good inode that so let's stick to something simple for\n>> now.\n>>\n>> Helped-by: Eric Sunshine <sunshine@sunshineco.com>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>>  worktree.c | 15 +++++++++++++++\n>>  worktree.h |  8 ++++++++\n>>  2 files changed, 23 insertions(+)\n>>\n>> diff --git a/worktree.c b/worktree.c\n>> index e2a94e0..554f566 100644\n>> --- a/worktree.c\n>> +++ b/worktree.c\n>> @@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n>>               return git_common_path(\"worktrees/%s\", wt->id);\n>>  }\n>>\n>> +struct worktree *find_worktree(struct worktree **list,\n>> +                            const char *prefix,\n>> +                            const char *arg)\n>> +{\n>> +     char *path;\n>> +\n>> +     arg = prefix_filename(prefix, strlen(prefix), arg);\n>> +     path = xstrdup(real_path(arg));\n>> +     for (; *list; list++)\n>> +             if (!fspathcmp(path, real_path((*list)->path)))\n>\n> The results of the call to real_path() should probably be cached\n> in the worktree structure, since real_path() is relatively expensive\n> (it calls chdir(), lstat(), readlink() etc.), so you don't want to\n> re-compute the same result time-after-time ...\n\nUrgh.. I missed this after sending out v5. Because find_worktree is\nprobably called once or twice per process, I don't think we need to\noptimize this yet. If nr. worktrees goes up to hundreds then this is\none of many items we need to do to make worktree list fast.\n-- \nDuy\n"},{"id":"290226","messageId":"914a6ad8-94d7-d26e-8c6f-f9656d912818@web.de","threadId":"42034","inReplyTo":"20160603121944.28980-1-pclouds@gmail.com","subject":"Re: [PATCH v4 0/6] worktree lock/unlock","fromName":"Torsten Bögershausen","fromEmail":"tboegi@web.de","sentAt":"2016-06-27T09:57:47Z","receivedAt":"2016-06-27T09:58:13Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":">On 03.06.16 14:19, Nguyễn Thái Ngọc Duy wrote:\n\nMinor problem:\nt2028 fails, when the test is run from a directory that is\n\na softlink.\n(In my case \n/Users/tb/projects/git/git.pu\nis a softlink to\n/Users/tb/NoBackup/projects/git/git.pu/\n\n\n[master (root-commit) 2519212] init\n Author: A U Thor <author@example.com>\n 1 file changed, 1 insertion(+)\n create mode 100644 init.t\nPreparing source (identifier source)\nHEAD is now at 2519212 init\n--- expected    2016-06-27 09:50:19.000000000 +0000\n+++ actual      2016-06-27 09:50:19.000000000 +0000\n@@ -1,2 +1,2 @@\n-worktree /Users/tb/projects/git/git.pu/t/trash directory.t2028-worktree-move\n-worktree /Users/tb/projects/git/git.pu/t/trash directory.t2028-worktree-move/source\n+worktree /Users/tb/NoBackup/projects/git/git.pu/t/trash directory.t2028-worktree-move\n+worktree /Users/tb/NoBackup/projects/git/git.pu/t/trash directory.t2028-worktree-move/source\nnot ok 1 - setup\n#\n\n"},{"id":"290599","messageId":"20160630160136.4819-1-pclouds@gmail.com","threadId":"42034","inReplyTo":"914a6ad8-94d7-d26e-8c6f-f9656d912818@web.de","subject":"[PATCH] fixup! worktree: add \"lock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-06-30T16:01:36Z","receivedAt":"2016-06-30T16:03:21Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Torsten, this seems to fix the symlink problem for me. How many times\n have I got similar reports from you and still managed to forget ...\n\n t/t2028-worktree-move.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex 68d3fe8..8298aaf 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -9,8 +9,8 @@ test_expect_success 'setup' '\n \tgit worktree add source &&\n \tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n \tcat <<-EOF >expected &&\n-\tworktree $TRASH_DIRECTORY\n-\tworktree $TRASH_DIRECTORY/source\n+\tworktree $(pwd)\n+\tworktree $(pwd)/source\n \tEOF\n \ttest_cmp expected actual\n '\n-- \n2.8.2.531.gd073806\n\n"}]}