possible to checkout same branch in different worktree
Hi,
the checkout command prevents me from checking out a branch in the current worktree if it is already checked out in another worktree.
However, if I rebase the branch in the current worktree onto the branch in the other worktree, I end up in a situation where the same branch is checked out twice in the two worktrees.
- Reto
--
PGP
Key ID = 0x67D30264
Fingerprint = E235 F740 4419 5B4B 29EC 3437 CBF2 2CB7 67D3 0264
Quoted 6 lines · Reto Hablützel
the checkout command prevents me from checking out a branch in the current worktree if it is already checked out in another worktree.
However, if I rebase the branch in the current worktree onto the branch in the other worktree, I end up in a situation where the same branch is checked out twice in the two worktrees.
I agree that any end-user facing subcommand like "git rebase", even if it is not "git checkout", should refuse to work on and update a branch that is checked out elsewhere. Otherwise it will end up causing confusion.
Thanks for a report. The "multiple worktree" feature is still highly experimental and those who are working on it would appreciate your reporting issues with it.
Quoted 5 lines · Junio C Hamano
Quoted 3 lines · Reto Hablützel
the checkout command prevents me from checking out a branch in the current worktree if it is already checked out in another worktree.
Quoted 8 lines
Quoted 3 lines
However, if I rebase the branch in the current worktree onto the branch in the other worktree, I end up in a situation where the same branch is checked out twice in the two worktrees.
I agree that any end-user facing subcommand like "git rebase", even if it is not "git checkout", should refuse to work on and update a branch that is checked out elsewhere. Otherwise it will end up causing confusion.
I agree. I suppose we need same treatment for git-push? A push can be rejected if the pushed ref is being checked out. Suppose HEAD is in the middle of a rebase (or am), it fails to detect ref name (and thus checkout state) the same way here and we definitely do not want "git rebase" to simply overwrite whatever is updated by the push request.
Signature · 1 line
Duy
cover fix checking out a being-rebased branch
First cut. I need opinions on 05/07, which converts wt_status_get_state() to support selecting any worktree. I'm not super happy with leaving "TODO: not supported yet" comments, even though strictly speaking this series does not need it.
The reason it's left unsupported is, in order to add worktree_get_sha1() or similar, resolve_ref() will be touched. But that function is going under some changes for quite some time now, for lmdb backend support, and I don't want to create more code conflicts there, potentially delaying lmdb backend further.
Another option is leave wt_status_get_state() alone, factor out the rebase-detection code and use that for worktree/checkout. We save a few syscalls this way too.
Comments?
[01/07] path.c: add git_common_path() and strbuf_git_common_path() [02/07] worktree.c: store "id" instead of "git_dir"
[03/07] path.c: refactor and add worktree_git_path()
[04/07] worktree.c: add worktree_git_path_..._head()
[05/07] wt-status.c: make wt_status_get_state() support worktree [06/07] worktree.c: avoid referencing to worktrees[i] multiple times [07/07] checkout: prevent checking out a branch being rebased in another worktree
Total 6 files changed, 167 insertions(+), 38 deletions(-)
PATCH 1/7 path.c: add git_common_path() and strbuf_git_common_path()
These are mostly convenient functions to reduce code duplication. Most of the time, we should be able to get by with git_path() which handles $GIT_COMMON_DIR internally. However there are a few cases where we need to construct paths manually, for example some paths from a specific worktree. These functions will enable that.
--- cache.h | 3 +++ path.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+)
cache.h +3 -0
@@ -799,11 +799,14 @@ extern void check_repository_format(void);*/ extern const char *mkpath (const char *fmt, ...) __attribute__((format (printf,1 ,2 )));extern const char *git_path (const char *fmt, ...) __attribute__((format (printf,1 ,2 ))); +extern const char *git_common_path (const char *fmt, ...) __attribute__((format (printf,1 ,2 )));extern char *mksnpath (char *buf,size_t n,const char *fmt, ...) __attribute__((format (printf,3 ,4 )));extern void strbuf_git_path (struct strbuf *sb,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); +extern void strbuf_git_common_path (struct strbuf *sb,const char *fmt, ...) + __attribute__((format (printf,2 ,3 )));extern char *git_path_buf (struct strbuf *buf,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
path.c +29 -0
@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,va_end (args); } +static void do_git_common_path (struct strbuf *buf, +const char *fmt, +va_list args) +{ +strbuf_addstr (buf,get_git_common_dir ()); +if (buf->len && !is_dir_sep (buf->buf[buf->len -1 ])) +strbuf_addch (buf,'/' ); +strbuf_vaddf (buf, fmt, args); +strbuf_cleanup_path (buf); +} + +const char *git_common_path (const char *fmt, ...) +{ +struct strbuf *pathname =get_pathname (); +va_list args; +va_start (args, fmt); +do_git_common_path (pathname, fmt, args); +va_end (args); +return pathname->buf; +} + +void strbuf_git_common_path (struct strbuf *sb,const char *fmt, ...) +{ +va_list args; +va_start (args, fmt); +do_git_common_path (sb, fmt, args); +va_end (args); +} +int validate_headref (const char *path) {struct stat st;
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH 2/7 worktree.c: store "id" instead of "git_dir"
In reply to Nguyễn Thái Ngọc DuyWe can reconstruct git_dir from id quite easily. It's a bit hackier to do the reverse.
--- worktree.c | 31 ++++++++++++++++++------------- worktree.h | 8 +++++++- 2 files changed, 25 insertions(+), 14 deletions(-)
worktree.c +18 -13
@@ -9,7 +9,7 @@ void free_worktrees(struct worktree **worktrees)for (i =0 ; worktrees[i]; i++) {free (worktrees[i]->path); -free (worktrees[i]->git_dir); +free (worktrees[i]->id);free (worktrees[i]->head_ref);free (worktrees[i]); }
@@ -74,13 +74,11 @@ static struct worktree *get_main_worktree(void)struct worktree *worktree =NULL ;struct strbuf path =STRBUF_INIT ;struct strbuf worktree_path =STRBUF_INIT ; -struct strbuf gitdir =STRBUF_INIT ;struct strbuf head_ref =STRBUF_INIT ;int is_bare =0 ;int is_detached =0 ; -strbuf_addf (&gitdir,"%s" ,absolute_path (get_git_common_dir ())); -strbuf_addbuf (&worktree_path, &gitdir); +strbuf_addstr (&worktree_path,absolute_path (get_git_common_dir ())); is_bare = !strbuf_strip_suffix (&worktree_path,"/.git" );if (is_bare)strbuf_strip_suffix (&worktree_path,"/." );
@@ -92,7 +90,7 @@ static struct worktree *get_main_worktree(void) worktree =xmalloc (sizeof (struct worktree )); worktree->path =strbuf_detach (&worktree_path,NULL ); - worktree->git_dir =strbuf_detach (&gitdir,NULL ); + worktree->id =NULL ; worktree->is_bare = is_bare; worktree->head_ref =NULL ; worktree->is_detached = is_detached;
@@ -100,7 +98,6 @@ static struct worktree *get_main_worktree(void) done:strbuf_release (&path); -strbuf_release (&gitdir);strbuf_release (&worktree_path);strbuf_release (&head_ref);return worktree;
@@ -111,16 +108,13 @@ static struct worktree *get_linked_worktree(const char *id)struct worktree *worktree =NULL ;struct strbuf path =STRBUF_INIT ;struct strbuf worktree_path =STRBUF_INIT ; -struct strbuf gitdir =STRBUF_INIT ;struct strbuf head_ref =STRBUF_INIT ;int is_detached =0 ;if (!id)die ("Missing linked worktree name" ); -strbuf_addf (&gitdir,"%s/worktrees/%s" , -absolute_path (get_git_common_dir ()), id); -strbuf_addf (&path,"%s/gitdir" , gitdir.buf); +strbuf_git_common_path (&path,"worktrees/%s/gitdir" , id);if (strbuf_read_file (&worktree_path, path.buf,0 ) <=0 )/* invalid gitdir file */ goto done;
@@ -140,7 +134,7 @@ static struct worktree *get_linked_worktree(const char *id) worktree =xmalloc (sizeof (struct worktree )); worktree->path =strbuf_detach (&worktree_path,NULL ); - worktree->git_dir =strbuf_detach (&gitdir,NULL ); + worktree->id =xstrdup (id); worktree->is_bare =0 ; worktree->head_ref =NULL ; worktree->is_detached = is_detached;
@@ -148,7 +142,6 @@ static struct worktree *get_linked_worktree(const char *id) done:strbuf_release (&path); -strbuf_release (&gitdir);strbuf_release (&worktree_path);strbuf_release (&head_ref);return worktree;
@@ -188,6 +181,16 @@ struct worktree **get_worktrees(void)return list; } +const char *get_worktree_git_dir (const struct worktree *wt) +{ +if (!wt) +return get_git_dir (); +else if (!wt->id) +return get_git_common_dir (); +else +return git_common_path ("worktrees/%s" , wt->id); +} +char *find_shared_symref (const char *symref,const char *target) {char *existing =NULL ;
@@ -199,7 +202,9 @@ char *find_shared_symref(const char *symref, const char *target)for (i =0 ; worktrees[i]; i++) {strbuf_reset (&path);strbuf_reset (&sb); -strbuf_addf (&path,"%s/%s" , worktrees[i]->git_dir, symref); +strbuf_addf (&path,"%s/%s" , +get_worktree_git_dir (worktrees[i]), + symref);if (parse_ref (path.buf, &sb,NULL )) {continue ;
worktree.h +7 -1
@@ -3,7 +3,7 @@struct worktree {char *path; -char *git_dir; +char *id;char *head_ref;unsigned char head_sha1[20 ];int is_detached;
@@ -23,6 +23,12 @@ struct worktree {extern struct worktree **get_worktrees (void );/* +* Return git dir of the worktree. Note that the path may be relative. +* If wt is NULL, git dir of current worktree is returned. +*/ +extern const char *get_worktree_git_dir (const struct worktree *wt); + +/* * Free up the memory for worktree(s) */ extern void free_worktrees (struct worktree **);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH 3/7 path.c: refactor and add worktree_git_path()
In reply to Nguyễn Thái Ngọc Duydo_git_path(), which is the common code for all git_path* functions, is modified to take a worktree struct and can produce paths for any worktree.
worktree_git_path() is the first function that makes use of this. It can be used to write code that can examine any worktree. For example, wt_status_get_state() will be converted using this to take am/rebase/... state of any worktree.
--- path.c | 34 ++++++++++++++++++++++++++++------ worktree.h | 11 +++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-)
path.c +28 -6
@@ -5,6 +5,7 @@#include "strbuf.h" #include "string-list.h" #include "dir.h" +#include "worktree.h" static int get_st_mode_bits (const char *path,int *mode) {
@@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)update_common_dir (buf, git_dir_len,NULL ); } -static void do_git_path (struct strbuf *buf,const char *fmt,va_list args) +static void do_git_path (const struct worktree *wt,struct strbuf *buf, +const char *fmt,va_list args) {int gitdir_len; -strbuf_addstr (buf,get_git_dir ()); +strbuf_addstr (buf,get_worktree_git_dir (wt));if (buf->len && !is_dir_sep (buf->buf[buf->len -1 ]))strbuf_addch (buf,'/' ); gitdir_len = buf->len;
@@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)va_list args;strbuf_reset (buf);va_start (args, fmt); -do_git_path (buf, fmt, args); +do_git_path (NULL , buf, fmt, args);va_end (args);return buf->buf; }
@@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) {va_list args;va_start (args, fmt); -do_git_path (sb, fmt, args); +do_git_path (NULL , sb, fmt, args);va_end (args); }
@@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)struct strbuf *pathname =get_pathname ();va_list args;va_start (args, fmt); -do_git_path (pathname, fmt, args); +do_git_path (NULL , pathname, fmt, args);va_end (args);return pathname->buf; }
@@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)struct strbuf path =STRBUF_INIT ;va_list args;va_start (args, fmt); -do_git_path (&path, fmt, args); +do_git_path (NULL , &path, fmt, args);va_end (args);return strbuf_detach (&path,NULL ); }
@@ -454,6 +456,26 @@ const char *mkpath(const char *fmt, ...)return cleanup_path (pathname->buf); } +const char *worktree_git_path (const struct worktree *wt,const char *fmt, ...) +{ +struct strbuf *pathname =get_pathname (); +va_list args; +va_start (args, fmt); +do_git_path (wt, pathname, fmt, args); +va_end (args); +return pathname->buf; +} + +char *worktree_git_pathdup (const struct worktree *wt,const char *fmt, ...) +{ +struct strbuf path =STRBUF_INIT ; +va_list args; +va_start (args, fmt); +do_git_path (wt, &path, fmt, args); +va_end (args); +return strbuf_detach (&path,NULL ); +} +static void do_submodule_path (struct strbuf *buf,const char *path,const char *fmt,va_list args) {
worktree.h +11 -0
@@ -41,4 +41,15 @@ extern void free_worktrees(struct worktree **);*/ extern char *find_shared_symref (const char *symref,const char *target); +/* +* Similar to git_path() and git_pathdup() but can produce paths for a +* specified worktree instead of current one +*/ +extern const char *worktree_git_path (const struct worktree *wt, +const char *fmt, ...) + __attribute__((format (printf,2 ,3 ))); +extern char *worktree_git_pathdup (const struct worktree *wt, +const char *fmt, ...) + __attribute__((format (printf,2 ,3 ))); +#endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH 4/7 worktree.c: add worktree_git_path_..._head()
In reply to Nguyễn Thái Ngọc DuyThese are a variant of git_path_..._head(), defined with GIT_PATH_FUNC macro but they takes worktree into account. To be used when wt_status_get_state() is converted to be worktree-aware.
--- worktree.c | 4 ++++ worktree.h | 13 +++++++++++++ 2 files changed, 17 insertions(+)
worktree.c +4 -0
@@ -222,3 +222,7 @@ char *find_shared_symref(const char *symref, const char *target)return existing; } + +WORKTREE_GIT_PATH_FUNC(merge_head, "MERGE_HEAD") +WORKTREE_GIT_PATH_FUNC(cherry_pick_head, "CHERRY_PICK_HEAD") +WORKTREE_GIT_PATH_FUNC (revert_head,"REVERT_HEAD" )
worktree.h +13 -0
@@ -52,4 +52,17 @@ extern char *worktree_git_pathdup(const struct worktree *wt, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +#define WORKTREE_GIT_PATH_FUNC (func, filename) \ + const char *worktree_git_path_ ## func(const struct worktree *wt) \ + { \ + static char *ret; \ + if (!ret) \ + ret = worktree_git_pathdup(wt, filename); \ + return ret; \ + } + +const char *worktree_git_path_merge_head (const struct worktree *); +const char *worktree_git_path_cherry_pick_head (const struct worktree *); +const char *worktree_git_path_revert_head (const struct worktree *); +#endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH 5/7 wt-status.c: make wt_status_get_state() support worktree
In reply to Nguyễn Thái Ngọc DuyThe function is to be used to detect if a ref is checked out in another worktree. In some cases, parsing "HEAD" alone is not enough because the worktree may be under rebase or bisect.
Note that because get_sha1() cannot take a worktree (and of course will not, we need something like worktree_get_sha1), cherry-pick and revert detection will not work when the examined worktree is not the current one. The same for get_detached_from.
--- wt-status.c | 42 +++++++++++++++++++++++++----------------- wt-status.h | 3 +++ 2 files changed, 28 insertions(+), 17 deletions(-)
wt-status.c +25 -17
@@ -15,6 +15,7 @@#include "column.h" #include "strbuf.h" #include "utf8.h" +#include "worktree.h" static const char cut_line[] ="------------------------ >8 ------------------------\n" ;
@@ -1262,13 +1263,13 @@ static void show_bisect_in_progress(struct wt_status *s,/* * Extract branch information from rebase/bisect */ -static char *read_and_strip_branch (const char *path) +static char *read_and_strip_branch (const struct worktree *wt,const char *path) {struct strbuf sb =STRBUF_INIT ;unsigned char sha1[20 ];const char *branch_name; -if (strbuf_read_file (&sb,git_path ("%s" , path),0 ) <=0 ) +if (strbuf_read_file (&sb,worktree_git_path (wt,"%s" , path),0 ) <=0 ) goto got_nothing;while (sb.len && sb.buf[sb.len -1 ] =='\n' )
@@ -1363,45 +1364,52 @@ static void wt_status_get_detached_from(struct wt_status_state *state)void wt_status_get_state (struct wt_status_state *state,int get_detached_from) { +const struct worktree *wt = state->wt;struct stat st;unsigned char sha1[20 ]; -if (!stat (git_path_merge_head (), &st)) { +if (!stat (worktree_git_path_merge_head (wt), &st)) { state->merge_in_progress =1 ; - }else if (!stat (git_path ("rebase-apply" ), &st)) { -if (!stat (git_path ("rebase-apply/applying" ), &st)) { + }else if (!stat (worktree_git_path (wt,"rebase-apply" ), &st)) { +if (!stat (worktree_git_path (wt,"rebase-apply/applying" ), &st)) { state->am_in_progress =1 ; -if (!stat (git_path ("rebase-apply/patch" ), &st) && !st.st_size) +if (!stat (worktree_git_path (wt,"rebase-apply/patch" ), &st) && !st.st_size) state->am_empty_patch =1 ; }else { state->rebase_in_progress =1 ; - state->branch =read_and_strip_branch ("rebase-apply/head-name" ); - state->onto =read_and_strip_branch ("rebase-apply/onto" ); + state->branch =read_and_strip_branch (wt,"rebase-apply/head-name" ); + state->onto =read_and_strip_branch (wt,"rebase-apply/onto" ); } - }else if (!stat (git_path ("rebase-merge" ), &st)) { -if (!stat (git_path ("rebase-merge/interactive" ), &st)) + }else if (!stat (worktree_git_path (wt,"rebase-merge" ), &st)) { +if (!stat (worktree_git_path (wt,"rebase-merge/interactive" ), &st)) state->rebase_interactive_in_progress =1 ;else state->rebase_in_progress =1 ; - state->branch =read_and_strip_branch ("rebase-merge/head-name" ); - state->onto =read_and_strip_branch ("rebase-merge/onto" ); - }else if (!stat (git_path_cherry_pick_head (), &st) && + state->branch =read_and_strip_branch (wt,"rebase-merge/head-name" ); + state->onto =read_and_strip_branch (wt,"rebase-merge/onto" ); + }else if (!wt &&/* TODO: get_sha1() cannot select worktree yet */ + !stat (worktree_git_path_cherry_pick_head (wt), &st) && !get_sha1 ("CHERRY_PICK_HEAD" , sha1)) { state->cherry_pick_in_progress =1 ;hashcpy (state->cherry_pick_head_sha1, sha1); } -if (!stat (git_path ("BISECT_LOG" ), &st)) { +if (!stat (worktree_git_path (wt,"BISECT_LOG" ), &st)) { state->bisect_in_progress =1 ; - state->branch =read_and_strip_branch ("BISECT_START" ); + state->branch =read_and_strip_branch (wt,"BISECT_START" ); } -if (!stat (git_path_revert_head (), &st) && +if (!wt &&/* TODO: get_sha1() cannot select worktree yet */ + !stat (worktree_git_path_revert_head (wt), &st) && !get_sha1 ("REVERT_HEAD" , sha1)) { state->revert_in_progress =1 ;hashcpy (state->revert_head_sha1, sha1); } -if (get_detached_from) +if (get_detached_from) { +if (wt) +die ("BUG: %s:%d: non-zero get_detached_from is not supported" , + __FILE__, __LINE__);wt_status_get_detached_from (state); + } } static void wt_status_print_state(struct wt_status *s,
wt-status.h +3 -0
@@ -6,6 +6,8 @@#include "color.h" #include "pathspec.h" +struct worktree ; +enum color_wt_status {WT_STATUS_HEADER =0 ,WT_STATUS_UPDATED ,
@@ -77,6 +79,7 @@ struct wt_status { };struct wt_status_state { +struct worktree *wt;int merge_in_progress;int am_in_progress;int am_empty_patch;
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH 6/7 worktree.c: avoid referencing to worktrees[i] multiple times
In reply to Nguyễn Thái Ngọc Duy--- worktree.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
worktree.c +4 -2
@@ -200,10 +200,12 @@ char *find_shared_symref(const char *symref, const char *target)int i =0 ;for (i =0 ; worktrees[i]; i++) { +struct worktree *wt = worktrees[i]; +strbuf_reset (&path);strbuf_reset (&sb);strbuf_addf (&path,"%s/%s" , -get_worktree_git_dir (worktrees[i]), +get_worktree_git_dir (wt), symref);if (parse_ref (path.buf, &sb,NULL )) {
@@ -211,7 +213,7 @@ char *find_shared_symref(const char *symref, const char *target) }if (!strcmp (sb.buf, target)) { - existing =xstrdup (worktrees[i]->path); + existing =xstrdup (wt->path);break ; } }
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH 7/7 checkout: prevent checking out a branch being rebased in another worktree
In reply to Nguyễn Thái Ngọc DuyWe failed to detect this case because the rebase-in-progress worktree has HEAD in detached state. Some more poking around is needed to determine if a rebase is in progress.
Note that even though bisect can also put a worktree in detached state and hide the real branch, we allow to checkout anyway. This is because unlike rebase, bisect does not update the branch. Yes it's still a bit confusing when the user doing the bisecting realizes that the branch is updated elsewhere, but at least we do not lose any valuable ref update.
--- worktree.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
worktree.c +23 -0
@@ -2,6 +2,7 @@#include "refs.h" #include "strbuf.h" #include "worktree.h" +#include "wt-status.h" void free_worktrees (struct worktree **worktrees) {
@@ -202,6 +203,28 @@ char *find_shared_symref(const char *symref, const char *target)for (i =0 ; worktrees[i]; i++) {struct worktree *wt = worktrees[i]; +if (wt->is_detached) { +struct wt_status_state wss; +int found; + +memset (&wss,0 ,sizeof (wss)); + wss.wt = wt; +wt_status_get_state (&wss,0 ); + found = (wss.rebase_in_progress || + wss.rebase_interactive_in_progress) && + wss.branch && +starts_with (target,"refs/heads/" ) && + !strcmp (wss.branch, + target +strlen ("refs/heads/" )); +free (wss.branch); +free (wss.onto); +free (wss.detached_from); +if (found) { + existing =xstrdup (wt->path); +break ; + } + } +strbuf_reset (&path);strbuf_reset (&sb); strbuf_addf(&path, "%s/%s",
Signature · 1 line
2.8.0.rc0.210.gd302cd2
Re: [PATCH 0/7] fix checking out a being-rebased branch
In reply to Nguyễn Thái Ngọc DuyQuoted 4 lines · Nguyễn Thái Ngọc Duy
First cut. I need opinions on 05/07, which converts
wt_status_get_state()to support selecting any worktree. I'm not super happy with leaving "TODO: not supported yet" comments, even though strictly speaking this series does not need it.
It is a reasonable idea to hook this to wt_status_get_state(), I would say.
Quoted 10 lines
Another option is leave
wt_status_get_state()alone, factor out the rebase-detection code and use that for worktree/checkout. We save a few syscalls this way too.Comments?
[01/07]
path.c: addgit_common_path()andstrbuf_git_common_path()[02/07]worktree.c: store "id" instead of "git_dir" [03/07]path.c: refactor and addworktree_git_path()[04/07]worktree.c: add worktree_git_path_..._head()
I actually wondered how ky/branch-[dm]-worktree topics to avoid "branch -d" and "branch -m" from removing or renaming a branch that is checked out in other worktrees from screwing them up. There may be a missed opportunity to clean them up with using these?
Quoted 5 lines
[05/07]
wt-status.c: makewt_status_get_state()support worktree [06/07]worktree.c: avoid referencing to worktrees[i] multiple times [07/07] checkout: prevent checking out a branch being rebased in another worktreeTotal 6 files changed, 167 insertions(+), 38 deletions(-)
Re: [PATCH 0/7] fix checking out a being-rebased branch
Quoted 11 lines · Junio C Hamano
Quoted 10 lines
Another option is leave
wt_status_get_state()alone, factor out the rebase-detection code and use that for worktree/checkout. We save a few syscalls this way too.Comments?
[01/07]
path.c: addgit_common_path()andstrbuf_git_common_path()[02/07]worktree.c: store "id" instead of "git_dir" [03/07]path.c: refactor and addworktree_git_path()[04/07]worktree.c: add worktree_git_path_..._head()
Quoted 4 lines
I actually wondered how ky/branch-[dm]-worktree topics to avoid "branch -d" and "branch -m" from removing or renaming a branch that is checked out in other worktrees from screwing them up. There may be a missed opportunity to clean them up with using these?
branch-m-worktree uses info populated at get_worktrees() phase. branch-d-worktree uses find_shared_symref() which is modified in this series in order to detect rebase branch. So both topics have the same problem when a branch is being rebased and if I do it right, I'll fix them both without extra code. Actually I may need to check branch-m-worktree again, renaming a branch under rebase might cause problems, I need to have a closer look at git-rebase*.sh.
Signature · 1 line
Duy
Re: [PATCH 0/7] fix checking out a being-rebased branch
Quoted 17 lines · Duy Nguyen
Quoted 11 lines · Junio C Hamano
Quoted 10 lines
Another option is leave
wt_status_get_state()alone, factor out the rebase-detection code and use that for worktree/checkout. We save a few syscalls this way too.Comments?
[01/07]
path.c: addgit_common_path()andstrbuf_git_common_path()[02/07]worktree.c: store "id" instead of "git_dir" [03/07]path.c: refactor and addworktree_git_path()[04/07]worktree.c: add worktree_git_path_..._head()Quoted 4 lines
I actually wondered how ky/branch-[dm]-worktree topics to avoid "branch -d" and "branch -m" from removing or renaming a branch that is checked out in other worktrees from screwing them up. There may be a missed opportunity to clean them up with using these?
Quoted 5 lines
branch-m-worktree uses info populated at
get_worktrees()phase. branch-d-worktree usesfind_shared_symref()which is modified in this series in order to detect rebase branch. So both topics have the same problem when a branch is being rebased and if I do it right, I'll fix them both without extra code.
Yup, that was exactly why I brought it up.
Quoted 3 lines
Actually I may need to check branch-m-worktree again, renaming a branch under rebase might cause problems, I need to have a closer look at git-rebase*.sh.
Thanks.
v2 cover fix checking out a being-rebased branch
In reply to Nguyễn Thái Ngọc DuyMuch happier with this version. This makes
- git checkout refuse if a branch is under rebase or bisect elsewhere
- git worktree add refuse if a branch is under rebase or bisect
- git branch -D refuse if a branch is under rebase or bisect. This applies for single worktree case as well.
- git branch -M refuse if a branch is under rebase or bisect (single worktree case as well)
[01/12] path.c: add git_common_path() and strbuf_git_common_path() [02/12] worktree.c: store "id" instead of "git_dir"
[03/12] worktree.c: make find_shared_symref() return struct worktree * [04/12] worktree.c: mark current worktree
[05/12] path.c: refactor and add worktree_git_path()
[06/12] wt-status.c: split rebase detection out of wt_status_get_state() [07/12] wt-status.c: make wt_status_check_rebase() work on any worktree [08/12] worktree.c: avoid referencing to worktrees[i] multiple times [09/12] worktree.c: test if branch being rebased in another worktree [10/12] wt-status.c: split bisect detection out of wt_status_get_state() [11/12] worktree.c: test if branch being bisected in another worktree [12/12] branch: do not rename a branch under bisect or rebase
Total 13 files changed, 335 insertions(+), 67 deletions(-)
PATCH v2 1/12 path.c: add git_common_path() and strbuf_git_common_path()
These are mostly convenient functions to reduce code duplication. Most of the time, we should be able to get by with git_path() which handles $GIT_COMMON_DIR internally. However there are a few cases where we need to construct paths manually, for example some paths from a specific worktree. These functions will enable that.
--- cache.h | 3 +++ path.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+)
cache.h +3 -0
@@ -799,11 +799,14 @@ extern void check_repository_format(void);*/ extern const char *mkpath (const char *fmt, ...) __attribute__((format (printf,1 ,2 )));extern const char *git_path (const char *fmt, ...) __attribute__((format (printf,1 ,2 ))); +extern const char *git_common_path (const char *fmt, ...) __attribute__((format (printf,1 ,2 )));extern char *mksnpath (char *buf,size_t n,const char *fmt, ...) __attribute__((format (printf,3 ,4 )));extern void strbuf_git_path (struct strbuf *sb,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); +extern void strbuf_git_common_path (struct strbuf *sb,const char *fmt, ...) + __attribute__((format (printf,2 ,3 )));extern char *git_path_buf (struct strbuf *buf,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
path.c +29 -0
@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,va_end (args); } +static void do_git_common_path (struct strbuf *buf, +const char *fmt, +va_list args) +{ +strbuf_addstr (buf,get_git_common_dir ()); +if (buf->len && !is_dir_sep (buf->buf[buf->len -1 ])) +strbuf_addch (buf,'/' ); +strbuf_vaddf (buf, fmt, args); +strbuf_cleanup_path (buf); +} + +const char *git_common_path (const char *fmt, ...) +{ +struct strbuf *pathname =get_pathname (); +va_list args; +va_start (args, fmt); +do_git_common_path (pathname, fmt, args); +va_end (args); +return pathname->buf; +} + +void strbuf_git_common_path (struct strbuf *sb,const char *fmt, ...) +{ +va_list args; +va_start (args, fmt); +do_git_common_path (sb, fmt, args); +va_end (args); +} +int validate_headref (const char *path) {struct stat st;
Signature · 1 line
2.8.0.rc0.210.gd302cd2
Re: [PATCH v2 01/12] path.c: add git_common_path() and strbuf_git_common_path()
path.c void strbuf_git_path_submodule(struct strbuf *buf, const char *path, · line 510
Quoted 10 lines · Nguyễn Thái Ngọc Duy
path.c +8
-0@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path, +const char *git_common_path (const char *fmt, ...) +{ +struct strbuf *pathname =get_pathname (); +va_list args; +va_start (args, fmt); +do_git_common_path (pathname, fmt, args); +va_end (args); +return pathname->buf;
Is the caller expected to free this value? If not, then shouldn't 'pathname' be static? If so, then perhaps strbuf_detach() would be clearer (and return 'char *' rather than 'const char *').
Quoted 1 line
path.c +1
-0+}
Re: [PATCH v2 01/12] path.c: add git_common_path() and strbuf_git_common_path()
Quoted 15 lines · Eric Sunshine
path.c void strbuf_git_path_submodule(struct strbuf *buf, const char *path, · line 510
Quoted 10 lines · Nguyễn Thái Ngọc Duy
path.c +8
-0@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path, +const char *git_common_path (const char *fmt, ...) +{ +struct strbuf *pathname =get_pathname (); +va_list args; +va_start (args, fmt); +do_git_common_path (pathname, fmt, args); +va_end (args); +return pathname->buf;Is the caller expected to free this value? If not, then shouldn't 'pathname' be static? If so, then perhaps
strbuf_detach()would be clearer (and return 'char *' rather than 'const char *').
get_pathname() actually holds a ring of static buffer. So no, we don't need static pathname, it can be a new buffer next time, mostly to to make printf("%s %s", git_common_path(..), git_common_path(..)) work. And no the caller is not supposed to free it, a little bit more convenient.
Signature · 1 line
Duy
PATCH v2 2/12 worktree.c: store "id" instead of "git_dir"
In reply to Nguyễn Thái Ngọc DuyWe can reconstruct git_dir from id quite easily. It's a bit hackier to do the reverse.
--- branch.c | 3 ++- worktree.c | 31 ++++++++++++++++++------------- worktree.h | 8 +++++++- 3 files changed, 27 insertions(+), 15 deletions(-)
branch.c +2 -1
@@ -357,7 +357,8 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref)if (strcmp (oldref, worktrees[i]->head_ref))continue ; -if (set_worktree_head_symref (worktrees[i]->git_dir, newref)) { +if (set_worktree_head_symref (get_worktree_git_dir (worktrees[i]), + newref)) { ret =-1 ;error (_ ("HEAD of working tree %s is not updated" ), worktrees[i]->path);
worktree.c +18 -13
@@ -9,7 +9,7 @@ void free_worktrees(struct worktree **worktrees)for (i =0 ; worktrees[i]; i++) {free (worktrees[i]->path); -free (worktrees[i]->git_dir); +free (worktrees[i]->id);free (worktrees[i]->head_ref);free (worktrees[i]); }
@@ -74,13 +74,11 @@ static struct worktree *get_main_worktree(void)struct worktree *worktree =NULL ;struct strbuf path =STRBUF_INIT ;struct strbuf worktree_path =STRBUF_INIT ; -struct strbuf gitdir =STRBUF_INIT ;struct strbuf head_ref =STRBUF_INIT ;int is_bare =0 ;int is_detached =0 ; -strbuf_addf (&gitdir,"%s" ,absolute_path (get_git_common_dir ())); -strbuf_addbuf (&worktree_path, &gitdir); +strbuf_addstr (&worktree_path,absolute_path (get_git_common_dir ())); is_bare = !strbuf_strip_suffix (&worktree_path,"/.git" );if (is_bare)strbuf_strip_suffix (&worktree_path,"/." );
@@ -92,7 +90,7 @@ static struct worktree *get_main_worktree(void) worktree =xmalloc (sizeof (struct worktree )); worktree->path =strbuf_detach (&worktree_path,NULL ); - worktree->git_dir =strbuf_detach (&gitdir,NULL ); + worktree->id =NULL ; worktree->is_bare = is_bare; worktree->head_ref =NULL ; worktree->is_detached = is_detached;
@@ -100,7 +98,6 @@ static struct worktree *get_main_worktree(void) done:strbuf_release (&path); -strbuf_release (&gitdir);strbuf_release (&worktree_path);strbuf_release (&head_ref);return worktree;
@@ -111,16 +108,13 @@ static struct worktree *get_linked_worktree(const char *id)struct worktree *worktree =NULL ;struct strbuf path =STRBUF_INIT ;struct strbuf worktree_path =STRBUF_INIT ; -struct strbuf gitdir =STRBUF_INIT ;struct strbuf head_ref =STRBUF_INIT ;int is_detached =0 ;if (!id)die ("Missing linked worktree name" ); -strbuf_addf (&gitdir,"%s/worktrees/%s" , -absolute_path (get_git_common_dir ()), id); -strbuf_addf (&path,"%s/gitdir" , gitdir.buf); +strbuf_git_common_path (&path,"worktrees/%s/gitdir" , id);if (strbuf_read_file (&worktree_path, path.buf,0 ) <=0 )/* invalid gitdir file */ goto done;
@@ -140,7 +134,7 @@ static struct worktree *get_linked_worktree(const char *id) worktree =xmalloc (sizeof (struct worktree )); worktree->path =strbuf_detach (&worktree_path,NULL ); - worktree->git_dir =strbuf_detach (&gitdir,NULL ); + worktree->id =xstrdup (id); worktree->is_bare =0 ; worktree->head_ref =NULL ; worktree->is_detached = is_detached;
@@ -148,7 +142,6 @@ static struct worktree *get_linked_worktree(const char *id) done:strbuf_release (&path); -strbuf_release (&gitdir);strbuf_release (&worktree_path);strbuf_release (&head_ref);return worktree;
@@ -188,6 +181,16 @@ struct worktree **get_worktrees(void)return list; } +const char *get_worktree_git_dir (const struct worktree *wt) +{ +if (!wt) +return get_git_dir (); +else if (!wt->id) +return get_git_common_dir (); +else +return git_common_path ("worktrees/%s" , wt->id); +} +char *find_shared_symref (const char *symref,const char *target) {char *existing =NULL ;
@@ -199,7 +202,9 @@ char *find_shared_symref(const char *symref, const char *target)for (i =0 ; worktrees[i]; i++) {strbuf_reset (&path);strbuf_reset (&sb); -strbuf_addf (&path,"%s/%s" , worktrees[i]->git_dir, symref); +strbuf_addf (&path,"%s/%s" , +get_worktree_git_dir (worktrees[i]), + symref);if (parse_ref (path.buf, &sb,NULL )) {continue ;
worktree.h +7 -1
@@ -3,7 +3,7 @@struct worktree {char *path; -char *git_dir; +char *id;char *head_ref;unsigned char head_sha1[20 ];int is_detached;
@@ -23,6 +23,12 @@ struct worktree {extern struct worktree **get_worktrees (void );/* +* Return git dir of the worktree. Note that the path may be relative. +* If wt is NULL, git dir of current worktree is returned. +*/ +extern const char *get_worktree_git_dir (const struct worktree *wt); + +/* * Free up the memory for worktree(s) */ extern void free_worktrees (struct worktree **);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v2 3/12 worktree.c: make find_shared_symref() return struct worktree *
In reply to Nguyễn Thái Ngọc DuyThis gives the caller more information and they can answer things like, "is it the main worktree" or "is it the current worktree". The latter question is needed for the "checkout a rebase branch" case later.
--- branch.c | 9 +++++---- builtin/branch.c | 8 ++++---- builtin/notes.c | 8 ++++---- worktree.c | 14 +++++++++----- worktree.h | 7 ++++--- 5 files changed, 26 insertions(+), 20 deletions(-)
branch.c +5 -4
@@ -336,12 +336,13 @@ void remove_branch_state(void)void die_if_checked_out (const char *branch) { -char *existing; +const struct worktree *wt; - existing =find_shared_symref ("HEAD" , branch); -if (existing) { + wt =find_shared_symref ("HEAD" , branch); +if (wt) {skip_prefix (branch,"refs/heads/" , &branch); -die (_ ("'%s' is already checked out at '%s'" ), branch, existing); +die (_ ("'%s' is already checked out at '%s'" ), + branch, wt->path); } }
builtin/branch.c +4 -4
@@ -220,12 +220,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, name =mkpathdup (fmt, bname.buf);if (kinds ==FILTER_REFS_BRANCHES ) { -char *worktree =find_shared_symref ("HEAD" , name); -if (worktree) { +const struct worktree *wt = +find_shared_symref ("HEAD" , name); +if (wt) {error (_ ("Cannot delete branch '%s' " "checked out at '%s'" ), - bname.buf, worktree); -free (worktree); + bname.buf, wt->path); ret =1 ;continue ; }
builtin/notes.c +4 -4
@@ -847,15 +847,15 @@ static int merge(int argc, const char **argv, const char *prefix)update_ref (msg.buf,default_notes_ref (), result_sha1,NULL ,0 ,UPDATE_REFS_DIE_ON_ERR ); else { /* Merge has unresolved conflicts */ -char *existing; +const struct worktree *wt;/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ update_ref (msg.buf,"NOTES_MERGE_PARTIAL" , result_sha1,NULL ,0 ,UPDATE_REFS_DIE_ON_ERR );/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ - existing =find_shared_symref ("NOTES_MERGE_REF" ,default_notes_ref ()); -if (existing) + wt =find_shared_symref ("NOTES_MERGE_REF" ,default_notes_ref ()); +if (wt)die (_ ("A notes merge into %s is already in-progress at %s" ), -default_notes_ref (), existing); +default_notes_ref (), wt->path);if (create_symref ("NOTES_MERGE_REF" ,default_notes_ref (),NULL ))die ("Failed to store link to current notes ref (%s)" ,default_notes_ref ());
worktree.c +9 -5
@@ -191,14 +191,19 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } -char *find_shared_symref (const char *symref,const char *target) +const struct worktree *find_shared_symref (const char *symref, +const char *target) { -char *existing =NULL ; +const struct worktree *existing =NULL ;struct strbuf path =STRBUF_INIT ;struct strbuf sb =STRBUF_INIT ; -struct worktree **worktrees =get_worktrees (); +static struct worktree **worktrees;int i =0 ; +if (worktrees) +free_worktrees (worktrees); + worktrees =get_worktrees (); +for (i =0 ; worktrees[i]; i++) {strbuf_reset (&path);strbuf_reset (&sb);
@@ -211,14 +216,13 @@ char *find_shared_symref(const char *symref, const char *target) }if (!strcmp (sb.buf, target)) { - existing =xstrdup (worktrees[i]->path); + existing = worktrees[i];break ; } }strbuf_release (&path);strbuf_release (&sb); -free_worktrees (worktrees);return existing; }
worktree.h +4 -3
@@ -36,9 +36,10 @@ extern void free_worktrees(struct worktree **);/* * Check if a per-worktree symref points to a ref in the main worktree * or any linked worktree, and return the path to the exising worktree -* if it is. Returns NULL if there is no existing ref. The caller is -* responsible for freeing the returned path. +* if it is. Returns NULL if there is no existing ref. The result +* may be destroyed by the next call. */ -extern char *find_shared_symref (const char *symref,const char *target); +extern const struct worktree *find_shared_symref (const char *symref, +const char *target);#endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
Re: [PATCH v2 03/12] worktree.c: make find_shared_symref() return struct worktree *
Quoted 3 lines · Nguyễn Thái Ngọc Duy
This gives the caller more information and they can answer things like, "is it the main worktree" or "is it the current worktree". The latter question is needed for the "checkout a rebase branch" case later.
That makes good sense.
worktree.h extern void free_worktrees(struct worktree **); · line 41
Quoted 13 lines
worktree.h +2
-2@@ -36,9 +36,10 @@ extern void free_worktrees(struct worktree **);/* * Check if a per-worktree symref points to a ref in the main worktree * or any linked worktree, and return the path to the exising worktree -* if it is. Returns NULL if there is no existing ref. The caller is -* responsible for freeing the returned path. +* if it is. Returns NULL if there is no existing ref. The result +* may be destroyed by the next call. */
To return and keep alive one worktree[] instance (i.e. "existing"), the code holds onto the entire return value from get_worktrees(), if I am not misreading it. Typically you would have only a handful of worktrees so this may not be an issue, though.
Re: [PATCH v2 03/12] worktree.c: make find_shared_symref() return struct worktree *
In reply to Nguyễn Thái Ngọc Duyworktree.h extern void free_worktrees(struct worktree **); · line 38
Quoted 11 lines · Nguyễn Thái Ngọc Duy
This gives the caller more information and they can answer things like, "is it the main worktree" or "is it the current worktree". The latter question is needed for the "checkout a rebase branch" case later.
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.h +0
-0@@ -36,9 +36,10 @@ extern void free_worktrees(struct worktree **);/* * Check if a per-worktree symref points to a ref in the main worktree * or any linked worktree, and return the path to the exising worktree
Doesn't "return the path" become outdated with this patch?
Also (not a new problem): s/exising/existing/
Quoted 8 lines
worktree.h +4
-3-* if it is. Returns NULL if there is no existing ref. The caller is -* responsible for freeing the returned path. +* if it is. Returns NULL if there is no existing ref. The result +* may be destroyed by the next call. */ -extern char *find_shared_symref (const char *symref,const char *target); +extern const struct worktree *find_shared_symref (const char *symref, +const char *target);
PATCH v2 4/12 worktree.c: mark current worktree
In reply to Nguyễn Thái Ngọc Duy--- worktree.c | 18 +++++++++++++++++- worktree.h | 1 + 2 files changed, 18 insertions(+), 1 deletion(-)
worktree.c +17 -1
@@ -2,6 +2,7 @@#include "refs.h" #include "strbuf.h" #include "worktree.h" +#include "dir.h" void free_worktrees (struct worktree **worktrees) {
@@ -94,6 +95,7 @@ static struct worktree *get_main_worktree(void) worktree->is_bare = is_bare; worktree->head_ref =NULL ; worktree->is_detached = is_detached; + worktree->is_current =0 ;add_head_info (&head_ref, worktree); done:
@@ -138,6 +140,7 @@ static struct worktree *get_linked_worktree(const char *id) worktree->is_bare =0 ; worktree->head_ref =NULL ; worktree->is_detached = is_detached; + worktree->is_current =0 ;add_head_info (&head_ref, worktree); done:
@@ -150,10 +153,11 @@ done:struct worktree **get_worktrees (void ) {struct worktree **list =NULL ; +struct strbuf git_dir =STRBUF_INIT ;struct strbuf path =STRBUF_INIT ;DIR *dir;struct dirent *d; -int counter =0 , alloc =2 ; +int i, counter =0 , alloc =2 ; list =xmalloc (alloc *sizeof (struct worktree *));
@@ -178,6 +182,18 @@ struct worktree **get_worktrees(void) }ALLOC_GROW (list, counter +1 , alloc); list[counter] =NULL ; + +strbuf_addstr (&git_dir,absolute_path (get_git_dir ())); +for (i =0 ; i < counter; i++) { +struct worktree *wt = list[i]; +strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); + wt->is_current = !strcmp_icase (git_dir.buf, path.buf); +strbuf_reset (&path); +if (wt->is_current) +break ; + } +strbuf_release (&git_dir); +strbuf_release (&path);return list; }
worktree.h +1 -0
@@ -8,6 +8,7 @@ struct worktree {unsigned char head_sha1[20 ];int is_detached;int is_bare; +int is_current; };/* Functions for acting on the information about worktrees. */
Signature · 1 line
2.8.0.rc0.210.gd302cd2
Re: [PATCH v2 04/12] worktree.c: mark current worktree
worktree.c struct worktree **get_worktrees(void) · line 190
Quoted 13 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.c +6
-0@@ -178,6 +182,18 @@ struct worktree **get_worktrees(void) }ALLOC_GROW (list, counter +1 , alloc); list[counter] =NULL ; + +strbuf_addstr (&git_dir,absolute_path (get_git_dir ())); +for (i =0 ; i < counter; i++) { +struct worktree *wt = list[i]; +strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); + wt->is_current = !strcmp_icase (git_dir.buf, path.buf);
Can you talk a bit about why this uses 'icase'? Should it be respecting cache.h:ignore_case?
worktree.c struct worktree **get_worktrees(void) · line 196
Quoted 6 lines
worktree.c +6
-0+strbuf_reset (&path); +if (wt->is_current) +break ; + } +strbuf_release (&git_dir); +strbuf_release (&path);
Minor: Would it make sense to place this new code in its own function -- say, mark_current_worktree() -- to keep get_worktrees() from becoming overlong?
Quoted 2 lines
worktree.c +0
-0return list; }
Re: [PATCH v2 04/12] worktree.c: mark current worktree
Quoted 17 lines · Eric Sunshine
worktree.c struct worktree **get_worktrees(void) · line 190
Quoted 13 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.c +6
-0@@ -178,6 +182,18 @@ struct worktree **get_worktrees(void) }ALLOC_GROW (list, counter +1 , alloc); list[counter] =NULL ; + +strbuf_addstr (&git_dir,absolute_path (get_git_dir ())); +for (i =0 ; i < counter; i++) { +struct worktree *wt = list[i]; +strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); + wt->is_current = !strcmp_icase (git_dir.buf, path.buf);Can you talk a bit about why this uses 'icase'? Should it be respecting cache.h:ignore_case?
It does.That function (in dir.c) is just one-liner
return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
I admit though, the naming does not make that clear.
Quoted 10 lines
worktree.c struct worktree **get_worktrees(void) · line 196
Quoted 6 lines
worktree.c +6
-0+strbuf_reset (&path); +if (wt->is_current) +break ; + } +strbuf_release (&git_dir); +strbuf_release (&path);Minor: Would it make sense to place this new code in its own function -- say,
mark_current_worktree()-- to keepget_worktrees()from becoming overlong?
Good idea. Will do.
Signature · 1 line
Duy
Re: [PATCH v2 04/12] worktree.c: mark current worktree
Quoted 19 lines · Duy Nguyen
Quoted 17 lines · Eric Sunshine
worktree.c struct worktree **get_worktrees(void) · line 190
Quoted 13 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.c +6
-0@@ -178,6 +182,18 @@ struct worktree **get_worktrees(void) }ALLOC_GROW (list, counter +1 , alloc); list[counter] =NULL ; + +strbuf_addstr (&git_dir,absolute_path (get_git_dir ())); +for (i =0 ; i < counter; i++) { +struct worktree *wt = list[i]; +strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); + wt->is_current = !strcmp_icase (git_dir.buf, path.buf);Can you talk a bit about why this uses 'icase'? Should it be respecting cache.h:ignore_case?
Quoted 5 lines
It does.That function (in
dir.c) is just one-linerreturn ignore_case ? strcasecmp(a, b) : strcmp(a, b);I admit though, the naming does not make that clear.
While we're at it, how about renaming it to pathcmp (and its friend strncmp_icase to pathncmp)?
Signature · 1 line
Duy
Re: [PATCH v2 04/12] worktree.c: mark current worktree
Quoted 10 lines · Duy Nguyen
Quoted 9 lines · Duy Nguyen
Quoted 8 lines · Eric Sunshine
Quoted 6 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>worktree.c +5
-0+strbuf_addstr (&git_dir,absolute_path (get_git_dir ())); +for (i =0 ; i < counter; i++) { +struct worktree *wt = list[i]; +strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); + wt->is_current = !strcmp_icase (git_dir.buf, path.buf);
Quoted 8 lines
Quoted 8 lines
Quoted 2 lines
Can you talk a bit about why this uses 'icase'? Should it be respecting cache.h:ignore_case?
It does.That function (in
dir.c) is just one-linerreturn ignore_case ? strcasecmp(a, b) : strcmp(a, b);I admit though, the naming does not make that clear.
Ugh, this is only the fourth patch, yet the second stupid review mistake I've made thus far in this series. For some reason, I kept reading this as a call to strcasecmp() or stricmp() rather than strcmp_icase(). Worse, I had even consulted path.c:strcmp_icase() to see how the issue was handled there.
Quoted 2 lines
While we're at it, how about renaming it to pathcmp (and its friend
strncmp_icaseto pathncmp)?
Yes, that seems like a good idea. For anyone familiar with strcasecmp() or stricmp(), having "icase" in the name makes it seem as though it's unconditionally case-insensitive, so dropping it from the name would likely be beneficial.
Re: [PATCH v2 04/12] worktree.c: mark current worktree
Quoted 7 lines · Eric Sunshine
Quoted 2 lines
While we're at it, how about renaming it to pathcmp (and its friend
strncmp_icaseto pathncmp)?Yes, that seems like a good idea. For anyone familiar with
strcasecmp()orstricmp(), having "icase" in the name makes it seem as though it's unconditionally case-insensitive, so dropping it from the name would likely be beneficial.
Seconded (thirded?). I have been caught by this confusion in the past, too.
-Peff
Re: [PATCH v2 04/12] worktree.c: mark current worktree
Quoted 5 lines · Jeff King
Quoted 3 lines · Eric Sunshine
Quoted 2 lines
While we're at it, how about renaming it to pathcmp (and its friend
strncmp_icaseto pathncmp)?
Quoted 7 lines
Quoted 4 lines
Yes, that seems like a good idea. For anyone familiar with
strcasecmp()orstricmp(), having "icase" in the name makes it seem as though it's unconditionally case-insensitive, so dropping it from the name would likely be beneficial.Seconded (thirded?). I have been caught by this confusion in the past, too.
I agree that strcmp_icase() gives a false impression that it always ignores case differences, but a new name that does not at all hint that it may do icase comparison as necessary will catch me by an opposite confusion in the future.
I have not yet formed a firm opinion if pathcmp() conveys enough hint.
Re: [PATCH v2 04/12] worktree.c: mark current worktree
Quoted 8 lines · Junio C Hamano
Quoted 8 lines
Quoted 7 lines
Quoted 2 lines
While we're at it, how about renaming it to pathcmp (and its friend
strncmp_icaseto pathncmp)?Yes, that seems like a good idea. For anyone familiar with
strcasecmp()orstricmp(), having "icase" in the name makes it seem as though it's unconditionally case-insensitive, so dropping it from the name would likely be beneficial.
Quoted 7 lines
Quoted 2 lines
Seconded (thirded?). I have been caught by this confusion in the past, too.
I agree that
strcmp_icase()gives a false impression that it always ignores case differences, but a new name that does not at all hint that it may do icase comparison as necessary will catch me by an opposite confusion in the future.
To me, the benefit is that you don't have to care about ignore_case. You have asked to compare two paths, and any system-appropriate magic should be applied. That may be icase, or it may be weird unicode normalization.
I think the key thing missing is that this is only about _filesystem_ paths. You would not want to use it for tree-to-tree pathname comparisons. So maybe "fspath" or something would be more descriptive.
-Peff
Re: [PATCH v2 04/12] worktree.c: mark current worktree
Quoted 7 lines · Jeff King
To me, the benefit is that you don't have to care about
ignore_case. You have asked to compare two paths, and any system-appropriate magic should be applied. That may be icase, or it may be weird unicode normalization.I think the key thing missing is that this is only about _filesystem_ paths. You would not want to use it for tree-to-tree pathname comparisons. So maybe "fspath" or something would be more descriptive.
Yup, I would be very happy with "fs" in the name.
PATCH v2 5/12 path.c: refactor and add worktree_git_path()
In reply to Nguyễn Thái Ngọc Duydo_git_path(), which is the common code for all git_path* functions, is modified to take a worktree struct and can produce paths for any worktree.
worktree_git_path() is the first function that makes use of this. It can be used to write code that can examine any worktree. For example, wt_status_get_state() will be converted using this to take am/rebase/... state of any worktree.
--- path.c | 34 ++++++++++++++++++++++++++++------ worktree.h | 11 +++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-)
path.c +28 -6
@@ -5,6 +5,7 @@#include "strbuf.h" #include "string-list.h" #include "dir.h" +#include "worktree.h" static int get_st_mode_bits (const char *path,int *mode) {
@@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)update_common_dir (buf, git_dir_len,NULL ); } -static void do_git_path (struct strbuf *buf,const char *fmt,va_list args) +static void do_git_path (const struct worktree *wt,struct strbuf *buf, +const char *fmt,va_list args) {int gitdir_len; -strbuf_addstr (buf,get_git_dir ()); +strbuf_addstr (buf,get_worktree_git_dir (wt));if (buf->len && !is_dir_sep (buf->buf[buf->len -1 ]))strbuf_addch (buf,'/' ); gitdir_len = buf->len;
@@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)va_list args;strbuf_reset (buf);va_start (args, fmt); -do_git_path (buf, fmt, args); +do_git_path (NULL , buf, fmt, args);va_end (args);return buf->buf; }
@@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) {va_list args;va_start (args, fmt); -do_git_path (sb, fmt, args); +do_git_path (NULL , sb, fmt, args);va_end (args); }
@@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)struct strbuf *pathname =get_pathname ();va_list args;va_start (args, fmt); -do_git_path (pathname, fmt, args); +do_git_path (NULL , pathname, fmt, args);va_end (args);return pathname->buf; }
@@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)struct strbuf path =STRBUF_INIT ;va_list args;va_start (args, fmt); -do_git_path (&path, fmt, args); +do_git_path (NULL , &path, fmt, args);va_end (args);return strbuf_detach (&path,NULL ); }
@@ -454,6 +456,26 @@ const char *mkpath(const char *fmt, ...)return cleanup_path (pathname->buf); } +const char *worktree_git_path (const struct worktree *wt,const char *fmt, ...) +{ +struct strbuf *pathname =get_pathname (); +va_list args; +va_start (args, fmt); +do_git_path (wt, pathname, fmt, args); +va_end (args); +return pathname->buf; +} + +char *worktree_git_pathdup (const struct worktree *wt,const char *fmt, ...) +{ +struct strbuf path =STRBUF_INIT ; +va_list args; +va_start (args, fmt); +do_git_path (wt, &path, fmt, args); +va_end (args); +return strbuf_detach (&path,NULL ); +} +static void do_submodule_path (struct strbuf *buf,const char *path,const char *fmt,va_list args) {
worktree.h +11 -0
@@ -43,4 +43,15 @@ extern void free_worktrees(struct worktree **);extern const struct worktree *find_shared_symref (const char *symref,const char *target); +/* +* Similar to git_path() and git_pathdup() but can produce paths for a +* specified worktree instead of current one +*/ +extern const char *worktree_git_path (const struct worktree *wt, +const char *fmt, ...) + __attribute__((format (printf,2 ,3 ))); +extern char *worktree_git_pathdup (const struct worktree *wt, +const char *fmt, ...) + __attribute__((format (printf,2 ,3 ))); +#endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v2 6/12 wt-status.c: split rebase detection out of wt_status_get_state()
In reply to Nguyễn Thái Ngọc Duyworktree.c:find_shared_symref() later needs to know if a branch is being rebased, and only rebased only. Split this code so it can be used independently from other in-progress tests.
--- wt-status.c | 23 +++++++++++++++++------ wt-status.h | 1 + 2 files changed, 18 insertions(+), 6 deletions(-)
wt-status.c +17 -6
@@ -1360,15 +1360,11 @@ static void wt_status_get_detached_from(struct wt_status_state *state)strbuf_release (&cb.buf); } -void wt_status_get_state (struct wt_status_state *state, -int get_detached_from) +int wt_status_check_rebase (struct wt_status_state *state) {struct stat st; -unsigned char sha1[20 ]; -if (!stat (git_path_merge_head (), &st)) { - state->merge_in_progress =1 ; - }else if (!stat (git_path ("rebase-apply" ), &st)) { +if (!stat (git_path ("rebase-apply" ), &st)) {if (!stat (git_path ("rebase-apply/applying" ), &st)) { state->am_in_progress =1 ;if (!stat (git_path ("rebase-apply/patch" ), &st) && !st.st_size)
@@ -1385,6 +1381,21 @@ void wt_status_get_state(struct wt_status_state *state, state->rebase_in_progress =1 ; state->branch =read_and_strip_branch ("rebase-merge/head-name" ); state->onto =read_and_strip_branch ("rebase-merge/onto" ); + }else +return 0 ; +return 1 ; +} + +void wt_status_get_state (struct wt_status_state *state, +int get_detached_from) +{ +struct stat st; +unsigned char sha1[20 ]; + +if (!stat (git_path_merge_head (), &st)) { + state->merge_in_progress =1 ; + }else if (wt_status_check_rebase (state)) { +/* all set */ }else if (!stat (git_path_cherry_pick_head (), &st) && !get_sha1 ("CHERRY_PICK_HEAD" , sha1)) { state->cherry_pick_in_progress =1 ;
wt-status.h +1 -0
@@ -100,6 +100,7 @@ void wt_status_prepare(struct wt_status *s);void wt_status_print (struct wt_status *s);void wt_status_collect (struct wt_status *s);void wt_status_get_state (struct wt_status_state *state,int get_detached_from); +int wt_status_check_rebase (struct wt_status_state *state);void wt_shortstatus_print (struct wt_status *s);void wt_porcelain_print (struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
Re: [PATCH v2 06/12] wt-status.c: split rebase detection out of wt_status_get_state()
Quoted 2 lines · Nguyễn Thái Ngọc Duy
worktree.c:find_shared_symref() later needs to know if a branch is being rebased, and only rebased only. Split this code so it can be used
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Err ... what?
ATB,
Ramsay Jones
Re: [PATCH v2 06/12] wt-status.c: split rebase detection out of wt_status_get_state()
Quoted 7 lines · Ramsay Jones
Quoted 2 lines · Nguyễn Thái Ngọc Duy
worktree.c:find_shared_symref() later needs to know if a branch is being rebased, and only rebased only. Split this code so it can be used
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Err ... what?
wt_status_get_state() detects more than rebase, it does bisect, cherry-pick, detached head as well. I "only" too much there though.
Signature · 1 line
Duy
PATCH v2 7/12 wt-status.c: make wt_status_check_rebase() work on any worktree
In reply to Nguyễn Thái Ngọc DuyThis is a preparation step for find_shared_symref() to detect if any worktree is being rebased.
--- wt-status.c | 33 ++++++++++++++++++++------------- wt-status.h | 5 ++++- 2 files changed, 24 insertions(+), 14 deletions(-)
wt-status.c +20 -13
@@ -15,6 +15,7 @@#include "column.h" #include "strbuf.h" #include "utf8.h" +#include "worktree.h" static const char cut_line[] ="------------------------ >8 ------------------------\n" ;
@@ -1262,13 +1263,13 @@ static void show_bisect_in_progress(struct wt_status *s,/* * Extract branch information from rebase/bisect */ -static char *read_and_strip_branch (const char *path) +static char *get_branch (const struct worktree *wt,const char *path) {struct strbuf sb =STRBUF_INIT ;unsigned char sha1[20 ];const char *branch_name; -if (strbuf_read_file (&sb,git_path ("%s" , path),0 ) <=0 ) +if (strbuf_read_file (&sb,worktree_git_path (wt,"%s" , path),0 ) <=0 ) goto got_nothing;while (sb.len && sb.buf[sb.len -1 ] =='\n' )
@@ -1295,6 +1296,11 @@ got_nothing:return NULL ; } +static char *read_and_strip_branch (const char *path) +{ +return get_branch (NULL , path); +} +struct grab_1st_switch_cbdata {struct strbuf buf;unsigned char nsha1[20 ];
@@ -1360,27 +1366,28 @@ static void wt_status_get_detached_from(struct wt_status_state *state)strbuf_release (&cb.buf); } -int wt_status_check_rebase (struct wt_status_state *state) +int wt_status_check_rebase (const struct worktree *wt, +struct wt_status_state *state) {struct stat st; -if (!stat (git_path ("rebase-apply" ), &st)) { -if (!stat (git_path ("rebase-apply/applying" ), &st)) { +if (!stat (worktree_git_path (wt,"rebase-apply" ), &st)) { +if (!stat (worktree_git_path (wt,"rebase-apply/applying" ), &st)) { state->am_in_progress =1 ; -if (!stat (git_path ("rebase-apply/patch" ), &st) && !st.st_size) +if (!stat (worktree_git_path (wt,"rebase-apply/patch" ), &st) && !st.st_size) state->am_empty_patch =1 ; }else { state->rebase_in_progress =1 ; - state->branch =read_and_strip_branch ("rebase-apply/head-name" ); - state->onto =read_and_strip_branch ("rebase-apply/onto" ); + state->branch =get_branch (wt,"rebase-apply/head-name" ); + state->onto =get_branch (wt,"rebase-apply/onto" ); } - }else if (!stat (git_path ("rebase-merge" ), &st)) { -if (!stat (git_path ("rebase-merge/interactive" ), &st)) + }else if (!stat (worktree_git_path (wt,"rebase-merge" ), &st)) { +if (!stat (worktree_git_path (wt,"rebase-merge/interactive" ), &st)) state->rebase_interactive_in_progress =1 ;else state->rebase_in_progress =1 ; - state->branch =read_and_strip_branch ("rebase-merge/head-name" ); - state->onto =read_and_strip_branch ("rebase-merge/onto" ); + state->branch =get_branch (wt,"rebase-merge/head-name" ); + state->onto =get_branch (wt,"rebase-merge/onto" ); }else return 0 ;return 1 ;
@@ -1394,7 +1401,7 @@ void wt_status_get_state(struct wt_status_state *state,if (!stat (git_path_merge_head (), &st)) { state->merge_in_progress =1 ; - }else if (wt_status_check_rebase (state)) { + }else if (wt_status_check_rebase (NULL , state)) {/* all set */ }else if (!stat (git_path_cherry_pick_head (), &st) && !get_sha1 ("CHERRY_PICK_HEAD" , sha1)) {
wt-status.h +4 -1
@@ -6,6 +6,8 @@#include "color.h" #include "pathspec.h" +struct worktree ; +enum color_wt_status {WT_STATUS_HEADER =0 ,WT_STATUS_UPDATED ,
@@ -100,7 +102,8 @@ void wt_status_prepare(struct wt_status *s);void wt_status_print (struct wt_status *s);void wt_status_collect (struct wt_status *s);void wt_status_get_state (struct wt_status_state *state,int get_detached_from); -int wt_status_check_rebase (struct wt_status_state *state); +int wt_status_check_rebase (const struct worktree *wt, +struct wt_status_state *state);void wt_shortstatus_print (struct wt_status *s);void wt_porcelain_print (struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v2 8/12 worktree.c: avoid referencing to worktrees[i] multiple times
In reply to Nguyễn Thái Ngọc Duy--- worktree.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
worktree.c +4 -2
@@ -221,10 +221,12 @@ const struct worktree *find_shared_symref(const char *symref, worktrees =get_worktrees ();for (i =0 ; worktrees[i]; i++) { +struct worktree *wt = worktrees[i]; +strbuf_reset (&path);strbuf_reset (&sb);strbuf_addf (&path,"%s/%s" , -get_worktree_git_dir (worktrees[i]), +get_worktree_git_dir (wt), symref);if (parse_ref (path.buf, &sb,NULL )) {
@@ -232,7 +234,7 @@ const struct worktree *find_shared_symref(const char *symref, }if (!strcmp (sb.buf, target)) { - existing = worktrees[i]; + existing = wt;break ; } }
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v2 9/12 worktree.c: test if branch being rebased in another worktree
In reply to Nguyễn Thái Ngọc DuyThis function find_shared_symref() is used in a couple places:
1) in builtin/branch.c: it's used to detect if a branch is checked out elsewhere and refuse to delete the branch.
2) in builtin/notes.c: it's used to detect if a note is being merged in another worktree
3) in branch.c, the function die_if_checked_out() is actually used by "git checkout" and "git worktree add" to see if a branch is already checked out elsewhere and refuse the operation.
In cases 1 and 3, if a rebase is ongoing, "HEAD" will be in detached mode, find_shared_symref() fails to detect it and declares "no branch is checked out here", which is incorrect.
This patch tightens the test. If the given symref is "HEAD", we try to detect if rebase is ongoing. If so return the branch being rebased. This makes checkout and branch delete operations safer because you can't checkout a branch being rebased in another place, or delete it.
Special case for checkout. If the current branch is being rebased, git-rebase.sh may use "git checkout" to abort and return back to the original branch. The updated test in find_shared_symref() will prevent that and "git rebase --abort" will fail as a result. find_shared_symref() and die_if_checked_out() have to learn a new option ignore_current_worktree to loose the test a bit.
--- branch.c | 4 ++-- branch.h | 2 +- builtin/branch.c | 2 +- builtin/checkout.c | 2 +- builtin/notes.c | 2 +- builtin/worktree.c | 4 ++-- t/t2025-worktree-add.sh | 38 ++++++++++++++++++++++++++++++++++++++ worktree.c | 32 +++++++++++++++++++++++++++++++- worktree.h | 3 ++- 9 files changed, 79 insertions(+), 10 deletions(-)
branch.c +2 -2
@@ -334,11 +334,11 @@ void remove_branch_state(void)unlink (git_path_squash_msg ()); } -void die_if_checked_out (const char *branch) +void die_if_checked_out (const char *branch,int ignore_current_worktree) {const struct worktree *wt; - wt =find_shared_symref ("HEAD" , branch); + wt =find_shared_symref ("HEAD" , branch, ignore_current_worktree);if (wt) {skip_prefix (branch,"refs/heads/" , &branch); die(_("'%s' is already checked out at '%s'"),
branch.h +1 -1
@@ -58,7 +58,7 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);* worktree and die (with a message describing its checkout location) if * it is. */ -extern void die_if_checked_out (const char *branch); +extern void die_if_checked_out (const char *branch,int ignore_current_worktree);/* * Update all per-worktree HEADs pointing at the old ref to point the new ref.
builtin/branch.c +1 -1
@@ -221,7 +221,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,if (kinds ==FILTER_REFS_BRANCHES ) {const struct worktree *wt = -find_shared_symref ("HEAD" , name); +find_shared_symref ("HEAD" , name,0 );if (wt) { error(_("Cannot delete branch '%s' " "checked out at '%s'"),
builtin/checkout.c +1 -1
@@ -1111,7 +1111,7 @@ static int checkout_branch(struct checkout_opts *opts,char *head_ref =resolve_refdup ("HEAD" ,0 , sha1, &flag);if (head_ref && (!(flag &REF_ISSYMREF ) ||strcmp (head_ref, new->path))) -die_if_checked_out (new->path); +die_if_checked_out (new->path,1 );free (head_ref); }
builtin/notes.c +1 -1
@@ -852,7 +852,7 @@ static int merge(int argc, const char **argv, const char *prefix)update_ref (msg.buf,"NOTES_MERGE_PARTIAL" , result_sha1,NULL ,0 ,UPDATE_REFS_DIE_ON_ERR );/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ - wt =find_shared_symref ("NOTES_MERGE_REF" ,default_notes_ref ()); + wt =find_shared_symref ("NOTES_MERGE_REF" ,default_notes_ref (),0 );if (wt)die (_ ("A notes merge into %s is already in-progress at %s" ),default_notes_ref (), wt->path);
builtin/worktree.c +2 -2
@@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname,if (!opts->detach && !strbuf_check_branch_ref (&symref, refname) &&ref_exists (symref.buf)) {/* it's a branch */ if (!opts->force) -die_if_checked_out (symref.buf); +die_if_checked_out (symref.buf,0 ); }else {/* must be a commit */ commit =lookup_commit_reference_by_name (refname);if (!commit)
@@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix)if (!opts.force && !strbuf_check_branch_ref (&symref, opts.new_branch) &&ref_exists (symref.buf)) -die_if_checked_out (symref.buf); +die_if_checked_out (symref.buf,0 );strbuf_release (&symref); }
t/t2025-worktree-add.sh +38 -0
@@ -4,6 +4,8 @@ test_description='test git worktree add' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + test_expect_success 'setup' ' test_commit init '
@@ -225,4 +227,40 @@ test_expect_success '"add" worktree with --checkout' ' test_cmp init.t swamp2/init.t ' +test_expect_success 'put a worktree under rebase' ' + git worktree add under-rebase && + ( + cd under-rebase && + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + git worktree list | grep "under-rebase.*detached HEAD" + ) +' + +test_expect_success 'add a worktree, checking out a rebased branch' ' + test_must_fail git worktree add new-rebase under-rebase && + ! test -d new-rebase +' + +test_expect_success 'checking out a rebased branch from another worktree' ' + git worktree add new-place && + test_must_fail git -C new-place checkout under-rebase +' + +test_expect_success 'not allow to delete a branch under rebase' ' + ( + cd under-rebase && + test_must_fail git branch -D under-rebase + ) +' + +test_expect_success 'check out from current worktree branch ok' ' + ( + cd under-rebase && + git checkout under-rebase && + git checkout - && + git rebase --abort + ) +' + test_done
worktree.c +31 -1
@@ -3,6 +3,7 @@#include "strbuf.h" #include "worktree.h" #include "dir.h" +#include "wt-status.h" void free_worktrees (struct worktree **worktrees) {
@@ -207,8 +208,27 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +static int is_worktree_being_rebased (const struct worktree *wt, +const char *target) +{ +struct wt_status_state state; +int found_rebase; + +memset (&state,0 ,sizeof (state)); + found_rebase =wt_status_check_rebase (wt, &state) && + ((state.rebase_in_progress || + state.rebase_interactive_in_progress) && + state.branch && +starts_with (target,"refs/heads/" ) && + !strcmp (state.branch, target +strlen ("refs/heads/" ))); +free (state.branch); +free (state.onto); +return found_rebase; +} +const struct worktree *find_shared_symref (const char *symref, -const char *target) +const char *target, +int ignore_current_worktree) {const struct worktree *existing =NULL ;struct strbuf path =STRBUF_INIT ;
@@ -223,6 +243,16 @@ const struct worktree *find_shared_symref(const char *symref,for (i =0 ; worktrees[i]; i++) {struct worktree *wt = worktrees[i]; +if (ignore_current_worktree && wt->is_current) +continue ; + +if (wt->is_detached && !strcmp (symref,"HEAD" )) { +if (is_worktree_being_rebased (wt, target)) { + existing = wt; +break ; + } + } +strbuf_reset (&path);strbuf_reset (&sb); strbuf_addf(&path, "%s/%s",
worktree.h +2 -1
@@ -41,7 +41,8 @@ extern void free_worktrees(struct worktree **);* may be destroyed by the next call. */ extern const struct worktree *find_shared_symref (const char *symref, -const char *target); +const char *target, +int ignore_current_worktree);/* * Similar to git_path() and git_pathdup() but can produce paths for a
Signature · 1 line
2.8.0.rc0.210.gd302cd2
Re: [PATCH v2 09/12] worktree.c: test if branch being rebased in another worktree
Quoted 1 line · Nguyễn Thái Ngọc Duy
Subject: Re: [PATCH v2 09/12]
worktree.c: test if branch being rebased in another worktree
Lacks the verb? Perhaps s/being/is/ is sufficient.
Quoted 8 lines
This function
find_shared_symref()is used in a couple places:1) in
builtin/branch.c: it's used to detect if a branch is checked out elsewhere and refuse to delete the branch.2) in
builtin/notes.c: it's used to detect if a note is being merged in another worktree
Quoted 7 lines
3) in
branch.c, the functiondie_if_checked_out()is actually used by "git checkout" and "git worktree add" to see if a branch is already checked out elsewhere and refuse the operation.In cases 1 and 3, if a rebase is ongoing, "HEAD" will be in detached mode,
find_shared_symref()fails to detect it and declares "no branch is checked out here", which is incorrect.
which is technically correct but is not what we want to check.
Quoted 4 lines
This patch tightens the test. If the given symref is "HEAD", we try to detect if rebase is ongoing. If so return the branch being rebased. This makes checkout and branch delete operations safer because you can't checkout a branch being rebased in another place, or delete it.
Is rebase the only thing that tentatively detach before working on the real branch? It may be currently the case, but I would imagine that we want to makefind-shared-symref to be responsible for detecting new cases other than rebase that we may introduce in the future, in which case we may want to leave come comment near the function to describe that expectation.
Quoted 6 lines
Special case for checkout. If the current branch is being rebased,
git-rebase.shmay use "git checkout" to abort and return back to the original branch. The updated test infind_shared_symref()will prevent that and "git rebase --abort" will fail as a result.find_shared_symref()anddie_if_checked_out()have to learn a new optionignore_current_worktreeto loose the test a bit.
s/loose/&n/
Quoted 14 lines
branch.c +2
-1+void die_if_checked_out (const char *branch,int ignore_current_worktree) {const struct worktree *wt; - wt =find_shared_symref ("HEAD" , branch); + wt =find_shared_symref ("HEAD" , branch, ignore_current_worktree);if (wt) {skip_prefix (branch,"refs/heads/" , &branch); die(_("'%s' is already checked out at '%s'"),builtin/checkout.c +0
-0
builtin/checkout.c static int checkout_branch(struct checkout_opts *opts, · line 1116
Quoted 7 lines
builtin/checkout.c +1
-1@@ -1111,7 +1111,7 @@ static int checkout_branch(struct checkout_opts *opts,char *head_ref =resolve_refdup ("HEAD" ,0 , sha1, &flag);if (head_ref && (!(flag &REF_ISSYMREF ) ||strcmp (head_ref, new->path))) -die_if_checked_out (new->path); +die_if_checked_out (new->path,1 );free (head_ref); }
So the idea is "if the branch is checked out (or "being worked on" even if technically the HEAD is detached, like with 'rebase') anywhere, callers of die-if-checked-out in general want to die; but for this caller, it is OK if the place the branch is checked out or being worked on is in this repository"?
I understand die_if_checked_out() taking that "ignore this one" bit may be sensible, but I do not understand why find_shared_symref() needs to be told about it. The change makes the meaning of the find_shared_symref() function unclear. It used to mean "This symbolic ref cannot point at the same ref in different worktrees, so for a given pair of a symbolic ref and a concrete ref, there can be at most one worktree in which the symbolic ref points at that ref". That is already a mouthful. As the worktree structure already have "Am I the current worktree?" bit, "ignore" logic can easily be done inside die_if_checked_out() and that would help find_shared_symref() stay simpler and more focused function, no?
Re: [PATCH v2 09/12] worktree.c: test if branch being rebased in another worktree
Quoted 19 lines · Junio C Hamano
builtin/checkout.c static int checkout_branch(struct checkout_opts *opts, · line 1116
Quoted 12 lines
builtin/checkout.c +1
-1@@ -1111,7 +1111,7 @@ static int checkout_branch(struct checkout_opts *opts,char *head_ref =resolve_refdup ("HEAD" ,0 , sha1, &flag);if (head_ref && (!(flag &REF_ISSYMREF ) ||strcmp (head_ref, new->path))) -die_if_checked_out (new->path); +die_if_checked_out (new->path,1 );free (head_ref); }So the idea is "if the branch is checked out (or "being worked on" even if technically the HEAD is detached, like with 'rebase') anywhere, callers of die-if-checked-out in general want to die; but for this caller, it is OK if the place the branch is checked out or being worked on is in this repository"?
Quoted 11 lines
I understand
die_if_checked_out()taking that "ignore this one" bit may be sensible, but I do not understand whyfind_shared_symref()needs to be told about it. The change makes the meaning of thefind_shared_symref()function unclear. It used to mean "This symbolic ref cannot point at the same ref in different worktrees, so for a given pair of a symbolic ref and a concrete ref, there can be at most one worktree in which the symbolic ref points at that ref". That is already a mouthful. As the worktree structure already have "Am I the current worktree?" bit, "ignore" logic can easily be done insidedie_if_checked_out()and that would helpfind_shared_symref()stay simpler and more focused function, no?
That was the intention when I made find_shared_symref() return struct worktree * instead of char *. I forget why I changed my mind and not do so. The only case when find_shared_symref should do this is when a ref is shared twice, then we need to ignore current worktree from inside the loop. But that can't happen. Will move this ignore-current-worktree test to die_if_checked_out().
Signature · 1 line
Duy
PATCH v2 10/12 wt-status.c: split bisect detection out of wt_status_get_state()
In reply to Nguyễn Thái Ngọc DuyAnd make it work with any given worktree, in preparation for (again) find_shared_symref(). read_and_strip_branch() is deleted because it's no longer used.
--- wt-status.c | 23 ++++++++++++++--------- wt-status.h | 2 ++ 2 files changed, 16 insertions(+), 9 deletions(-)
wt-status.c +14 -9
@@ -1296,11 +1296,6 @@ got_nothing:return NULL ; } -static char *read_and_strip_branch (const char *path) -{ -return get_branch (NULL , path); -} -struct grab_1st_switch_cbdata {struct strbuf buf;unsigned char nsha1[20 ];
@@ -1393,6 +1388,19 @@ int wt_status_check_rebase(const struct worktree *wt,return 1 ; } +int wt_status_check_bisect (const struct worktree *wt, +struct wt_status_state *state) +{ +struct stat st; + +if (!stat (worktree_git_path (wt,"BISECT_LOG" ), &st)) { + state->bisect_in_progress =1 ; + state->branch =get_branch (wt,"BISECT_START" ); +return 1 ; + } +return 0 ; +} +void wt_status_get_state (struct wt_status_state *state,int get_detached_from) {
@@ -1408,10 +1416,7 @@ void wt_status_get_state(struct wt_status_state *state, state->cherry_pick_in_progress =1 ;hashcpy (state->cherry_pick_head_sha1, sha1); } -if (!stat (git_path ("BISECT_LOG" ), &st)) { - state->bisect_in_progress =1 ; - state->branch =read_and_strip_branch ("BISECT_START" ); - } +wt_status_check_bisect (NULL , state);if (!stat (git_path_revert_head (), &st) && !get_sha1 ("REVERT_HEAD" , sha1)) { state->revert_in_progress =1 ;
wt-status.h +2 -0
@@ -104,6 +104,8 @@ void wt_status_collect(struct wt_status *s);void wt_status_get_state (struct wt_status_state *state,int get_detached_from);int wt_status_check_rebase (const struct worktree *wt,struct wt_status_state *state); +int wt_status_check_bisect (const struct worktree *wt, +struct wt_status_state *state);void wt_shortstatus_print (struct wt_status *s);void wt_porcelain_print (struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v2 11/12 worktree.c: test if branch being bisected in another worktree
In reply to Nguyễn Thái Ngọc DuySimilar to the rebase case, we want to detect if "HEAD" in some worktree is being bisected because
1) we do not want to checkout this branch in another worktree, after bisect is done it will want to go back to this branch
2) we do not want to delete the branch is either or git bisect will fail to return to the (long gone) branch
--- t/t2025-worktree-add.sh | 13 +++++++++++++ worktree.c | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+)
t/t2025-worktree-add.sh +13 -0
@@ -263,4 +263,17 @@ test_expect_success 'check out from current worktree branch ok' ' ) ' +test_expect_success 'checkout a branch under bisect' ' + git worktree add under-bisect && + ( + cd under-bisect && + git bisect start && + git bisect bad && + git bisect good HEAD~2 && + git worktree list | grep "under-bisect.*detached HEAD" && + test_must_fail git worktree add new-bisect under-bisect && + ! test -d new-bisect + ) +' + test_done
worktree.c +19 -0
@@ -226,6 +226,21 @@ static int is_worktree_being_rebased(const struct worktree *wt,return found_rebase; } +static int is_worktree_being_bisected (const struct worktree *wt, +const char *target) +{ +struct wt_status_state state; +int found_rebase; + +memset (&state,0 ,sizeof (state)); + found_rebase =wt_status_check_bisect (wt, &state) && + state.branch && +starts_with (target,"refs/heads/" ) && + !strcmp (state.branch, target +strlen ("refs/heads/" )); +free (state.branch); +return found_rebase; +} +const struct worktree *find_shared_symref (const char *symref,const char *target,int ignore_current_worktree)
@@ -251,6 +266,10 @@ const struct worktree *find_shared_symref(const char *symref, existing = wt;break ; } +if (is_worktree_being_bisected (wt, target)) { + existing = wt; +break ; + } }strbuf_reset (&path);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v2 12/12 branch: do not rename a branch under bisect or rebase
In reply to Nguyễn Thái Ngọc DuyThe branch name in that case could be saved in rebase's head_name or bisect's BISECT_START files. Ideally we should try to update them as well. But it's trickier (*). Let's play safe and see if the user complains about inconveniences before doing that.
(*) If we do it, bisect and rebase need to provide an API to rename branches. We can't do it in worktree.c or builtin/branch.c because when other people change rebase/bisect code, they may not be aware of this code and accidentally break it (e.g. rename the branch file, or refer to the branch in new files). It's a lot more work.
--- builtin/branch.c | 25 +++++++++++++++++++++++++ t/t2025-worktree-add.sh | 8 ++++++++ worktree.c | 8 ++++---- worktree.h | 3 +++ 4 files changed, 40 insertions(+), 4 deletions(-)
builtin/branch.c +25 -0
@@ -524,6 +524,29 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortinref_array_clear (&array); } +static void reject_rebase_or_bisect_branch (const char *target) +{ +struct worktree **worktrees =get_worktrees (); +int i; + +for (i =0 ; worktrees[i]; i++) { +struct worktree *wt = worktrees[i]; + +if (!wt->is_detached) +continue ; + +if (is_worktree_being_rebased (wt, target)) +die (_ ("Branch %s is being rebased at %s" ), + target, wt->path); + +if (is_worktree_being_bisected (wt, target)) +die (_ ("Branch %s is being bisected at %s" ), + target, wt->path); + } + +free_worktrees (worktrees); +} +static void rename_branch (const char *oldname,const char *newname,int force) {struct strbuf oldref =STRBUF_INIT , newref =STRBUF_INIT , logmsg =STRBUF_INIT ;
@@ -553,6 +576,8 @@ static void rename_branch(const char *oldname, const char *newname, int force)validate_new_branchname (newname, &newref, force, clobber_head_ok); +reject_rebase_or_bisect_branch (oldref.buf); +strbuf_addf (&logmsg,"Branch: renamed %s to %s" , oldref.buf, newref.buf);
t/t2025-worktree-add.sh +8 -0
@@ -254,6 +254,10 @@ test_expect_success 'not allow to delete a branch under rebase' ' ) ' +test_expect_success 'rename a branch under rebase not allowed' ' + test_must_fail git branch -M under-rebase rebase-with-new-name +' + test_expect_success 'check out from current worktree branch ok' ' ( cd under-rebase &&
@@ -276,4 +280,8 @@ test_expect_success 'checkout a branch under bisect' ' ) ' +test_expect_success 'rename a branch under bisect not allowed' ' + test_must_fail git branch -M under-bisect bisect-with-new-name +' + test_done
worktree.c +4 -4
@@ -208,8 +208,8 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } -static int is_worktree_being_rebased (const struct worktree *wt, -const char *target) +int is_worktree_being_rebased (const struct worktree *wt, +const char *target) {struct wt_status_state state;int found_rebase;
@@ -226,8 +226,8 @@ static int is_worktree_being_rebased(const struct worktree *wt,return found_rebase; } -static int is_worktree_being_bisected (const struct worktree *wt, -const char *target) +int is_worktree_being_bisected (const struct worktree *wt, +const char *target) {struct wt_status_state state;int found_rebase;
worktree.h +3 -0
@@ -44,6 +44,9 @@ extern const struct worktree *find_shared_symref(const char *symref,const char *target, int ignore_current_worktree); +int is_worktree_being_rebased (const struct worktree *wt,const char *target); +int is_worktree_being_bisected (const struct worktree *wt,const char *target); +/* * Similar to git_path() and git_pathdup() but can produce paths for a * specified worktree instead of current one
Signature · 1 line
2.8.0.rc0.210.gd302cd2
Re: [PATCH v2 00/12] fix checking out a being-rebased branch
In reply to Nguyễn Thái Ngọc DuyQuoted 5 lines · Nguyễn Thái Ngọc Duy
Much happier with this version. This makes
- git checkout refuse if a branch is under rebase or bisect elsewhere
Quoted 7 lines
- git worktree add refuse if a branch is under rebase or bisect
- git branch -D refuse if a branch is under rebase or bisect. This applies for single worktree case as well.
- git branch -M refuse if a branch is under rebase or bisect (single worktree case as well)
I agree that this reads much nicer than v1, especially with the abstraction around find_shared_symref() that returns the actual worktree.
Quoted 14 lines
[01/12]
path.c: addgit_common_path()andstrbuf_git_common_path()[02/12]worktree.c: store "id" instead of "git_dir"
[03/12]worktree.c: makefind_shared_symref()return struct worktree * [04/12]worktree.c: mark current worktree
[05/12]path.c: refactor and addworktree_git_path()
[06/12]wt-status.c: split rebase detection out ofwt_status_get_state()[07/12]wt-status.c: makewt_status_check_rebase()work on any worktree [08/12]worktree.c: avoid referencing to worktrees[i] multiple times [09/12]worktree.c: test if branch being rebased in another worktree [10/12]wt-status.c: split bisect detection out ofwt_status_get_state()[11/12]worktree.c: test if branch being bisected in another worktree [12/12] branch: do not rename a branch under bisect or rebaseTotal 13 files changed, 335 insertions(+), 67 deletions(-)
v3 cover nd/worktree-various-heads
In reply to Nguyễn Thái Ngọc Duyv3 fixes all the things found in v2, deletes unused stuff and adds a new patch 01/13 that renames str(n)cmp_icase to fspath(n)cmp.
Interdiff
branch.c +6 -6
@@ -338,12 +338,12 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree) {const struct worktree *wt; - wt =find_shared_symref ("HEAD" , branch, ignore_current_worktree); -if (wt) { -skip_prefix (branch,"refs/heads/" , &branch); -die (_ ("'%s' is already checked out at '%s'" ), - branch, wt->path); - } + wt =find_shared_symref ("HEAD" , branch); +if (!wt || (ignore_current_worktree && wt->is_current)) +return ; +skip_prefix (branch,"refs/heads/" , &branch); +die (_ ("'%s' is already checked out at '%s'" ), + branch, wt->path); }int replace_each_worktree_head_symref (const char *oldref,const char *newref)
builtin/branch.c +1 -1
@@ -221,7 +221,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,if (kinds ==FILTER_REFS_BRANCHES ) {const struct worktree *wt = -find_shared_symref ("HEAD" , name,0 ); +find_shared_symref ("HEAD" , name);if (wt) { error(_("Cannot delete branch '%s' " "checked out at '%s'"),
builtin/notes.c +1 -1
@@ -852,7 +852,7 @@ static int merge(int argc, const char **argv, const char *prefix)update_ref (msg.buf,"NOTES_MERGE_PARTIAL" , result_sha1,NULL ,0 ,UPDATE_REFS_DIE_ON_ERR );/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ - wt =find_shared_symref ("NOTES_MERGE_REF" ,default_notes_ref (),0 ); + wt =find_shared_symref ("NOTES_MERGE_REF" ,default_notes_ref ());if (wt)die (_ ("A notes merge into %s is already in-progress at %s" ),default_notes_ref (), wt->path);
dir.c +6 -7
@@ -53,13 +53,12 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, int check_only, const struct path_simplify *simplify);static int get_dtype (struct dirent *de,const char *path,int len); -/* helper string functions with support for the ignore_case flag */ -int strcmp_icase (const char *a,const char *b) +int fspathcmp (const char *a,const char *b) {return ignore_case ?strcasecmp (a, b) :strcmp (a, b); } -int strncmp_icase (const char *a,const char *b,size_t count) +int fspathncmp (const char *a,const char *b,size_t count) {return ignore_case ?strncasecmp (a, b, count) :strncmp (a, b, count); }
@@ -802,12 +801,12 @@ int match_basename(const char *basename, int basenamelen, {if (prefix == patternlen) {if (patternlen == basenamelen && - !strncmp_icase (pattern, basename, basenamelen)) + !fspathncmp (pattern, basename, basenamelen))return 1 ; }else if (flags &EXC_FLAG_ENDSWITH ) {/* "*literal" matching against "fooliteral" */ if (patternlen -1 <= basenamelen && - !strncmp_icase (pattern +1 , + !fspathncmp (pattern +1 , basename + basenamelen - (patternlen -1 ), patternlen -1 ))return 1 ;
@@ -844,7 +843,7 @@ int match_pathname(const char *pathname, int pathlen,*/ if (pathlen < baselen +1 || (baselen && pathname[baselen] !='/' ) || -strncmp_icase (pathname, base, baselen)) +fspathncmp (pathname, base, baselen))return 0 ; namelen = baselen ? pathlen - baselen -1 : pathlen;
@@ -858,7 +857,7 @@ int match_pathname(const char *pathname, int pathlen,if (prefix > namelen)return 0 ; -if (strncmp_icase (pattern, name, prefix)) +if (fspathncmp (pattern, name, prefix))return 0 ; pattern += prefix; patternlen -= prefix;
dir.h +2 -2
@@ -270,8 +270,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);/* tries to remove the path with empty directories along it, ignores ENOENT */ extern int remove_path (const char *path); -extern int strcmp_icase (const char *a,const char *b); -extern int strncmp_icase (const char *a,const char *b,size_t count); +extern int fspathcmp (const char *a,const char *b); +extern int fspathncmp (const char *a,const char *b,size_t count);extern int fnmatch_icase (const char *pattern,const char *string,int flags);/*
fast-import.c +3 -3
@@ -1512,7 +1512,7 @@ static int tree_content_set( t = root->tree;for (i =0 ; i < t->entry_count; i++) { e = t->entries[i]; -if (e->name->str_len == n && !strncmp_icase (p, e->name->str_dat, n)) { +if (e->name->str_len == n && !fspathncmp (p, e->name->str_dat, n)) {if (!*slash1) {if (!S_ISDIR (mode) && e->versions[1].mode == mode
@@ -1602,7 +1602,7 @@ static int tree_content_remove( t = root->tree;for (i =0 ; i < t->entry_count; i++) { e = t->entries[i]; -if (e->name->str_len == n && !strncmp_icase (p, e->name->str_dat, n)) { +if (e->name->str_len == n && !fspathncmp (p, e->name->str_dat, n)) {if (*slash1 && !S_ISDIR (e->versions[1 ].mode))/* * If p names a file in some subdirectory, and a
@@ -1669,7 +1669,7 @@ static int tree_content_get( t = root->tree;for (i =0 ; i < t->entry_count; i++) { e = t->entries[i]; -if (e->name->str_len == n && !strncmp_icase (p, e->name->str_dat, n)) { +if (e->name->str_len == n && !fspathncmp (p, e->name->str_dat, n)) {if (!*slash1) goto found_entry;if (!S_ISDIR (e->versions[1 ].mode))
path.c +0 -10
@@ -466,16 +466,6 @@ const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)return pathname->buf; } -char *worktree_git_pathdup (const struct worktree *wt,const char *fmt, ...) -{ -struct strbuf path =STRBUF_INIT ; -va_list args; -va_start (args, fmt); -do_git_path (wt, &path, fmt, args); -va_end (args); -return strbuf_detach (&path,NULL ); -} -static void do_submodule_path (struct strbuf *buf,const char *path,const char *fmt,va_list args) {
sha1_file.c +1 -1
@@ -301,7 +301,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,return -1 ; } } -if (!strcmp_icase (ent->base, normalized_objdir)) { +if (!fspathcmp (ent->base, normalized_objdir)) {free (ent);return -1 ; }
worktree.c +28 -18
77 lines not on this page · raw message
worktree.h +5 -10
@@ -36,26 +36,21 @@ extern void free_worktrees(struct worktree **);/* * Check if a per-worktree symref points to a ref in the main worktree -* or any linked worktree, and return the path to the exising worktree -* if it is. Returns NULL if there is no existing ref. The result -* may be destroyed by the next call. +* or any linked worktree, and return the worktree that holds the ref, +* or NULL otherwise. The result may be destroyed by the next call. */ extern const struct worktree *find_shared_symref (const char *symref, -const char *target, -int ignore_current_worktree); +const char *target);int is_worktree_being_rebased (const struct worktree *wt,const char *target);int is_worktree_being_bisected (const struct worktree *wt,const char *target);/* -* Similar to git_path() and git_pathdup() but can produce paths for a -* specified worktree instead of current one +* Similar to git_path() but can produce paths for a specified +* worktree instead of current one */ extern const char *worktree_git_path (const struct worktree *wt,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); -extern char *worktree_git_pathdup (const struct worktree *wt, -const char *fmt, ...) - __attribute__((format (printf,2 ,3 )));#endif
wt-status.c +1 -1
@@ -1410,7 +1410,7 @@ void wt_status_get_state(struct wt_status_state *state,if (!stat (git_path_merge_head (), &st)) { state->merge_in_progress =1 ; }else if (wt_status_check_rebase (NULL , state)) { - ;/* all set */ + ;/* all set */ }else if (!stat (git_path_cherry_pick_head (), &st) && !get_sha1 ("CHERRY_PICK_HEAD" , sha1)) { state->cherry_pick_in_progress =1 ;
[01/13] dir.c: rename str(n)cmp_icase to fspath(n)cmp [02/13] path.c: add git_common_path() and strbuf_git_common_path() [03/13] worktree.c: store "id" instead of "git_dir"
[04/13] worktree.c: make find_shared_symref() return struct worktree * [05/13] worktree.c: mark current worktree
[06/13] path.c: refactor and add worktree_git_path()
[07/13] wt-status.c: split rebase detection out of wt_status_get_state() [08/13] wt-status.c: make wt_status_check_rebase() work on any worktree [09/13] worktree.c: avoid referencing to worktrees[i] multiple times [10/13] worktree.c: check whether branch is rebased in another worktree [11/13] wt-status.c: split bisect detection out of wt_status_get_state() [12/13] worktree.c: check whether branch is bisected in another worktree [13/13] branch: do not rename a branch under bisect or rebase
Total 17 files changed, 344 insertions(+), 82 deletions(-) --
Duy
PATCH v3 1/13 dir.c: rename str(n)cmp_icase to fspath(n)cmp
These functions compare two paths that are taken from file system. Depending on the running file system, paths may need to be compared case-sensitively or not, and maybe even something else in future. The current names do not convey that well.
--- dir.c | 13 ++++++------- dir.h | 4 ++-- fast-import.c | 6 +++--- sha1_file.c | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-)
dir.c +6 -7
@@ -53,13 +53,12 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, int check_only, const struct path_simplify *simplify);static int get_dtype (struct dirent *de,const char *path,int len); -/* helper string functions with support for the ignore_case flag */ -int strcmp_icase (const char *a,const char *b) +int fspathcmp (const char *a,const char *b) {return ignore_case ?strcasecmp (a, b) :strcmp (a, b); } -int strncmp_icase (const char *a,const char *b,size_t count) +int fspathncmp (const char *a,const char *b,size_t count) {return ignore_case ?strncasecmp (a, b, count) :strncmp (a, b, count); }
@@ -802,12 +801,12 @@ int match_basename(const char *basename, int basenamelen, {if (prefix == patternlen) {if (patternlen == basenamelen && - !strncmp_icase (pattern, basename, basenamelen)) + !fspathncmp (pattern, basename, basenamelen))return 1 ; }else if (flags &EXC_FLAG_ENDSWITH ) {/* "*literal" matching against "fooliteral" */ if (patternlen -1 <= basenamelen && - !strncmp_icase (pattern +1 , + !fspathncmp (pattern +1 , basename + basenamelen - (patternlen -1 ), patternlen -1 ))return 1 ;
@@ -844,7 +843,7 @@ int match_pathname(const char *pathname, int pathlen,*/ if (pathlen < baselen +1 || (baselen && pathname[baselen] !='/' ) || -strncmp_icase (pathname, base, baselen)) +fspathncmp (pathname, base, baselen))return 0 ; namelen = baselen ? pathlen - baselen -1 : pathlen;
@@ -858,7 +857,7 @@ int match_pathname(const char *pathname, int pathlen,if (prefix > namelen)return 0 ; -if (strncmp_icase (pattern, name, prefix)) +if (fspathncmp (pattern, name, prefix))return 0 ; pattern += prefix; patternlen -= prefix;
dir.h +2 -2
@@ -270,8 +270,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);/* tries to remove the path with empty directories along it, ignores ENOENT */ extern int remove_path (const char *path); -extern int strcmp_icase (const char *a,const char *b); -extern int strncmp_icase (const char *a,const char *b,size_t count); +extern int fspathcmp (const char *a,const char *b); +extern int fspathncmp (const char *a,const char *b,size_t count);extern int fnmatch_icase (const char *pattern,const char *string,int flags);/*
fast-import.c +3 -3
@@ -1512,7 +1512,7 @@ static int tree_content_set( t = root->tree;for (i =0 ; i < t->entry_count; i++) { e = t->entries[i]; -if (e->name->str_len == n && !strncmp_icase (p, e->name->str_dat, n)) { +if (e->name->str_len == n && !fspathncmp (p, e->name->str_dat, n)) {if (!*slash1) {if (!S_ISDIR (mode) && e->versions[1].mode == mode
@@ -1602,7 +1602,7 @@ static int tree_content_remove( t = root->tree;for (i =0 ; i < t->entry_count; i++) { e = t->entries[i]; -if (e->name->str_len == n && !strncmp_icase (p, e->name->str_dat, n)) { +if (e->name->str_len == n && !fspathncmp (p, e->name->str_dat, n)) {if (*slash1 && !S_ISDIR (e->versions[1 ].mode))/* * If p names a file in some subdirectory, and a
@@ -1669,7 +1669,7 @@ static int tree_content_get( t = root->tree;for (i =0 ; i < t->entry_count; i++) { e = t->entries[i]; -if (e->name->str_len == n && !strncmp_icase (p, e->name->str_dat, n)) { +if (e->name->str_len == n && !fspathncmp (p, e->name->str_dat, n)) {if (!*slash1) goto found_entry;if (!S_ISDIR (e->versions[1 ].mode))
sha1_file.c +1 -1
@@ -301,7 +301,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,return -1 ; } } -if (!strcmp_icase (ent->base, normalized_objdir)) { +if (!fspathcmp (ent->base, normalized_objdir)) {free (ent);return -1 ; }
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v3 2/13 path.c: add git_common_path() and strbuf_git_common_path()
In reply to Nguyễn Thái Ngọc DuyThese are mostly convenient functions to reduce code duplication. Most of the time, we should be able to get by with git_path() which handles $GIT_COMMON_DIR internally. However there are a few cases where we need to construct paths manually, for example some paths from a specific worktree. These functions will enable that.
--- cache.h | 3 +++ path.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+)
cache.h +3 -0
@@ -799,11 +799,14 @@ extern void check_repository_format(void);*/ extern const char *mkpath (const char *fmt, ...) __attribute__((format (printf,1 ,2 )));extern const char *git_path (const char *fmt, ...) __attribute__((format (printf,1 ,2 ))); +extern const char *git_common_path (const char *fmt, ...) __attribute__((format (printf,1 ,2 )));extern char *mksnpath (char *buf,size_t n,const char *fmt, ...) __attribute__((format (printf,3 ,4 )));extern void strbuf_git_path (struct strbuf *sb,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); +extern void strbuf_git_common_path (struct strbuf *sb,const char *fmt, ...) + __attribute__((format (printf,2 ,3 )));extern char *git_path_buf (struct strbuf *buf,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
path.c +29 -0
@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,va_end (args); } +static void do_git_common_path (struct strbuf *buf, +const char *fmt, +va_list args) +{ +strbuf_addstr (buf,get_git_common_dir ()); +if (buf->len && !is_dir_sep (buf->buf[buf->len -1 ])) +strbuf_addch (buf,'/' ); +strbuf_vaddf (buf, fmt, args); +strbuf_cleanup_path (buf); +} + +const char *git_common_path (const char *fmt, ...) +{ +struct strbuf *pathname =get_pathname (); +va_list args; +va_start (args, fmt); +do_git_common_path (pathname, fmt, args); +va_end (args); +return pathname->buf; +} + +void strbuf_git_common_path (struct strbuf *sb,const char *fmt, ...) +{ +va_list args; +va_start (args, fmt); +do_git_common_path (sb, fmt, args); +va_end (args); +} +int validate_headref (const char *path) {struct stat st;
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v3 3/13 worktree.c: store "id" instead of "git_dir"
In reply to Nguyễn Thái Ngọc DuyWe can reconstruct git_dir from id quite easily. It's a bit hackier to do the reverse.
--- branch.c | 3 ++- worktree.c | 31 ++++++++++++++++++------------- worktree.h | 8 +++++++- 3 files changed, 27 insertions(+), 15 deletions(-)
branch.c +2 -1
@@ -357,7 +357,8 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref)if (strcmp (oldref, worktrees[i]->head_ref))continue ; -if (set_worktree_head_symref (worktrees[i]->git_dir, newref)) { +if (set_worktree_head_symref (get_worktree_git_dir (worktrees[i]), + newref)) { ret =-1 ;error (_ ("HEAD of working tree %s is not updated" ), worktrees[i]->path);
worktree.c +18 -13
@@ -9,7 +9,7 @@ void free_worktrees(struct worktree **worktrees)for (i =0 ; worktrees[i]; i++) {free (worktrees[i]->path); -free (worktrees[i]->git_dir); +free (worktrees[i]->id);free (worktrees[i]->head_ref);free (worktrees[i]); }
@@ -74,13 +74,11 @@ static struct worktree *get_main_worktree(void)struct worktree *worktree =NULL ;struct strbuf path =STRBUF_INIT ;struct strbuf worktree_path =STRBUF_INIT ; -struct strbuf gitdir =STRBUF_INIT ;struct strbuf head_ref =STRBUF_INIT ;int is_bare =0 ;int is_detached =0 ; -strbuf_addf (&gitdir,"%s" ,absolute_path (get_git_common_dir ())); -strbuf_addbuf (&worktree_path, &gitdir); +strbuf_addstr (&worktree_path,absolute_path (get_git_common_dir ())); is_bare = !strbuf_strip_suffix (&worktree_path,"/.git" );if (is_bare)strbuf_strip_suffix (&worktree_path,"/." );
@@ -92,7 +90,7 @@ static struct worktree *get_main_worktree(void) worktree =xmalloc (sizeof (struct worktree )); worktree->path =strbuf_detach (&worktree_path,NULL ); - worktree->git_dir =strbuf_detach (&gitdir,NULL ); + worktree->id =NULL ; worktree->is_bare = is_bare; worktree->head_ref =NULL ; worktree->is_detached = is_detached;
@@ -100,7 +98,6 @@ static struct worktree *get_main_worktree(void) done:strbuf_release (&path); -strbuf_release (&gitdir);strbuf_release (&worktree_path);strbuf_release (&head_ref);return worktree;
@@ -111,16 +108,13 @@ static struct worktree *get_linked_worktree(const char *id)struct worktree *worktree =NULL ;struct strbuf path =STRBUF_INIT ;struct strbuf worktree_path =STRBUF_INIT ; -struct strbuf gitdir =STRBUF_INIT ;struct strbuf head_ref =STRBUF_INIT ;int is_detached =0 ;if (!id)die ("Missing linked worktree name" ); -strbuf_addf (&gitdir,"%s/worktrees/%s" , -absolute_path (get_git_common_dir ()), id); -strbuf_addf (&path,"%s/gitdir" , gitdir.buf); +strbuf_git_common_path (&path,"worktrees/%s/gitdir" , id);if (strbuf_read_file (&worktree_path, path.buf,0 ) <=0 )/* invalid gitdir file */ goto done;
@@ -140,7 +134,7 @@ static struct worktree *get_linked_worktree(const char *id) worktree =xmalloc (sizeof (struct worktree )); worktree->path =strbuf_detach (&worktree_path,NULL ); - worktree->git_dir =strbuf_detach (&gitdir,NULL ); + worktree->id =xstrdup (id); worktree->is_bare =0 ; worktree->head_ref =NULL ; worktree->is_detached = is_detached;
@@ -148,7 +142,6 @@ static struct worktree *get_linked_worktree(const char *id) done:strbuf_release (&path); -strbuf_release (&gitdir);strbuf_release (&worktree_path);strbuf_release (&head_ref);return worktree;
@@ -188,6 +181,16 @@ struct worktree **get_worktrees(void)return list; } +const char *get_worktree_git_dir (const struct worktree *wt) +{ +if (!wt) +return get_git_dir (); +else if (!wt->id) +return get_git_common_dir (); +else +return git_common_path ("worktrees/%s" , wt->id); +} +char *find_shared_symref (const char *symref,const char *target) {char *existing =NULL ;
@@ -199,7 +202,9 @@ char *find_shared_symref(const char *symref, const char *target)for (i =0 ; worktrees[i]; i++) {strbuf_reset (&path);strbuf_reset (&sb); -strbuf_addf (&path,"%s/%s" , worktrees[i]->git_dir, symref); +strbuf_addf (&path,"%s/%s" , +get_worktree_git_dir (worktrees[i]), + symref);if (parse_ref (path.buf, &sb,NULL )) {continue ;
worktree.h +7 -1
@@ -3,7 +3,7 @@struct worktree {char *path; -char *git_dir; +char *id;char *head_ref;unsigned char head_sha1[20 ];int is_detached;
@@ -23,6 +23,12 @@ struct worktree {extern struct worktree **get_worktrees (void );/* +* Return git dir of the worktree. Note that the path may be relative. +* If wt is NULL, git dir of current worktree is returned. +*/ +extern const char *get_worktree_git_dir (const struct worktree *wt); + +/* * Free up the memory for worktree(s) */ extern void free_worktrees (struct worktree **);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v3 4/13 worktree.c: make find_shared_symref() return struct worktree *
In reply to Nguyễn Thái Ngọc DuyThis gives the caller more information and they can answer things like, "is it the main worktree" or "is it the current worktree". The latter question is needed for the "checkout a rebase branch" case later.
--- branch.c | 13 +++++++------ builtin/branch.c | 8 ++++---- builtin/notes.c | 8 ++++---- worktree.c | 14 +++++++++----- worktree.h | 8 ++++---- 5 files changed, 28 insertions(+), 23 deletions(-)
branch.c +7 -6
@@ -336,13 +336,14 @@ void remove_branch_state(void)void die_if_checked_out (const char *branch) { -char *existing; +const struct worktree *wt; - existing =find_shared_symref ("HEAD" , branch); -if (existing) { -skip_prefix (branch,"refs/heads/" , &branch); -die (_ ("'%s' is already checked out at '%s'" ), branch, existing); - } + wt =find_shared_symref ("HEAD" , branch); +if (!wt) +return ; +skip_prefix (branch,"refs/heads/" , &branch); +die (_ ("'%s' is already checked out at '%s'" ), + branch, wt->path); }int replace_each_worktree_head_symref (const char *oldref,const char *newref)
builtin/branch.c +4 -4
@@ -220,12 +220,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, name =mkpathdup (fmt, bname.buf);if (kinds ==FILTER_REFS_BRANCHES ) { -char *worktree =find_shared_symref ("HEAD" , name); -if (worktree) { +const struct worktree *wt = +find_shared_symref ("HEAD" , name); +if (wt) {error (_ ("Cannot delete branch '%s' " "checked out at '%s'" ), - bname.buf, worktree); -free (worktree); + bname.buf, wt->path); ret =1 ;continue ; }
builtin/notes.c +4 -4
@@ -847,15 +847,15 @@ static int merge(int argc, const char **argv, const char *prefix)update_ref (msg.buf,default_notes_ref (), result_sha1,NULL ,0 ,UPDATE_REFS_DIE_ON_ERR ); else { /* Merge has unresolved conflicts */ -char *existing; +const struct worktree *wt;/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ update_ref (msg.buf,"NOTES_MERGE_PARTIAL" , result_sha1,NULL ,0 ,UPDATE_REFS_DIE_ON_ERR );/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ - existing =find_shared_symref ("NOTES_MERGE_REF" ,default_notes_ref ()); -if (existing) + wt =find_shared_symref ("NOTES_MERGE_REF" ,default_notes_ref ()); +if (wt)die (_ ("A notes merge into %s is already in-progress at %s" ), -default_notes_ref (), existing); +default_notes_ref (), wt->path);if (create_symref ("NOTES_MERGE_REF" ,default_notes_ref (),NULL ))die ("Failed to store link to current notes ref (%s)" ,default_notes_ref ());
worktree.c +9 -5
@@ -191,14 +191,19 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } -char *find_shared_symref (const char *symref,const char *target) +const struct worktree *find_shared_symref (const char *symref, +const char *target) { -char *existing =NULL ; +const struct worktree *existing =NULL ;struct strbuf path =STRBUF_INIT ;struct strbuf sb =STRBUF_INIT ; -struct worktree **worktrees =get_worktrees (); +static struct worktree **worktrees;int i =0 ; +if (worktrees) +free_worktrees (worktrees); + worktrees =get_worktrees (); +for (i =0 ; worktrees[i]; i++) {strbuf_reset (&path);strbuf_reset (&sb);
@@ -211,14 +216,13 @@ char *find_shared_symref(const char *symref, const char *target) }if (!strcmp (sb.buf, target)) { - existing =xstrdup (worktrees[i]->path); + existing = worktrees[i];break ; } }strbuf_release (&path);strbuf_release (&sb); -free_worktrees (worktrees);return existing; }
worktree.h +4 -4
@@ -35,10 +35,10 @@ extern void free_worktrees(struct worktree **);/* * Check if a per-worktree symref points to a ref in the main worktree -* or any linked worktree, and return the path to the exising worktree -* if it is. Returns NULL if there is no existing ref. The caller is -* responsible for freeing the returned path. +* or any linked worktree, and return the worktree that holds the ref, +* or NULL otherwise. The result may be destroyed by the next call. */ -extern char *find_shared_symref (const char *symref,const char *target); +extern const struct worktree *find_shared_symref (const char *symref, +const char *target);#endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v3 5/13 worktree.c: mark current worktree
In reply to Nguyễn Thái Ngọc Duy--- worktree.c | 24 ++++++++++++++++++++++++ worktree.h | 1 + 2 files changed, 25 insertions(+)
worktree.c +24 -0
@@ -2,6 +2,7 @@#include "refs.h" #include "strbuf.h" #include "worktree.h" +#include "dir.h" void free_worktrees (struct worktree **worktrees) {
@@ -94,6 +95,7 @@ static struct worktree *get_main_worktree(void) worktree->is_bare = is_bare; worktree->head_ref =NULL ; worktree->is_detached = is_detached; + worktree->is_current =0 ;add_head_info (&head_ref, worktree); done:
@@ -138,6 +140,7 @@ static struct worktree *get_linked_worktree(const char *id) worktree->is_bare =0 ; worktree->head_ref =NULL ; worktree->is_detached = is_detached; + worktree->is_current =0 ;add_head_info (&head_ref, worktree); done:
@@ -147,6 +150,25 @@ done:return worktree; } +static void mark_current_worktree (struct worktree **worktrees) +{ +struct strbuf git_dir =STRBUF_INIT ; +struct strbuf path =STRBUF_INIT ; +int i; + +strbuf_addstr (&git_dir,absolute_path (get_git_dir ())); +for (i =0 ; worktrees[i]; i++) { +struct worktree *wt = worktrees[i]; +strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); + wt->is_current = !fspathcmp (git_dir.buf, path.buf); +strbuf_reset (&path); +if (wt->is_current) +break ; + } +strbuf_release (&git_dir); +strbuf_release (&path); +} +struct worktree **get_worktrees (void ) {struct worktree **list =NULL ;
@@ -178,6 +200,8 @@ struct worktree **get_worktrees(void) }ALLOC_GROW (list, counter +1 , alloc); list[counter] =NULL ; + +mark_current_worktree (list);return list; }
worktree.h +1 -0
@@ -8,6 +8,7 @@ struct worktree {unsigned char head_sha1[20 ];int is_detached;int is_bare; +int is_current; };/* Functions for acting on the information about worktrees. */
Signature · 1 line
2.8.0.rc0.210.gd302cd2
Re: [PATCH v3 05/13] worktree.c: mark current worktree
worktree.c done: · line 156
Quoted 11 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.c +7
-0@@ -147,6 +150,25 @@ done: +static void mark_current_worktree (struct worktree **worktrees) +{ +struct strbuf git_dir =STRBUF_INIT ; +struct strbuf path =STRBUF_INIT ; +int i; + +strbuf_addstr (&git_dir,absolute_path (get_git_dir ()));
This could also just be:
char *git_dir = xstrdup(absolute_path(...));
with a corresponding 'free(git_dir)' below.
worktree.c done: · line 163
Quoted 4 lines
worktree.c +4
-0+for (i =0 ; worktrees[i]; i++) { +struct worktree *wt = worktrees[i]; +strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); + wt->is_current = !fspathcmp (git_dir.buf, path.buf);
Similarly, it looks like 'path' doesn't need to be a strbuf at all since the result of absolute_path() should remain valid long enough for fspathcmp(). It could just be:
const char *path = absolute_path(...);
wt->is_current = !fspathcmp(git_dir, path);But these are very minor; probably not worth a re-roll.
Quoted 7 lines
worktree.c +7
-0+strbuf_reset (&path); +if (wt->is_current) +break ; + } +strbuf_release (&git_dir); +strbuf_release (&path); +}
Re: [PATCH v3 05/13] worktree.c: mark current worktree
Quoted 24 lines · Eric Sunshine
worktree.c done: · line 156
Quoted 11 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.c +7
-0@@ -147,6 +150,25 @@ done: +static void mark_current_worktree (struct worktree **worktrees) +{ +struct strbuf git_dir =STRBUF_INIT ; +struct strbuf path =STRBUF_INIT ; +int i; + +strbuf_addstr (&git_dir,absolute_path (get_git_dir ()));This could also just be:
char *git_dir = xstrdup(absolute_path(...));with a corresponding '
free(git_dir)' below.worktree.c done: · line 163
Quoted 4 lines
worktree.c +4
-0+for (i =0 ; worktrees[i]; i++) { +struct worktree *wt = worktrees[i]; +strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); + wt->is_current = !fspathcmp (git_dir.buf, path.buf);
Quoted 8 lines
Similarly, it looks like 'path' doesn't need to be a strbuf at all since the result of
absolute_path()should remain valid long enough forfspathcmp(). It could just be:const char *path = absolute_path(...); wt->is_current = !fspathcmp(git_dir, path);But these are very minor; probably not worth a re-roll.
Yeah. I think the use of strbuf is influenced by the code in get_worktrees(). But since this code is now in a separate function, it makes little sense to go with the strbuf hammer. If there's no big change in this series, I'll just do this as a cleanup step in my next series, worktree-move.
Signature · 1 line
Duy
Re: [PATCH v3 05/13] worktree.c: mark current worktree
Quoted 7 lines · Duy Nguyen
Quoted 7 lines
Similarly, it looks like 'path' doesn't need to be a strbuf at all since the result of
absolute_path()should remain valid long enough forfspathcmp(). It could just be:const char *path = absolute_path(...); wt->is_current = !fspathcmp(git_dir, path);
Quoted 7 lines
Quoted 1 line
But these are very minor; probably not worth a re-roll.
Yeah. I think the use of strbuf is influenced by the code in
get_worktrees(). But since this code is now in a separate function, it makes little sense to go with the strbuf hammer. If there's no big change in this series, I'll just do this as a cleanup step in my next series, worktree-move.
On second thought, why hold patches back, lengthen the worktree-move series and make it a pain to review? I moved a few patches from worktree-move into this series and I took two other out to create nd/error-errno. So I'm going to take more patches out of it, creating two bite-sized series, to be sent shortly.
The first one is purely cleanup (ok, 1/7 is not exactly cleanup)
[1/7] completion: support git-worktree
[2/7] worktree.c: rewrite mark_current_worktree() to avoid [3/7] git-worktree.txt: keep subcommand listing in alphabetical [4/7] worktree.c: use is_dot_or_dotdot()
[5/7] worktree.c: add clear_worktree()
[6/7] worktree: avoid 0{40}, too many zeroes, hard to read [7/7] worktree: simplify prefixing paths
And the second one adds "git worktree lock" and "git worktree unlock". This series is built on top of the first one, it needs 1/7.
[1/5] worktree.c: add find_worktree_by_path()
[2/5] worktree.c: add is_main_worktree()
[3/5] worktree.c: add is_worktree_locked()
[4/5] worktree: add "lock" command
[5/5] worktree: add "unlock" command
After this, worktree-move becomes ~10 patch series.
Signature · 1 line
Duy
PATCH 1/7 completion: support git-worktree
--- contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
contrib/completion/git-completion.bash +23 -0
@@ -2595,6 +2595,29 @@ _git_whatchanged () _git_log } +_git_worktree () +{ + local subcommands="add list prune" + local subcommand="$(__git_find_on_cmdline "$subcommands")" + if [ -z "$subcommand" ]; then + __gitcomp "$subcommands" + else + case "$subcommand,$cur" in + add,--*) + __gitcomp "--detach --force" + ;; + list,--*) + __gitcomp "--porcelain" + ;; + prune,--*) + __gitcomp "--dry-run --expire --verbose" + ;; + *) + ;; + esac + fi +} + __git_main () { local i c=1 command __git_dir
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH 2/7 worktree.c: rewrite mark_current_worktree() to avoid strbuf
strbuf is a bit overkill for this function. What we need is call absolute_path() twice and make sure the second call does not destroy the result of the first. One buffer allocation is enough.
--- worktree.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-)
worktree.c +7 -9
@@ -153,21 +153,19 @@ done:static void mark_current_worktree (struct worktree **worktrees) { -struct strbuf git_dir =STRBUF_INIT ; -struct strbuf path =STRBUF_INIT ; +char *git_dir =xstrdup (absolute_path (get_git_dir ()));int i; -strbuf_addstr (&git_dir,absolute_path (get_git_dir ()));for (i =0 ; worktrees[i]; i++) {struct worktree *wt = worktrees[i]; -strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); - wt->is_current = !fspathcmp (git_dir.buf, path.buf); -strbuf_reset (&path); -if (wt->is_current) +const char *wt_git_dir =get_worktree_git_dir (wt); + +if (!fspathcmp (git_dir,absolute_path (wt_git_dir))) { + wt->is_current =1 ;break ; + } } -strbuf_release (&git_dir); -strbuf_release (&path); +free (git_dir); }struct worktree **get_worktrees (void )
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH 2/7] worktree.c: rewrite mark_current_worktree() to avoid strbuf
Quoted 1 line · Nguyễn Thái Ngọc Duy
strbuf is a bit overkill for this function. What we need is call
s/is call/is to call/
Quoted 2 lines
absolute_path()twice and make sure the second call does not destroy the result of the first. One buffer allocation is enough.
The patch itself looks good.
Quoted 29 lines
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.c +7
-9@@ -153,21 +153,19 @@ done:static void mark_current_worktree (struct worktree **worktrees) { -struct strbuf git_dir =STRBUF_INIT ; -struct strbuf path =STRBUF_INIT ; +char *git_dir =xstrdup (absolute_path (get_git_dir ()));int i; -strbuf_addstr (&git_dir,absolute_path (get_git_dir ()));for (i =0 ; worktrees[i]; i++) {struct worktree *wt = worktrees[i]; -strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); - wt->is_current = !fspathcmp (git_dir.buf, path.buf); -strbuf_reset (&path); -if (wt->is_current) +const char *wt_git_dir =get_worktree_git_dir (wt); + +if (!fspathcmp (git_dir,absolute_path (wt_git_dir))) { + wt->is_current =1 ;break ; + } } -strbuf_release (&git_dir); -strbuf_release (&path); +free (git_dir); }
PATCH 3/7 git-worktree.txt: keep subcommand listing in alphabetical order
In reply to Nguyễn Thái Ngọc DuyThis is probably not the best order. But it makes it no-brainer to know where to insert new commands. At some point we might want to reorder at least the synopsis part again, grouping commonly use subcommands together.
--- Documentation/git-worktree.txt | 10 +++++----- builtin/worktree.c | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-)
Documentation/git-worktree.txt +5 -5
@@ -10,8 +10,8 @@ SYNOPSIS -------- [verse] 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] -'git worktree prune' [-n] [-v] [--expire <expire>] 'git worktree list' [--porcelain] +'git worktree prune' [-n] [-v] [--expire <expire>] DESCRIPTION -----------
@@ -54,10 +54,6 @@ If `<branch>` is omitted and neither `-b` nor `-B` nor `--detached` used, then, as a convenience, a new branch based at HEAD is created automatically, as if `-b $(basename <path>)` was specified. -prune:: - -Prune working tree information in $GIT_DIR/worktrees. - list:: List details of each worktree. The main worktree is listed first, followed by
@@ -65,6 +61,10 @@ each of the linked worktrees. The output details include if the worktree is bare, the revision currently checked out, and the branch currently checked out (or 'detached HEAD' if none). +prune:: + +Prune working tree information in $GIT_DIR/worktrees. + OPTIONS -------
builtin/worktree.c +1 -1
@@ -13,8 +13,8 @@static const char *const worktree_usage[] = {N_ ("git worktree add [<options>] <path> [<branch>]" ), -N_ ("git worktree prune [<options>]" ),N_ ("git worktree list [<options>]" ), +N_ ("git worktree prune [<options>]" ),NULL };
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH 4/7 worktree.c: use is_dot_or_dotdot()
In reply to Nguyễn Thái Ngọc Duy--- builtin/worktree.c | 2 +- worktree.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-)
builtin/worktree.c +1 -1
@@ -95,7 +95,7 @@ static void prune_worktrees(void)if (!dir)return ;while ((d =readdir (dir)) !=NULL ) { -if (!strcmp (d->d_name,"." ) || !strcmp (d->d_name,".." )) +if (is_dot_or_dotdot (d->d_name))continue ;strbuf_reset (&reason);if (!prune_worktree (d->d_name, &reason))
worktree.c +1 -1
@@ -187,7 +187,7 @@ struct worktree **get_worktrees(void)if (dir) {while ((d =readdir (dir)) !=NULL ) {struct worktree *linked =NULL ; -if (!strcmp (d->d_name,"." ) || !strcmp (d->d_name,".." )) +if (is_dot_or_dotdot (d->d_name))continue ;if ((linked =get_linked_worktree (d->d_name))) {
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH 5/7 worktree.c: add clear_worktree()
In reply to Nguyễn Thái Ngọc DuyThe use case is keep some worktree and discard the rest of the worktree list.
--- worktree.c | 14 +++++++++++--- worktree.h | 5 +++++ 2 files changed, 16 insertions(+), 3 deletions(-)
worktree.c +11 -3
@@ -5,14 +5,22 @@#include "dir.h" #include "wt-status.h" +void clear_worktree (struct worktree *wt) +{ +if (!wt) +return ; +free (wt->path); +free (wt->id); +free (wt->head_ref); +memset (wt,0 ,sizeof (*wt)); +} +void free_worktrees (struct worktree **worktrees) {int i =0 ;for (i =0 ; worktrees[i]; i++) { -free (worktrees[i]->path); -free (worktrees[i]->id); -free (worktrees[i]->head_ref); +clear_worktree (worktrees[i]);free (worktrees[i]); }free (worktrees);
worktree.h +5 -0
@@ -29,6 +29,11 @@ extern struct worktree **get_worktrees(void);*/ extern const char *get_worktree_git_dir (const struct worktree *wt); +/* +* Free up the memory for worktree +*/ +extern void clear_worktree (struct worktree *); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH 5/7] worktree.c: add clear_worktree()
Quoted 2 lines · Nguyễn Thái Ngọc Duy
The use case is keep some worktree and discard the rest of the worktree list.
So, you're saying that rather than a client freeing the entire worktree list like this:
free_worktrees(worktrees);
it might instead want to keep some element ('n') and free all others plus the list itself, like this:
struct worktree *keep = worktrees[n];
struct worktree **p = worktrees;
for (; *p; p++)
if (*p != keep)
clear_worktree(*p);
free(worktrees);Is that correct?
If so, then doesn't this require the client to have far too intimate knowledge of the internals of free_worktrees(). Or, was your idea that the client would simply leak the 'worktrees' array after freeing the unwanted elements?
In either case, a cleaner approach might be to provide a function for copying a worktree element. Perhaps:
struct worktree *copy_worktree(const struct worktree *);
or something?
Quoted 50 lines
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.c +11
-3@@ -5,14 +5,22 @@#include "dir.h" #include "wt-status.h" +void clear_worktree (struct worktree *wt) +{ +if (!wt) +return ; +free (wt->path); +free (wt->id); +free (wt->head_ref); +memset (wt,0 ,sizeof (*wt)); +} +void free_worktrees (struct worktree **worktrees) {int i =0 ;for (i =0 ; worktrees[i]; i++) { -free (worktrees[i]->path); -free (worktrees[i]->id); -free (worktrees[i]->head_ref); +clear_worktree (worktrees[i]);free (worktrees[i]); }free (worktrees);worktree.h +5
-1@@ -29,6 +29,11 @@ extern struct worktree **get_worktrees(void);*/ extern const char *get_worktree_git_dir (const struct worktree *wt); +/* +* Free up the memory for worktree +*/ +extern void clear_worktree (struct worktree *); +/* * Free up the memory for worktree(s) */ --2.8.2.524.g6ff3d78
Re: [PATCH 5/7] worktree.c: add clear_worktree()
Quoted 27 lines · Eric Sunshine
Quoted 2 lines · Nguyễn Thái Ngọc Duy
The use case is keep some worktree and discard the rest of the worktree list.
So, you're saying that rather than a client freeing the entire worktree list like this:
free_worktrees(worktrees);it might instead want to keep some element ('n') and free all others plus the list itself, like this:
struct worktree *keep = worktrees[n]; struct worktree **p = worktrees; for (; *p; p++) if (*p != keep) clear_worktree(*p); free(worktrees);Is that correct?
If so, then doesn't this require the client to have far too intimate knowledge of the internals of
free_worktrees(). Or, was your idea that the client would simply leak the 'worktrees' array after freeing the unwanted elements?
Quoted 4 lines
In either case, a cleaner approach might be to provide a function for copying a worktree element. Perhaps:
struct worktree *copy_worktree(const struct worktree *);
Of course, you'd still need a clear_worktree() or free_worktree() function to dispose of the copy.
Re: [PATCH 5/7] worktree.c: add clear_worktree()
In reply to Eric SunshineQuoted 20 lines · Eric Sunshine
Quoted 2 lines · Nguyễn Thái Ngọc Duy
The use case is keep some worktree and discard the rest of the worktree list.
So, you're saying that rather than a client freeing the entire worktree list like this:
free_worktrees(worktrees);it might instead want to keep some element ('n') and free all others plus the list itself, like this:
struct worktree *keep = worktrees[n]; struct worktree **p = worktrees; for (; *p; p++) if (*p != keep) clear_worktree(*p); free(worktrees);
Quoted 6 lines
Is that correct?
If so, then doesn't this require the client to have far too intimate knowledge of the internals of
free_worktrees(). Or, was your idea that the client would simply leak the 'worktrees' array after freeing the unwanted elements?
I looked back in my trees to see why I needed it in the first place. It turns out my explanation was a lie. I needed it for refactoring builtin/worktree.c:add_worktree(), where I initialized struct worktree separately, not from get_worktrees() [1]. Because it's not clear of the exact use case for this commit, and because that commit I mentioned may take even longer time to be here (it adds "git worktree move --repository", a lot more complex operation than a "git worktree move"), I'll take this commit out of the series for now.
[1] https://github.com/pclouds/git/commit/528d81ce5609b5fbbe1ba193389f7aeecc83d992
Quoted 6 lines
In either case, a cleaner approach might be to provide a function for copying a worktree element. Perhaps:
struct worktree *copy_worktree(const struct worktree *);or something?
Signature · 1 line
Duy
PATCH 6/7 worktree: avoid 0{40}, too many zeroes, hard to read
In reply to Nguyễn Thái Ngọc Duy--- builtin/worktree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
builtin/worktree.c +1 -1
@@ -262,7 +262,7 @@ static int add_worktree(const char *path, const char *refname,*/ strbuf_reset (&sb);strbuf_addf (&sb,"%s/HEAD" , sb_repo.buf); -write_file (sb.buf,"0000000000000000000000000000000000000000" ); +write_file (sb.buf,sha1_to_hex (null_sha1));strbuf_reset (&sb);strbuf_addf (&sb,"%s/commondir" , sb_repo.buf);write_file (sb.buf,"../.." );
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH 7/7 worktree: simplify prefixing paths
In reply to Nguyễn Thái Ngọc Duy--- builtin/worktree.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
builtin/worktree.c +3 -1
@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)if (ac <1 || ac >2 )usage_with_options (worktree_usage, options); - path = prefix ?prefix_filename (prefix,strlen (prefix), av[0 ]) : av[0 ]; + path =prefix_filename (prefix,strlen (prefix), av[0 ]); branch = ac <2 ?"HEAD" : av[1 ]; opts.force_new_branch = !!new_branch_force;
@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)if (ac <2 )usage_with_options (worktree_usage, options); +if (!prefix) + prefix ="" ;if (!strcmp (av[1 ],"add" ))return add (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"prune" ))
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH 7/7] worktree: simplify prefixing paths
Quoted 2 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
This changes semantics, doesn't it? prefix_filename() seems to do a lot more than just strbuf_vadd("%s%s", prefix, filename); would do.
It may be a good change (e.g. turn '\' into '/' on Windows), but this is way more than "simplify prefixing". It is something else whose effect needs to be explained.
Quoted 25 lines
builtin/worktree.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)builtin/worktree.c +3
-1@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)if (ac <1 || ac >2 )usage_with_options (worktree_usage, options); - path = prefix ?prefix_filename (prefix,strlen (prefix), av[0 ]) : av[0 ]; + path =prefix_filename (prefix,strlen (prefix), av[0 ]); branch = ac <2 ?"HEAD" : av[1 ]; opts.force_new_branch = !!new_branch_force;@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)if (ac <2 )usage_with_options (worktree_usage, options); +if (!prefix) + prefix ="" ;if (!strcmp (av[1 ],"add" ))return add (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"prune" ))
Re: [PATCH 7/7] worktree: simplify prefixing paths
Quoted 11 lines · Junio C Hamano
Quoted 2 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
This changes semantics, doesn't it?
prefix_filename()seems to do a lot more than juststrbuf_vadd("%s%s", prefix, filename); would do.It may be a good change (e.g. turn '\' into '/' on Windows), but this is way more than "simplify prefixing". It is something else whose effect needs to be explained.
Quoted 11 lines · Junio C Hamano
Quoted 2 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
This changes semantics, doesn't it?
prefix_filename()seems to do a lot more than juststrbuf_vadd("%s%s", prefix, filename); would do.It may be a good change (e.g. turn '\' into '/' on Windows), but this is way more than "simplify prefixing". It is something else whose effect needs to be explained.
I admit I forgot about Windows part in prefix_filename(). For non-Windows code, it's exactly the same behavior. Maybe we should do this to emphasize that prefix_filename() is no-op when pfx_len is zero? The same pattern is used elsewhere too (or I'm spreading it in my local tree..) Unless of course if convert_slashes() is a good thing to always do, then I need to update my commit message (+Johannes for this question).
abspath.c +4 -1
@@ -160,8 +160,11 @@ const char *absolute_path(const char *path)const char *prefix_filename (const char *pfx,int pfx_len,const char *arg) {static struct strbuf path =STRBUF_INIT ; + +if (!pfx_len) +return arg;#ifndef GIT_WINDOWS_NATIVE -if (!pfx_len ||is_absolute_path (arg)) +if (is_absolute_path (arg))return arg;strbuf_reset (&path);strbuf_add (&path, pfx, pfx_len);
Quoted 26 lines
Quoted 26 lines
builtin/worktree.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)builtin/worktree.c +3
-1@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)if (ac <1 || ac >2 )usage_with_options (worktree_usage, options); - path = prefix ?prefix_filename (prefix,strlen (prefix), av[0 ]) : av[0 ]; + path =prefix_filename (prefix,strlen (prefix), av[0 ]); branch = ac <2 ?"HEAD" : av[1 ]; opts.force_new_branch = !!new_branch_force;@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)if (ac <2 )usage_with_options (worktree_usage, options); +if (!prefix) + prefix ="" ;if (!strcmp (av[1 ],"add" ))return add (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"prune" ))
Signature · 1 line
Duy
Re: [PATCH 1/7] completion: support git-worktree
In reply to Nguyễn Thái Ngọc DuyQuoted 1 line · Nguyễn Thái Ngọc Duy
completion: support git-worktree
See [1] and, especially, [2] for previous attempts...
[1]: http://thread.gmane.org/gmane.comp.version-control.git/274526
[2]: http://thread.gmane.org/gmane.comp.version-control.git/274526/focus=276333
Quoted 38 lines
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
contrib/completion/git-completion.bash +23
-1@@ -2595,6 +2595,29 @@ _git_whatchanged () _git_log } +_git_worktree () +{ + local subcommands="add list prune" + local subcommand="$(__git_find_on_cmdline "$subcommands")" + if [ -z "$subcommand" ]; then + __gitcomp "$subcommands" + else + case "$subcommand,$cur" in + add,--*) + __gitcomp "--detach --force" + ;; + list,--*) + __gitcomp "--porcelain" + ;; + prune,--*) + __gitcomp "--dry-run --expire --verbose" + ;; + *) + ;; + esac + fi +} + __git_main () { local i c=1 command __git_dir --2.8.2.524.g6ff3d78
PATCH v2 1/5 worktree.c: add find_worktree_by_path()
In reply to Duy NguyenSo far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.
--- worktree.c | 16 ++++++++++++++++ worktree.h | 6 ++++++ 2 files changed, 22 insertions(+)
worktree.c +16 -0
@@ -222,6 +222,22 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +struct worktree *find_worktree_by_path (struct worktree **list, +const char *path_) +{ +char *path =xstrdup (real_path (path_)); +struct worktree *wt =NULL ; + +while (*list) { + wt = *list++; +if (!fspathcmp (path,real_path (wt->path))) +break ; + wt =NULL ; + } +free (path); +return wt; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +6 -0
@@ -29,6 +29,12 @@ extern struct worktree **get_worktrees(void);*/ extern const char *get_worktree_git_dir (const struct worktree *wt); +/* +* Search a worktree by its path. Paths are normalized internally. +*/ +extern struct worktree *find_worktree_by_path (struct worktree **list, +const char *path_); +/* * Free up the memory for worktree */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v2 2/5 worktree.c: add is_main_worktree()
Main worktree _is_ different. You can lock a linked worktree but not the main one, for example. Provide an API for checking that.
--- worktree.c | 5 +++++ worktree.h | 5 +++++ 2 files changed, 10 insertions(+)
worktree.c +5 -0
@@ -238,6 +238,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,return wt; } +int is_main_worktree (const struct worktree *wt) +{ +return wt && !wt->id; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +5 -0
@@ -35,6 +35,11 @@ extern const char *get_worktree_git_dir(const struct worktree *wt);extern struct worktree *find_worktree_by_path (struct worktree **list,const char *path_); +/* +* Return true if the given worktree is the main one. +*/ +extern int is_main_worktree (const struct worktree *wt); +/* * Free up the memory for worktree */
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH 2/5] worktree.c: add is_main_worktree()
worktree.c struct worktree *find_worktree_by_path(struct worktree **list, · line 241
Quoted 11 lines · Nguyễn Thái Ngọc Duy
Main worktree _is_ different. You can lock a linked worktree but not the main one, for example. Provide an API for checking that.
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.c +4
-0@@ -238,6 +238,11 @@ struct worktree *find_worktree_by_path(struct worktree **list, +int is_main_worktree (const struct worktree *wt) +{ +return wt && !wt->id; +}
Is or will there ever be a case when it is valid for 'wt' to be NULL? If not, perhaps assert() or die() if given NULL?
PATCH v2 3/5 worktree.c: add is_worktree_locked()
In reply to Nguyễn Thái Ngọc DuyThis provides an API for checking if a worktree is locked. We need to check this to avoid double locking a worktree, or try to unlock one when it's not even locked.
--- worktree.c | 18 ++++++++++++++++++ worktree.h | 6 ++++++ 2 files changed, 24 insertions(+)
worktree.c +18 -0
@@ -243,6 +243,24 @@ int is_main_worktree(const struct worktree *wt)return wt && !wt->id; } +const char *is_worktree_locked (const struct worktree *wt) +{ +static struct strbuf sb =STRBUF_INIT ; + +if (!file_exists (git_common_path ("worktrees/%s/locked" , wt->id))) +return NULL ; + +strbuf_reset (&sb); +if (strbuf_read_file (&sb, +git_common_path ("worktrees/%s/locked" , wt->id), +0 ) <0 ) +die_errno (_ ("failed to read '%s'" ), +git_common_path ("worktrees/%s/locked" , wt->id)); + +strbuf_rtrim (&sb); +return sb.buf; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +6 -0
@@ -40,6 +40,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,*/ extern int is_main_worktree (const struct worktree *wt); +/* +* Return the reason string if the given worktree is locked. Return +* NULL otherwise. +*/ +extern const char *is_worktree_locked (const struct worktree *wt); +/* * Free up the memory for worktree */
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH 3/5] worktree.c: add is_worktree_locked()
Quoted 8 lines · Nguyễn Thái Ngọc Duy
This provides an API for checking if a worktree is locked. We need to check this to avoid double locking a worktree, or try to unlock one when it's not even locked.
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.c int is_main_worktree(const struct worktree *wt) · line 248
Quoted 6 lines
worktree.c +6
-0@@ -243,6 +243,24 @@ int is_main_worktree(const struct worktree *wt) +const char *is_worktree_locked (const struct worktree *wt) +{ +static struct strbuf sb =STRBUF_INIT ; + +if (!file_exists (git_common_path ("worktrees/%s/locked" , wt->id))) +return NULL ;
The git_common_path(...) invocation is textually lengthy and repeated three times in this function. If you instead assign the result to a variable (possibly xstrdup'ing it if needed), then the below strbuf_read_file() would likely fit on one line, thus be easier to read.
worktree.c int is_main_worktree(const struct worktree *wt) · line 256
Quoted 5 lines
worktree.c +5
-0+ +strbuf_reset (&sb); +if (strbuf_read_file (&sb, +git_common_path ("worktrees/%s/locked" , wt->id), +0 ) <0 )
It's too bad that strbuf_read_file() doesn't guarantee anything about 'errno', such as indicating that the file did not exist, in which case the !file_exists() check would not be needed, and a bit of raciness eliminated, but that's outside the scope of this series.
worktree.c int is_main_worktree(const struct worktree *wt) · line 260
Quoted 4 lines
worktree.c +4
-0+die_errno (_ ("failed to read '%s'" ), +git_common_path ("worktrees/%s/locked" , wt->id)); + +strbuf_rtrim (&sb);
Since this file is presumably human-editable (historically and at this point in the series) in order to specify the lock reason, should this be doing a full trim() rather than only rtrim()?
worktree.h extern struct worktree *find_worktree_by_path(struct worktree **list, · line 43
Quoted 8 lines
worktree.c +2
-0+return sb.buf; +}worktree.h +4
-0@@ -40,6 +40,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list, +/* +* Return the reason string if the given worktree is locked. Return +* NULL otherwise. +*/
Does this need to mention that the returned "locked reason" string is only guaranteed valid until the next invocation?
Actually, I recall that when I suggested the idea of 'struct worktree' and get_worktrees() to Mike that it would be natural for the structure to have a 'locked' (or 'locked_reason') field, in which case the reason could be stored there instead of in this static strbuf. In fact, my idea at the time was that get_worktrees() would populate that field automatically, rather than having to do so via a separate on-demand function call as in this patch.
worktree.h extern struct worktree *find_worktree_by_path(struct worktree **list, · line 47
Quoted 1 line
worktree.h +1
-0+extern const char *is_worktree_locked (const struct worktree *wt);
I was wondering if builtin/worktree.c:prune_worktree() should be updated to invoke this new function instead of consulting "worktrees/<id>/locked" manually, but I see that the entire "prune worktrees" functionality in builting/worktree.c first needs to be updated to the get_worktrees() API for that to happen.
Re: [PATCH 3/5] worktree.c: add is_worktree_locked()
Quoted 7 lines · Eric Sunshine
Actually, I recall that when I suggested the idea of 'struct worktree' and
get_worktrees()to Mike that it would be natural for the structure to have a 'locked' (or 'locked_reason') field, in which case the reason could be stored there instead of in this static strbuf. In fact, my idea at the time was thatget_worktrees()would populate that field automatically, rather than having to do so via a separate on-demand function call as in this patch.
I'm keeping this as a separate function for now. I don't like get_worktrees() doing extra work unless it has to. We soon will see the complete picture of "git worktree" then we can merge it back to get_worktrees() if it turns out checking "locked" is the common operation. is_worktree_locked() then may become a thin wrapper to access this "locked" field.
Quoted 7 lines
Quoted 1 line
+extern const char *is_worktree_locked(const struct worktree *wt);
I was wondering if builtin/worktree.c:prune_worktree() should be updated to invoke this new function instead of consulting "worktrees/<id>/locked" manually, but I see that the entire "prune worktrees" functionality in
builting/worktree.cfirst needs to be updated to theget_worktrees()API for that to happen.
I thought about updating prune too. But it is in a bit special situation where it may need to consider invalid (or partly invalid) worktrees as well. So far worktree api is about valid worktrees only if I'm not mistaken and we probably should keep it that way, otherwise all callers may need to check "is this worktree valid" all over the place.
Signature · 1 line
Duy
Re: [PATCH 3/5] worktree.c: add is_worktree_locked()
Quoted 9 lines · Duy Nguyen
Quoted 7 lines · Eric Sunshine
Actually, I recall that when I suggested the idea of 'struct worktree' and
get_worktrees()to Mike that it would be natural for the structure to have a 'locked' (or 'locked_reason') field, in which case the reason could be stored there instead of in this static strbuf. In fact, my idea at the time was thatget_worktrees()would populate that field automatically, rather than having to do so via a separate on-demand function call as in this patch.
Quoted 6 lines
I'm keeping this as a separate function for now. I don't like
get_worktrees()doing extra work unless it has to. We soon will see the complete picture of "git worktree" then we can merge it back toget_worktrees()if it turns out checking "locked" is the common operation.is_worktree_locked()then may become a thin wrapper to access this "locked" field.
is_worktree_locked() could also just go away since the 'struct worktree::locked' field would be non-NULL for locked, else NULL.
Quoted 8 lines
Quoted 7 lines
Quoted 1 line
+extern const char *is_worktree_locked(const struct worktree *wt);
I was wondering if builtin/worktree.c:prune_worktree() should be updated to invoke this new function instead of consulting "worktrees/<id>/locked" manually, but I see that the entire "prune worktrees" functionality in
builting/worktree.cfirst needs to be updated to theget_worktrees()API for that to happen.
Quoted 6 lines
I thought about updating prune too. But it is in a bit special situation where it may need to consider invalid (or partly invalid) worktrees as well. So far worktree api is about valid worktrees only if I'm not mistaken and we probably should keep it that way, otherwise all callers may need to check "is this worktree valid" all over the place.
Yes and no. In addition to suggesting to Mike that 'struct worktree' should have a 'locked' field, I also suggested a 'prunable' field which would indicate whether a worktree was a candidate for pruning. In fact, the field would be a 'const char *' where non-NULL would mean prunable and give a reason (one of the reasons already determined by builtin/worktree.c:prune_worktree()). Alternately it could be an enum. Like the 'locked' (or 'lock_reason') field, 'prunable' (or 'prune_reason') is likely the sort of information which should be presented by "git worktree list". And, as with 'locked', I think it makes sense for the 'prunable' field to be populated automatically by get_worktrees().
As for your concern about clients having to take care with valid vs. invalid worktrees, get_worktrees() could be updated to return all worktrees or only valid worktrees (for some definition of "valid"). "git worktree list" is an example of a client which would want to see all worktrees assuming its updated to show 'locked' and 'prunable' status.
PATCH v2 4/5 worktree: add "lock" command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 12 ++++++++-- builtin/worktree.c | 41 ++++++++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 ++++- t/t2028-worktree-move.sh (new +x) | 34 ++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100755 t/t2028-worktree-move.sh
Documentation/git-worktree.txt +10 -2
@@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] 'git worktree list' [--porcelain] +'git worktree lock' [--reason <string>] <path> 'git worktree prune' [-n] [-v] [--expire <expire>] DESCRIPTION
@@ -61,6 +62,12 @@ each of the linked worktrees. The output details include if the worktree is bare, the revision currently checked out, and the branch currently checked out (or 'detached HEAD' if none). +lock:: + +When a worktree is locked, it cannot be pruned, moved or deleted. For +example, if the worktree is on portable device that is not available +when "git worktree <command>" is executed. + prune:: Prune working tree information in $GIT_DIR/worktrees.
@@ -110,6 +117,9 @@ OPTIONS --expire <time>:: With `prune`, only expire unused working trees older than <time>. +--reason <string>: + An explanation why the worktree is locked. + DETAILS ------- Each linked working tree has a private sub-directory in the repository's
@@ -226,8 +236,6 @@ performed manually, such as: - `remove` to remove a linked working tree and its administrative files (and warn if the working tree is dirty) - `mv` to move or rename a working tree and update its administrative files -- `lock` to prevent automatic pruning of administrative files (for instance, - for a working tree on a portable device) GIT ---
builtin/worktree.c +41 -0
58 lines not on this page · raw message
contrib/completion/git-completion.bash +4 -1
@@ -2597,7 +2597,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list prune" + local subcommands="add list lock prune" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
@@ -2609,6 +2609,9 @@ _git_worktree () list,--*) __gitcomp "--porcelain" ;; + lock,--*) + __gitcomp "--reason" + ;; prune,--*) __gitcomp "--dry-run --expire --verbose" ;;
t/t2028-worktree-move.sh new file +34 -0
@@ -0,0 +1,34 @@ +#!/bin/sh + +test_description='test git worktree move, remove, lock and unlock' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + git worktree add source && + git worktree list --porcelain | grep "^worktree" >actual && + cat <<-EOF >expected && + worktree $TRASH_DIRECTORY + worktree $TRASH_DIRECTORY/source + EOF + test_cmp expected actual +' + +test_expect_success 'lock main worktree' ' + test_must_fail git worktree lock . +' + +test_expect_success 'lock linked worktree' ' + git worktree lock --reason hahaha source && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_expect_success 'lock worktree twice' ' + test_must_fail git worktree lock source && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_done
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH 4/5] worktree: add "lock" command
Quoted 11 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +11
-0+static int lock_worktree (int ac,const char **av,const char *prefix) +{ +const char *reason ="" , *old_reason; +struct option options[] = { +OPT_STRING (0 ,"reason" , &reason,N_ ("string" ), +N_ ("reason for locking" )), +OPT_END () + }; +struct worktree **worktrees, *wt; +struct strbuf dst =STRBUF_INIT ; +
builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 483
Quoted 10 lines
builtin/worktree.c +10
-0+ ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + +strbuf_addstr (&dst,prefix_filename (prefix, +strlen (prefix), + av[0 ])); + + worktrees =get_worktrees (); + wt =find_worktree_by_path (worktrees, dst.buf);
Oh, I forgot to mention in my previous email that this is leaking 'strbuf dst'. It could be released right here at its point of final use.
Quoted 16 lines
builtin/worktree.c +16
-0+if (!wt) +die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); + + old_reason =is_worktree_locked (wt); +if (old_reason) { +if (*old_reason) +die (_ ("already locked, reason: %s" ), old_reason); +die (_ ("already locked, no reason" )); + } + +write_file (git_common_path ("worktrees/%s/locked" , wt->id), +"%s" , reason); +return 0 ; +}
Re: [PATCH 4/5] worktree: add "lock" command
In reply to Nguyễn Thái Ngọc DuyDocumentation/git-worktree.txt SYNOPSIS · line 17
Quoted 11 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
Documentation/git-worktree.txt +1
-0@@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] 'git worktree list' [--porcelain] +'git worktree lock' [--reason <string>] <path> 'git worktree prune' [-n] [-v] [--expire <expire>] DESCRIPTION
This forgets to update the paragraph in DESCRIPTION which currently talks about manually creating the 'locked' file. Perhaps it can be rewritten like this:
If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from being pruned by issuing the git worktree lock command, optionally specifying --reason to explain why the working tree is locked.
The DETAILS section might also need a small update. It currently says:
...add a file named 'locked'...
but perhaps it should instead say:
...use the git worktree lock command, which adds a file named locked,...
Documentation/git-worktree.txt each of the linked worktrees. The output details include if the worktree is · line 66
Quoted 6 lines
Documentation/git-worktree.txt +5
-0@@ -61,6 +62,12 @@ each of the linked worktrees. The output details include if the worktree is +lock:: + +When a worktree is locked, it cannot be pruned, moved or deleted. For +example, if the worktree is on portable device that is not available +when "git worktree <command>" is executed.
The really important reason for locking a worktree is to prevent its administrative files from being pruned *automatically*. This description doesn't properly emphasize that point.
Also, bc48328 (Documentation/git-worktree: consistently use term "linked working tree", 2015-07-20) comprehensively replaced the term "worktree" with "working tree" and, although some new instances slipped in with bb9c03b (worktree: add 'list' command, 2015-10-08), we should probably avoid adding more.
Perhaps the description of "lock" can be rewritten like this:
If a working tree is on a portable device or network share which is not always mounted, lock it to prevent its administrative files from being pruned automatically. This also prevents it from being moved or deleted. Optionally, specify a reason for the lock with --reason.
I guess preventing it from being deleted when locked is a safety measure?
Documentation/git-worktree.txt OPTIONS · line 121
Quoted 6 lines
Documentation/git-worktree.txt +2
-0@@ -110,6 +117,9 @@ OPTIONS --expire <time>:: With `prune`, only expire unused working trees older than <time>. +--reason <string>: + An explanation why the worktree is locked.
For consistency with other option descriptions, perhaps:
With lock, an explanation ...
though I don't feel strongly about it.
Quoted 24 lines
builtin/worktree.c +22
-0@@ -459,6 +460,44 @@ static int list(int ac, const char **av, const char *prefix) +static int lock_worktree (int ac,const char **av,const char *prefix) +{ +const char *reason ="" , *old_reason; +struct option options[] = { +OPT_STRING (0 ,"reason" , &reason,N_ ("string" ), +N_ ("reason for locking" )), +OPT_END () + }; +struct worktree **worktrees, *wt; +struct strbuf dst =STRBUF_INIT ; + + ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + +strbuf_addstr (&dst,prefix_filename (prefix, +strlen (prefix), + av[0 ])); + + worktrees =get_worktrees (); + wt =find_worktree_by_path (worktrees, dst.buf); +if (!wt)
builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 491
Quoted 10 lines
builtin/worktree.c +10
-0+die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); + + old_reason =is_worktree_locked (wt); +if (old_reason) { +if (*old_reason) +die (_ ("already locked, reason: %s" ), old_reason); +die (_ ("already locked, no reason" )); + }
As a convenience, would it make sense to allow a worktree to be re-locked with a different reason rather than erroring out?
Also, the "no reason" in the error message might be confusing. Perhaps it would be better to say merely "already locked" in this case.
builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 499
Quoted 4 lines
builtin/worktree.c +4
-0+write_file (git_common_path ("worktrees/%s/locked" , wt->id), +"%s" , reason); +return 0 ; +}
This is a tangent, but it would be nice for the "git worktree list" command to show whether a worktree is locked, and the reason (if available), both in pretty and porcelain formats. (That was another reason why I suggested to Mike, back when he was adding the "list" command, that 'struct worktree' should have a 'locked' field and get_worktrees() should populate it automatically.)
Quoted 18 lines
t/t2028-worktree-move.sh +16
-0@@ -0,0 +1,34 @@ +#!/bin/sh + +test_description='test git worktree move, remove, lock and unlock' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + git worktree add source && + git worktree list --porcelain | grep "^worktree" >actual && + cat <<-EOF >expected && + worktree $TRASH_DIRECTORY + worktree $TRASH_DIRECTORY/source + EOF + test_cmp expected actual +'
t/t2028-worktree-move.sh line 26
Quoted 10 lines
t/t2028-worktree-move.sh +10
-0+ +test_expect_success 'lock main worktree' ' + test_must_fail git worktree lock . +' + +test_expect_success 'lock linked worktree' ' + git worktree lock --reason hahaha source && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +'
Would it make sense to also add tests of locking from within the worktree being locked, and from within a worktree other than the one being locked (and other than the main one)?
Quoted 7 lines
t/t2028-worktree-move.sh +7
-0+test_expect_success 'lock worktree twice' ' + test_must_fail git worktree lock source && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_done
Re: [PATCH 4/5] worktree: add "lock" command
(the answer to rest of your questions is "yes you're right, will fix" or something along that line so I will not quote them here)
Quoted 9 lines · Eric Sunshine
builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 494
Quoted 6 lines
builtin/worktree.c +6
-0+ old_reason =is_worktree_locked (wt); +if (old_reason) { +if (*old_reason) +die (_ ("already locked, reason: %s" ), old_reason); +die (_ ("already locked, no reason" )); + }As a convenience, would it make sense to allow a worktree to be re-locked with a different reason rather than erroring out?
For now I think the user can just unlock then relock, after examining the previous reason and maybe incorporating it to the new reason. Yes there is a race but for these operations I don't think it really matters. If it's done often enough, we can lift this restriction.
Quoted 11 lines
builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 499
Quoted 4 lines
builtin/worktree.c +4
-0+write_file (git_common_path ("worktrees/%s/locked" , wt->id), +"%s" , reason); +return 0 ; +}This is a tangent, but it would be nice for the "git worktree list" command to show whether a worktree is locked, and the reason (if available), both in pretty and porcelain formats. (That was another reason why I suggested to Mike, back when he was adding the "list" command, that 'struct worktree' should have a 'locked' field and
get_worktrees()should populate it automatically.)
Yes. And a good reason for get_worktrees() to read lock status automatically too. I will not do it right away though or at least until after I'm done with move/remove and the shared config file problem.
Signature · 1 line
Duy
Re: [PATCH 4/5] worktree: add "lock" command
Quoted 8 lines · Duy Nguyen
Quoted 7 lines · Eric Sunshine
Quoted 6 lines
builtin/worktree.c +6
-0+ old_reason =is_worktree_locked (wt); +if (old_reason) { +if (*old_reason) +die (_ ("already locked, reason: %s" ), old_reason); +die (_ ("already locked, no reason" )); + }
Quoted 7 lines
Quoted 2 lines
As a convenience, would it make sense to allow a worktree to be re-locked with a different reason rather than erroring out?
For now I think the user can just unlock then relock, after examining the previous reason and maybe incorporating it to the new reason. Yes there is a race but for these operations I don't think it really matters. If it's done often enough, we can lift this restriction.
Agreed. It's easy to imagine someone wanting to change the lock message to correct a typo, but that's likely a rare occurrence, and other conceivable reasons for wanting to change the message are probably even more unusual, thus not worth worrying about now.
Quoted 12 lines
Quoted 11 lines
builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 499
Quoted 4 lines
builtin/worktree.c +4
-0+write_file (git_common_path ("worktrees/%s/locked" , wt->id), +"%s" , reason); +return 0 ; +}This is a tangent, but it would be nice for the "git worktree list" command to show whether a worktree is locked, and the reason (if available), both in pretty and porcelain formats. (That was another reason why I suggested to Mike, back when he was adding the "list" command, that 'struct worktree' should have a 'locked' field and
get_worktrees()should populate it automatically.)
Quoted 4 lines
Yes. And a good reason for
get_worktrees()to read lock status automatically too. I will not do it right away though or at least until after I'm done with move/remove and the shared config file problem.
For the record (for future readers of this thread), [1] itemized various useful bits of information that would be handy as fields in 'struct worktree' and populated automatically by get_worktrees(), all of which (I think) could be nice to have in the output of "git worktree list". Excerpt:
For git-worktree commands such as "lock", "mv", "remove", it likely will be nice to allow people to specify the linked worktree not only by path, but also by tag, and possibly even by $(basename $path) if not ambiguous. Therefore, to support such usage, at minimum, I think you also want to show the worktree's tag (d->d_name) in addition to the path.
Other information which would be nice to display for each worktree (possibly controlled by a --verbose flag):
* the checked out branch or detached head
* whether it is locked
- the lock reason (if available)
- and whether the worktree is currently accessible
* whether it can be pruned
- and the prune reason if so
The prune reason could be obtained by factoring out the reason-determination code from worktree.c:prune_worktree() to make it re-usable.
[1]: http://article.gmane.org/gmane.comp.version-control.git/275528
PATCH v2 5/5 worktree: add "unlock" command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 5 +++++ builtin/worktree.c | 31 +++++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 2 +- t/t2028-worktree-move.sh | 14 ++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +5 -0
@@ -13,6 +13,7 @@ SYNOPSIS 'git worktree list' [--porcelain] 'git worktree lock' [--reason <string>] <path> 'git worktree prune' [-n] [-v] [--expire <expire>] +'git worktree unlock' <path> DESCRIPTION -----------
@@ -72,6 +73,10 @@ prune:: Prune working tree information in $GIT_DIR/worktrees. +unlock:: + +Unlock a worktree, allowing it to be pruned, moved or deleted. + OPTIONS -------
builtin/worktree.c +31 -0
@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {N_ ("git worktree list [<options>]" ),N_ ("git worktree lock [<options>] <path>" ),N_ ("git worktree prune [<options>]" ), +N_ ("git worktree unlock <path>" ), NULL };
@@ -498,6 +499,34 @@ static int lock_worktree(int ac, const char **av, const char *prefix)return 0 ; } +static int unlock_worktree (int ac,const char **av,const char *prefix) +{ +struct option options[] = { +OPT_END () + }; +struct worktree **worktrees, *wt; +struct strbuf dst =STRBUF_INIT ; + + ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + +strbuf_addstr (&dst,prefix_filename (prefix, +strlen (prefix), + av[0 ])); + + worktrees =get_worktrees (); + wt =find_worktree_by_path (worktrees, dst.buf); +if (!wt) +die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); +if (!is_worktree_locked (wt)) +die (_ ("not locked" )); + +return unlink_or_warn (git_common_path ("worktrees/%s/locked" , wt->id)); +} +int cmd_worktree (int ac,const char **av,const char *prefix) {struct option options[] = {
@@ -516,5 +545,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)return list (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"lock" ))return lock_worktree (ac -1 , av +1 , prefix); +if (!strcmp (av[1 ],"unlock" )) +return unlock_worktree (ac -1 , av +1 , prefix);usage_with_options (worktree_usage, options); }
contrib/completion/git-completion.bash +1 -1
@@ -2597,7 +2597,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list lock prune" + local subcommands="add list lock prune unlock" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
t/t2028-worktree-move.sh +14 -0
@@ -31,4 +31,18 @@ test_expect_success 'lock worktree twice' ' test_cmp expected .git/worktrees/source/locked ' +test_expect_success 'unlock main worktree' ' + test_must_fail git worktree unlock . +' + +test_expect_success 'unlock linked worktree' ' + git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + +test_expect_success 'unlock worktree twice' ' + test_must_fail git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + test_done
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH 5/5] worktree: add "unlock" command
Quoted 12 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
builtin/worktree.c +8
-0@@ -498,6 +499,34 @@ static int lock_worktree(int ac, const char **av, const char *prefix) +static int unlock_worktree (int ac,const char **av,const char *prefix) +{ +struct option options[] = { +OPT_END () + }; +struct worktree **worktrees, *wt; +struct strbuf dst =STRBUF_INIT ; +
builtin/worktree.c static int lock_worktree(int ac, const char **av, const char *prefix) · line 516
Quoted 10 lines
builtin/worktree.c +10
-0+ ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + +strbuf_addstr (&dst,prefix_filename (prefix, +strlen (prefix), + av[0 ])); + + worktrees =get_worktrees (); + wt =find_worktree_by_path (worktrees, dst.buf);
strbuf_release(&dst);
Quoted 9 lines
builtin/worktree.c +9
-0+if (!wt) +die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); +if (!is_worktree_locked (wt)) +die (_ ("not locked" )); + +return unlink_or_warn (git_common_path ("worktrees/%s/locked" , wt->id)); +}
Re: [PATCH 1/5] worktree.c: add find_worktree_by_path()
In reply to Nguyễn Thái Ngọc DuyQuoted 14 lines · Nguyễn Thái Ngọc Duy
So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.c +5
-0@@ -222,6 +222,22 @@ const char *get_worktree_git_dir(const struct worktree *wt) +struct worktree *find_worktree_by_path (struct worktree **list, +const char *path_) +{ +char *path =xstrdup (real_path (path_)); +struct worktree *wt =NULL ;
worktree.c const char *get_worktree_git_dir(const struct worktree *wt) · line 236
Quoted 10 lines
worktree.c +10
-0+ +while (*list) { + wt = *list++; +if (!fspathcmp (path,real_path (wt->path))) +break ; + wt =NULL ; + } +free (path); +return wt; +}
Very slightly shorter and perhaps idiomatic (but not at all worth a re-roll):
for (; *list; list++)
if (!fspathcmp(path, real_path((*list)->path)))
break;
free(path);
return *list;Re: [PATCH v3 05/13] worktree.c: mark current worktree
In reply to Duy NguyenQuoted 19 lines · Duy Nguyen
On second thought, why hold patches back, lengthen the worktree-move series and make it a pain to review? I moved a few patches from worktree-move into this series and I took two other out to create nd/error-errno. So I'm going to take more patches out of it, creating two bite-sized series, to be sent shortly.
The first one is purely cleanup (ok, 1/7 is not exactly cleanup)
[1/7] completion: support git-worktree
[2/7]worktree.c: rewritemark_current_worktree()to avoid [3/7] git-worktree.txt: keep subcommand listing in alphabetical [4/7]worktree.c: useis_dot_or_dotdot()
[5/7]worktree.c: addclear_worktree()
[6/7] worktree: avoid 0{40}, too many zeroes, hard to read [7/7] worktree: simplify prefixing pathsAnd the second one adds "git worktree lock" and "git worktree unlock". This series is built on top of the first one, it needs 1/7.
Quoted 7 lines
[1/5]
worktree.c: addfind_worktree_by_path()
[2/5]worktree.c: addis_main_worktree()
[3/5]worktree.c: addis_worktree_locked()
[4/5] worktree: add "lock" command
[5/5] worktree: add "unlock" commandAfter this, worktree-move becomes ~10 patch series.
Yay. Thanks; will take a look.
Re: [PATCH v3 05/13] worktree.c: mark current worktree
In reply to Duy NguyenQuoted 8 lines · Duy Nguyen
On second thought, why hold patches back, lengthen the worktree-move series and make it a pain to review? I moved a few patches from worktree-move into this series and I took two other out to create nd/error-errno. So I'm going to take more patches out of it, creating two bite-sized series, to be sent shortly.
The first one is purely cleanup (ok, 1/7 is not exactly cleanup)
Quoted 7 lines
[1/7] completion: support git-worktree
[2/7]worktree.c: rewritemark_current_worktree()to avoid [3/7] git-worktree.txt: keep subcommand listing in alphabetical [4/7]worktree.c: useis_dot_or_dotdot()
[5/7]worktree.c: addclear_worktree()
[6/7] worktree: avoid 0{40}, too many zeroes, hard to read [7/7] worktree: simplify prefixing paths
Where are these patches designed to apply?
It appears that this depends on something in 'next' (probably nd/worktree-various-heads topic?)
Quoted 11 lines
And the second one adds "git worktree lock" and "git worktree unlock". This series is built on top of the first one, it needs 1/7.
[1/5]
worktree.c: addfind_worktree_by_path()
[2/5]worktree.c: addis_main_worktree()
[3/5]worktree.c: addis_worktree_locked()
[4/5] worktree: add "lock" command
[5/5] worktree: add "unlock" commandAfter this, worktree-move becomes ~10 patch series.
Re: [PATCH v3 05/13] worktree.c: mark current worktree
Quoted 18 lines · Junio C Hamano
Quoted 8 lines · Duy Nguyen
On second thought, why hold patches back, lengthen the worktree-move series and make it a pain to review? I moved a few patches from worktree-move into this series and I took two other out to create nd/error-errno. So I'm going to take more patches out of it, creating two bite-sized series, to be sent shortly.
The first one is purely cleanup (ok, 1/7 is not exactly cleanup)
Quoted 7 lines
[1/7] completion: support git-worktree
[2/7]worktree.c: rewritemark_current_worktree()to avoid [3/7] git-worktree.txt: keep subcommand listing in alphabetical [4/7]worktree.c: useis_dot_or_dotdot()
[5/7]worktree.c: addclear_worktree()
[6/7] worktree: avoid 0{40}, too many zeroes, hard to read [7/7] worktree: simplify prefixing paths
Quoted 4 lines
Where are these patches designed to apply?
It appears that this depends on something in 'next' (probably nd/worktree-various-heads topic?)
Yes. Sorry I forgot to mention that. Though if you move 2/7 to nd/worktree-various-heads (and deal with some conflicts in worktree.c) then it may become independent.
Signature · 1 line
Duy
v2 cover nd/worktree-cleanup-post-head-protection update
In reply to Duy NguyenThis is mostly commit message update plus
- dropping "--force" from the completion patch (a comment from Szeder Gabor in a previous attempt of worktree completion support).
- the removal of clear_worktree() patch.
I keep the completion patch anyway even though two versions of it were posted by Eric and Szeder (and didn't get discussed much) because I needed this for new commands and because I didn't aim high. It's meant to provide very basic completion support. Fancy stuff like ref completion can be added later by people who are more experienced in bash completion stuff.
Anyway, interdiff
-- 8< --
contrib/completion/git-completion.bash +1 -1
@@ -2604,7 +2604,7 @@ _git_worktree () else case "$subcommand,$cur" in add,--*) - __gitcomp "--detach --force" + __gitcomp "--detach" ;; list,--*) __gitcomp "--porcelain"
worktree.c +3 -11
@@ -5,22 +5,14 @@#include "dir.h" #include "wt-status.h" -void clear_worktree (struct worktree *wt) -{ -if (!wt) -return ; -free (wt->path); -free (wt->id); -free (wt->head_ref); -memset (wt,0 ,sizeof (*wt)); -} -void free_worktrees (struct worktree **worktrees) {int i =0 ;for (i =0 ; worktrees[i]; i++) { -clear_worktree (worktrees[i]); +free (worktrees[i]->path); +free (worktrees[i]->id); +free (worktrees[i]->head_ref);free (worktrees[i]); }free (worktrees);
worktree.h +0 -5
@@ -29,11 +29,6 @@ extern struct worktree **get_worktrees(void);*/ extern const char *get_worktree_git_dir (const struct worktree *wt); -/* -* Free up the memory for worktree -*/ -extern void clear_worktree (struct worktree *); -/* * Free up the memory for worktree(s) */
-- 8< --
Nguyễn Thái Ngọc Duy (6):
completion: support git-worktree
worktree.c: rewrite mark_current_worktree() to avoid strbuf git-worktree.txt: keep subcommand listing in alphabetical order worktree.c: use is_dot_or_dotdot()
worktree: avoid 0{40}, too many zeroes, hard to read
worktree: simplify prefixing paths
Documentation/git-worktree.txt | 10 +++++----- builtin/worktree.c | 10 ++++++---- contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++ worktree.c | 18 ++++++++---------- 4 files changed, 42 insertions(+), 19 deletions(-)
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v2 1/6 completion: support git-worktree
This adds bare-bone completion support for git-worktree. More advanced completion (e.g. ref completion in git-worktree-add) can be added later.
--force completion in "worktree add" is left out because that option should be handled with care.
--- contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
contrib/completion/git-completion.bash +23 -0
@@ -2595,6 +2595,29 @@ _git_whatchanged () _git_log } +_git_worktree () +{ + local subcommands="add list prune" + local subcommand="$(__git_find_on_cmdline "$subcommands")" + if [ -z "$subcommand" ]; then + __gitcomp "$subcommands" + else + case "$subcommand,$cur" in + add,--*) + __gitcomp "--detach" + ;; + list,--*) + __gitcomp "--porcelain" + ;; + prune,--*) + __gitcomp "--dry-run --expire --verbose" + ;; + *) + ;; + esac + fi +} + __git_main () { local i c=1 command __git_dir
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v2 2/6 worktree.c: rewrite mark_current_worktree() to avoid strbuf
In reply to Nguyễn Thái Ngọc Duystrbuf is a bit overkill for this function. What we need is to call absolute_path() twice and make sure the second call does not destroy the result of the first. One buffer allocation is enough.
--- worktree.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-)
worktree.c +7 -9
@@ -153,21 +153,19 @@ done:static void mark_current_worktree (struct worktree **worktrees) { -struct strbuf git_dir =STRBUF_INIT ; -struct strbuf path =STRBUF_INIT ; +char *git_dir =xstrdup (absolute_path (get_git_dir ()));int i; -strbuf_addstr (&git_dir,absolute_path (get_git_dir ()));for (i =0 ; worktrees[i]; i++) {struct worktree *wt = worktrees[i]; -strbuf_addstr (&path,absolute_path (get_worktree_git_dir (wt))); - wt->is_current = !fspathcmp (git_dir.buf, path.buf); -strbuf_reset (&path); -if (wt->is_current) +const char *wt_git_dir =get_worktree_git_dir (wt); + +if (!fspathcmp (git_dir,absolute_path (wt_git_dir))) { + wt->is_current =1 ;break ; + } } -strbuf_release (&git_dir); -strbuf_release (&path); +free (git_dir); }struct worktree **get_worktrees (void )
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v2 3/6 git-worktree.txt: keep subcommand listing in alphabetical order
In reply to Nguyễn Thái Ngọc DuyThis is probably not the best order. But it makes it no-brainer to know where to insert new commands. At some point we might want to reorder at least the synopsis part again, grouping commonly use subcommands together.
--- Documentation/git-worktree.txt | 10 +++++----- builtin/worktree.c | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-)
Documentation/git-worktree.txt +5 -5
@@ -10,8 +10,8 @@ SYNOPSIS -------- [verse] 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] -'git worktree prune' [-n] [-v] [--expire <expire>] 'git worktree list' [--porcelain] +'git worktree prune' [-n] [-v] [--expire <expire>] DESCRIPTION -----------
@@ -54,10 +54,6 @@ If `<branch>` is omitted and neither `-b` nor `-B` nor `--detached` used, then, as a convenience, a new branch based at HEAD is created automatically, as if `-b $(basename <path>)` was specified. -prune:: - -Prune working tree information in $GIT_DIR/worktrees. - list:: List details of each worktree. The main worktree is listed first, followed by
@@ -65,6 +61,10 @@ each of the linked worktrees. The output details include if the worktree is bare, the revision currently checked out, and the branch currently checked out (or 'detached HEAD' if none). +prune:: + +Prune working tree information in $GIT_DIR/worktrees. + OPTIONS -------
builtin/worktree.c +1 -1
@@ -13,8 +13,8 @@static const char *const worktree_usage[] = {N_ ("git worktree add [<options>] <path> [<branch>]" ), -N_ ("git worktree prune [<options>]" ),N_ ("git worktree list [<options>]" ), +N_ ("git worktree prune [<options>]" ),NULL };
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v2 4/6 worktree.c: use is_dot_or_dotdot()
In reply to Nguyễn Thái Ngọc Duy--- builtin/worktree.c | 2 +- worktree.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-)
builtin/worktree.c +1 -1
@@ -95,7 +95,7 @@ static void prune_worktrees(void)if (!dir)return ;while ((d =readdir (dir)) !=NULL ) { -if (!strcmp (d->d_name,"." ) || !strcmp (d->d_name,".." )) +if (is_dot_or_dotdot (d->d_name))continue ;strbuf_reset (&reason);if (!prune_worktree (d->d_name, &reason))
worktree.c +1 -1
@@ -187,7 +187,7 @@ struct worktree **get_worktrees(void)if (dir) {while ((d =readdir (dir)) !=NULL ) {struct worktree *linked =NULL ; -if (!strcmp (d->d_name,"." ) || !strcmp (d->d_name,".." )) +if (is_dot_or_dotdot (d->d_name))continue ;if ((linked =get_linked_worktree (d->d_name))) {
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v2 5/6 worktree: avoid 0{40}, too many zeroes, hard to read
In reply to Nguyễn Thái Ngọc Duy--- builtin/worktree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
builtin/worktree.c +1 -1
@@ -262,7 +262,7 @@ static int add_worktree(const char *path, const char *refname,*/ strbuf_reset (&sb);strbuf_addf (&sb,"%s/HEAD" , sb_repo.buf); -write_file (sb.buf,"0000000000000000000000000000000000000000" ); +write_file (sb.buf,sha1_to_hex (null_sha1));strbuf_reset (&sb);strbuf_addf (&sb,"%s/commondir" , sb_repo.buf);write_file (sb.buf,"../.." );
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v2 6/6 worktree: simplify prefixing paths
In reply to Nguyễn Thái Ngọc DuyThis also makes slash conversion always happen on Windows (a side effect of prefix_filename). Which is a good thing.
--- builtin/worktree.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
builtin/worktree.c +3 -1
@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)if (ac <1 || ac >2 )usage_with_options (worktree_usage, options); - path = prefix ?prefix_filename (prefix,strlen (prefix), av[0 ]) : av[0 ]; + path =prefix_filename (prefix,strlen (prefix), av[0 ]); branch = ac <2 ?"HEAD" : av[1 ]; opts.force_new_branch = !!new_branch_force;
@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)if (ac <2 )usage_with_options (worktree_usage, options); +if (!prefix) + prefix ="" ;if (!strcmp (av[1 ],"add" ))return add (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"prune" ))
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v2 6/6] worktree: simplify prefixing paths
Quoted 2 lines · Nguyễn Thái Ngọc Duy
This also makes slash conversion always happen on Windows (a side effect of
prefix_filename). Which is a good thing.
Selling this patch as a mere simplification seems misguided (especially as it's subjective whether this is indeed simpler). I'd find it more easy to buy this change if it was described primarily as benefiting Windows, in the same vein as[1].
More below...
Quoted 13 lines
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
builtin/worktree.c +1
-1@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)if (ac <1 || ac >2 )usage_with_options (worktree_usage, options); - path = prefix ?prefix_filename (prefix,strlen (prefix), av[0 ]) : av[0 ]; + path =prefix_filename (prefix,strlen (prefix), av[0 ]); branch = ac <2 ?"HEAD" : av[1 ]; opts.force_new_branch = !!new_branch_force;
builtin/worktree.c int cmd_worktree(int ac, const char **av, const char *prefix) · line 471
Quoted 5 lines
builtin/worktree.c +2
-0@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)if (ac <2 )usage_with_options (worktree_usage, options); +if (!prefix) + prefix ="" ;
Considering that all the other existing git-worktree subcommands already handle NULL prefix acceptably, it makes me a bit uncomfortable that this "fix", which could be local to add(), leaks into all the other subcommands, thus making the assumption that they (or other new subcommands) will never care about the distinction between NULL and "".
Not a big objection; just a minor niggle, probably not worth a re-roll.
Quoted 3 lines
builtin/worktree.c +0
-0if (!strcmp (av[1 ],"add" ))return add (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"prune" ))
Re: [PATCH v2 6/6] worktree: simplify prefixing paths
Quoted 14 lines · Eric Sunshine
builtin/worktree.c int cmd_worktree(int ac, const char **av, const char *prefix) · line 470
Quoted 5 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +2
-0@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)if (ac <2 )usage_with_options (worktree_usage, options); +if (!prefix) + prefix ="" ;Considering that all the other existing git-worktree subcommands already handle NULL prefix acceptably, it makes me a bit uncomfortable that this "fix", which could be local to
add(), leaks into all the other subcommands, thus making the assumption that they (or other new subcommands) will never care about the distinction between NULL and "".
Quoted 1 line
Not a big objection; just a minor niggle, probably not worth a re-roll.
Okay, I see that you're taking advantage of the prefix="" in patch 5/6, as well, so it's not benefitting only add().
Re: [PATCH v2 6/6] worktree: simplify prefixing paths
Quoted 15 lines · Eric Sunshine
Quoted 14 lines · Eric Sunshine
builtin/worktree.c int cmd_worktree(int ac, const char **av, const char *prefix) · line 470
Quoted 5 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +2
-0@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)if (ac <2 )usage_with_options (worktree_usage, options); +if (!prefix) + prefix ="" ;Considering that all the other existing git-worktree subcommands already handle NULL prefix acceptably, it makes me a bit uncomfortable that this "fix", which could be local to
add(), leaks into all the other subcommands, thus making the assumption that they (or other new subcommands) will never care about the distinction between NULL and "".
Quoted 4 lines
Quoted 1 line
Not a big objection; just a minor niggle, probably not worth a re-roll.
Okay, I see that you're taking advantage of the prefix="" in patch 5/6, as well, so it's not benefitting only
add().
Yep. 'add' so far is the only command taking paths. The next four, lock, unlock, move and delete, all deal with paths and need to deal with NULL prefix otherwise.
Signature · 1 line
Duy
Re: [PATCH v2 0/6] nd/worktree-cleanup-post-head-protection update
In reply to Nguyễn Thái Ngọc DuyQuoted 5 lines · Nguyễn Thái Ngọc Duy
This is mostly commit message update plus
- dropping "--force" from the completion patch (a comment from Szeder Gabor in a previous attempt of worktree completion support).
Quoted 8 lines
- the removal of
clear_worktree()patch.I keep the completion patch anyway even though two versions of it were posted by Eric and Szeder (and didn't get discussed much) because I needed this for new commands and because I didn't aim high. It's meant to provide very basic completion support. Fancy stuff like ref completion can be added later by people who are more experienced in bash completion stuff.
Thanks. Dropping the clear_worktree() patch does indeed seem like a good idea. I've re-read the entire series, and aside from my minor comments on 6/6, everything looks fine and the series is:
Reviewed by: Eric Sunshine <[email protected]>
v2 cover worktree lock/unlock
In reply to Duy NguyenThis should address all of Eric's comments (thanks!). An extra change I made is free_worktrees() at the end of {,un}lock_worktree() to avoid leaking. This series depends on nd/worktree-cleanup-post-head-protection.
Interdiff
-- 8< --
Documentation/git-worktree.txt +10 -8
@@ -40,9 +40,8 @@ section "DETAILS" for more information. If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from -being pruned by creating a file named 'locked' alongside the other -administrative files, optionally containing a plain text reason that -pruning should be suppressed. See section "DETAILS" for more information. +being pruned by issuing the `git worktree lock` command, optionally +specifying `--reason` to explain why the working tree is locked. COMMANDS --------
@@ -65,9 +64,11 @@ bare, the revision currently checked out, and the branch currently checked out lock:: -When a worktree is locked, it cannot be pruned, moved or deleted. For -example, if the worktree is on portable device that is not available -when "git worktree <command>" is executed. +If a working tree is on a portable device or network share which +is not always mounted, lock it to prevent its administrative +files from being pruned automatically. This also prevents it from +being moved or deleted. Optionally, specify a reason for the lock +with `--reason`. prune::
@@ -123,7 +124,7 @@ OPTIONS With `prune`, only expire unused working trees older than <time>. --reason <string>: - An explanation why the worktree is locked. + With `lock`, an explanation why the worktree is locked. DETAILS -------
@@ -165,7 +166,8 @@ instead. To prevent a $GIT_DIR/worktrees entry from being pruned (which can be useful in some situations, such as when the -entry's working tree is stored on a portable device), add a file named +entry's working tree is stored on a portable device), use the +`git worktree lock` comamnd, which adds a file named 'locked' to the entry's directory. The file contains the reason in plain text. For example, if a linked working tree's `.git` file points to `/path/main/.git/worktrees/test-next` then a file named
builtin/worktree.c +8 -3
@@ -482,6 +482,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix) worktrees =get_worktrees (); wt =find_worktree_by_path (worktrees, dst.buf); +strbuf_release (&dst);if (!wt)die (_ ("'%s' is not a working directory" ), av[0 ]);if (is_main_worktree (wt))
@@ -491,11 +492,12 @@ static int lock_worktree(int ac, const char **av, const char *prefix)if (old_reason) {if (*old_reason)die (_ ("already locked, reason: %s" ), old_reason); -die (_ ("already locked, no reason" )); +die (_ ("already locked" )); }write_file (git_common_path ("worktrees/%s/locked" , wt->id),"%s" , reason); +free_worktrees (worktrees);return 0 ; }
@@ -506,6 +508,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) };struct worktree **worktrees, *wt;struct strbuf dst =STRBUF_INIT ; +int ret; ac =parse_options (ac, av, prefix, options, worktree_usage,0 );if (ac !=1 )
@@ -517,14 +520,16 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) worktrees =get_worktrees (); wt =find_worktree_by_path (worktrees, dst.buf); +strbuf_release (&dst);if (!wt)die (_ ("'%s' is not a working directory" ), av[0 ]);if (is_main_worktree (wt))die (_ ("'%s' is a main working directory" ), av[0 ]);if (!is_worktree_locked (wt))die (_ ("not locked" )); - -return unlink_or_warn (git_common_path ("worktrees/%s/locked" , wt->id)); + ret =unlink_or_warn (git_common_path ("worktrees/%s/locked" , wt->id)); +free_worktrees (worktrees); +return ret; }int cmd_worktree (int ac,const char **av,const char *prefix)
t/t2028-worktree-move.sh +20 -0
@@ -25,12 +25,32 @@ test_expect_success 'lock linked worktree' ' test_cmp expected .git/worktrees/source/locked ' +test_expect_success 'lock linked worktree from another worktree' ' + rm .git/worktrees/source/locked && + git worktree add elsewhere && + ( + cd elsewhere && + git worktree lock --reason hahaha ../source + ) && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + test_expect_success 'lock worktree twice' ' test_must_fail git worktree lock source && echo hahaha >expected && test_cmp expected .git/worktrees/source/locked ' +test_expect_success 'lock worktree twice (from the locked worktree)' ' + ( + cd source && + test_must_fail git worktree lock . + ) && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + test_expect_success 'unlock main worktree' ' test_must_fail git worktree unlock . '
worktree.c +14 -15
@@ -218,38 +218,37 @@ struct worktree *find_worktree_by_path(struct worktree **list, const char *path_) {char *path =xstrdup (real_path (path_)); -struct worktree *wt =NULL ; -while (*list) { - wt = *list++; -if (!fspathcmp (path,real_path (wt->path))) +for (; *list; list++) +if (!fspathcmp (path,real_path ((*list)->path)))break ; - wt =NULL ; - }free (path); -return wt; +return *list; }int is_main_worktree (const struct worktree *wt) { -return wt && !wt->id; +return !wt->id; }const char *is_worktree_locked (const struct worktree *wt) {static struct strbuf sb =STRBUF_INIT ; +struct strbuf path =STRBUF_INIT ; + +strbuf_git_common_path (&path,"worktrees/%s/locked" , wt->id); -if (!file_exists (git_common_path ("worktrees/%s/locked" , wt->id))) +if (!file_exists (path.buf)) { +strbuf_release (&path);return NULL ; + }strbuf_reset (&sb); -if (strbuf_read_file (&sb, -git_common_path ("worktrees/%s/locked" , wt->id), -0 ) <0 ) -die_errno (_ ("failed to read '%s'" ), -git_common_path ("worktrees/%s/locked" , wt->id)); +if (strbuf_read_file (&sb, path.buf,0 ) <0 ) +die_errno (_ ("failed to read '%s'" ), path.buf); +strbuf_release (&path); -strbuf_rtrim (&sb); +strbuf_trim (&sb);return sb.buf; }
worktree.h +1 -2
@@ -42,7 +42,7 @@ extern int is_main_worktree(const struct worktree *wt);/* * Return the reason string if the given worktree is locked. Return -* NULL otherwise. +* NULL otherwise. Return value is only valid until the next invocation. */ extern const char *is_worktree_locked (const struct worktree *wt); -- 8< --
Nguyễn Thái Ngọc Duy (5):
worktree.c: add find_worktree_by_path()
worktree.c: add is_main_worktree()
worktree.c: add is_worktree_locked()
worktree: add "lock" command
worktree: add "unlock" command
Documentation/git-worktree.txt | 27 +++++++++--- builtin/worktree.c | 77 ++++++++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 ++- t/t2028-worktree-move.sh (new +x) | 68 ++++++++++++++++++++++++++++++ worktree.c | 38 +++++++++++++++++ worktree.h | 17 ++++++++ 6 files changed, 225 insertions(+), 7 deletions(-) create mode 100755 t/t2028-worktree-move.sh
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v2 1/5 worktree.c: add find_worktree_by_path()
So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.
--- worktree.c | 12 ++++++++++++ worktree.h | 6 ++++++ 2 files changed, 18 insertions(+)
worktree.c +12 -0
@@ -214,6 +214,18 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +struct worktree *find_worktree_by_path (struct worktree **list, +const char *path_) +{ +char *path =xstrdup (real_path (path_)); + +for (; *list; list++) +if (!fspathcmp (path,real_path ((*list)->path))) +break ; +free (path); +return *list; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +6 -0
@@ -29,6 +29,12 @@ extern struct worktree **get_worktrees(void);*/ extern const char *get_worktree_git_dir (const struct worktree *wt); +/* +* Search a worktree by its path. Paths are normalized internally. +*/ +extern struct worktree *find_worktree_by_path (struct worktree **list, +const char *path_); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v2 1/5] worktree.c: add find_worktree_by_path()
Quoted 10 lines · Nguyễn Thái Ngọc Duy
So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.
Helped-by Eric Sunshine <[email protected]>Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
worktree.h extern struct worktree **get_worktrees(void); · line 34
Quoted 6 lines
worktree.h +6
-0@@ -29,6 +29,12 @@ extern struct worktree **get_worktrees(void); +/* +* Search a worktree by its path. Paths are normalized internally. +*/ +extern struct worktree *find_worktree_by_path (struct worktree **list, +const char *path_); +
Sorry for not noticing last time, but I'd probably have named this argument 'path' rather than 'path_' here in the header.
Not worth a re-roll, of course.
Re: [PATCH v2 1/5] worktree.c: add find_worktree_by_path()
In reply to Nguyễn Thái Ngọc DuyQuoted 4 lines · Nguyễn Thái Ngọc Duy
So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.
Beyond the above methods for specifying a worktree, [1] adds $(basename $path) as a possibility if not ambiguous. Excerpt:
For git-worktree commands such as "lock", "mv", "remove", it likely will be nice to allow people to specify the linked worktree not only by path, but also by tag, and possibly even by $(basename $path) if not ambiguous.
[1]: http://article.gmane.org/gmane.comp.version-control.git/275528
Quoted 2 lines
Helped-by Eric Sunshine <[email protected]>Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>
Re: [PATCH v2 1/5] worktree.c: add find_worktree_by_path()
Quoted 6 lines · Eric Sunshine
Quoted 4 lines · Nguyễn Thái Ngọc Duy
So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.
Quoted 7 lines
Beyond the above methods for specifying a worktree, [1] adds $(basename $path) as a possibility if not ambiguous. Excerpt:
For git-worktree commands such as "lock", "mv", "remove", it likely will be nice to allow people to specify the linked worktree not only by path, but also by tag, and possibly even by $(basename $path) if not ambiguous.
Thanks. This changes things. Turns out my "find_by_path" API cannot be easily extended to support identifying worktrees some other way. I will need to pass the command line argument as-is, so that this "find_worktree" (by any means) has enough info to decide (especially about ambiguation).
Signature · 1 line
Duy
PATCH v2 2/5 worktree.c: add is_main_worktree()
In reply to Nguyễn Thái Ngọc DuyMain worktree _is_ different. You can lock a linked worktree but not the main one, for example. Provide an API for checking that.
--- worktree.c | 5 +++++ worktree.h | 5 +++++ 2 files changed, 10 insertions(+)
worktree.c +5 -0
@@ -226,6 +226,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,return *list; } +int is_main_worktree (const struct worktree *wt) +{ +return !wt->id; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +5 -0
@@ -35,6 +35,11 @@ extern const char *get_worktree_git_dir(const struct worktree *wt);extern struct worktree *find_worktree_by_path (struct worktree **list,const char *path_); +/* +* Return true if the given worktree is the main one. +*/ +extern int is_main_worktree (const struct worktree *wt); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v2 3/5 worktree.c: add is_worktree_locked()
In reply to Nguyễn Thái Ngọc DuyThis provides an API for checking if a worktree is locked. We need to check this to avoid double locking a worktree, or try to unlock one when it's not even locked.
--- worktree.c | 21 +++++++++++++++++++++ worktree.h | 6 ++++++ 2 files changed, 27 insertions(+)
worktree.c +21 -0
@@ -231,6 +231,27 @@ int is_main_worktree(const struct worktree *wt)return !wt->id; } +const char *is_worktree_locked (const struct worktree *wt) +{ +static struct strbuf sb =STRBUF_INIT ; +struct strbuf path =STRBUF_INIT ; + +strbuf_git_common_path (&path,"worktrees/%s/locked" , wt->id); + +if (!file_exists (path.buf)) { +strbuf_release (&path); +return NULL ; + } + +strbuf_reset (&sb); +if (strbuf_read_file (&sb, path.buf,0 ) <0 ) +die_errno (_ ("failed to read '%s'" ), path.buf); +strbuf_release (&path); + +strbuf_trim (&sb); +return sb.buf; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +6 -0
@@ -40,6 +40,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,*/ extern int is_main_worktree (const struct worktree *wt); +/* +* Return the reason string if the given worktree is locked. Return +* NULL otherwise. Return value is only valid until the next invocation. +*/ +extern const char *is_worktree_locked (const struct worktree *wt); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v2 4/5 worktree: add "lock" command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 22 ++++++++++---- builtin/worktree.c | 43 +++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 +++- t/t2028-worktree-move.sh (new +x) | 54 ++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 7 deletions(-) create mode 100755 t/t2028-worktree-move.sh
Documentation/git-worktree.txt +16 -6
@@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] 'git worktree list' [--porcelain] +'git worktree lock' [--reason <string>] <path> 'git worktree prune' [-n] [-v] [--expire <expire>] DESCRIPTION
@@ -38,9 +39,8 @@ section "DETAILS" for more information. If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from -being pruned by creating a file named 'locked' alongside the other -administrative files, optionally containing a plain text reason that -pruning should be suppressed. See section "DETAILS" for more information. +being pruned by issuing the `git worktree lock` command, optionally +specifying `--reason` to explain why the working tree is locked. COMMANDS --------
@@ -61,6 +61,14 @@ each of the linked worktrees. The output details include if the worktree is bare, the revision currently checked out, and the branch currently checked out (or 'detached HEAD' if none). +lock:: + +If a working tree is on a portable device or network share which +is not always mounted, lock it to prevent its administrative +files from being pruned automatically. This also prevents it from +being moved or deleted. Optionally, specify a reason for the lock +with `--reason`. + prune:: Prune working tree information in $GIT_DIR/worktrees.
@@ -110,6 +118,9 @@ OPTIONS --expire <time>:: With `prune`, only expire unused working trees older than <time>. +--reason <string>: + With `lock`, an explanation why the worktree is locked. + DETAILS ------- Each linked working tree has a private sub-directory in the repository's
@@ -150,7 +161,8 @@ instead. To prevent a $GIT_DIR/worktrees entry from being pruned (which can be useful in some situations, such as when the -entry's working tree is stored on a portable device), add a file named +entry's working tree is stored on a portable device), use the +`git worktree lock` comamnd, which adds a file named 'locked' to the entry's directory. The file contains the reason in plain text. For example, if a linked working tree's `.git` file points to `/path/main/.git/worktrees/test-next` then a file named
@@ -226,8 +238,6 @@ performed manually, such as: - `remove` to remove a linked working tree and its administrative files (and warn if the working tree is dirty) - `mv` to move or rename a working tree and update its administrative files -- `lock` to prevent automatic pruning of administrative files (for instance, - for a working tree on a portable device) GIT ---
builtin/worktree.c +43 -0
60 lines not on this page · raw message
contrib/completion/git-completion.bash +4 -1
@@ -2597,7 +2597,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list prune" + local subcommands="add list lock prune" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
@@ -2609,6 +2609,9 @@ _git_worktree () list,--*) __gitcomp "--porcelain" ;; + lock,--*) + __gitcomp "--reason" + ;; prune,--*) __gitcomp "--dry-run --expire --verbose" ;;
t/t2028-worktree-move.sh new file +54 -0
54 lines not on this page · raw message
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v2 4/5] worktree: add "lock" command
Documentation/git-worktree.txt instead. · line 165
Quoted 11 lines · Nguyễn Thái Ngọc Duy
Helped-by Eric Sunshine <[email protected]>Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
Documentation/git-worktree.txt +2
-1@@ -150,7 +161,8 @@ instead. To prevent a $GIT_DIR/worktrees entry from being pruned (which can be useful in some situations, such as when the -entry's working tree is stored on a portable device), add a file named +entry's working tree is stored on a portable device), use the +`git worktree lock` comamnd, which adds a file named
s/comamnd/command/
t/t2028-worktree-move.sh line 6
Quoted 11 lines
Documentation/git-worktree.txt +0
-0'locked' to the entry's directory. The file contains the reason in plain text. For example, if a linked working tree's `.git` file points to `/path/main/.git/worktrees/test-next` then a file namedt/t2028-worktree-move.sh +6
-0@@ -0,0 +1,54 @@ +test_expect_success 'lock linked worktree from another worktree' ' + rm .git/worktrees/source/locked && + git worktree add elsewhere && + ( + cd elsewhere && + git worktree lock --reason hahaha ../source
Could use "git -C elsewhere worktree lock ..." and drop the subshell, but not worth a re-roll.
t/t2028-worktree-move.sh line 48
Quoted 9 lines
t/t2028-worktree-move.sh +9
-0+ ) && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_expect_success 'lock worktree twice (from the locked worktree)' ' + ( + cd source && + test_must_fail git worktree lock .
Likewise, -C.
Quoted 4 lines
t/t2028-worktree-move.sh +4
-0+ ) && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +'
PATCH v2 5/5 worktree: add "unlock" command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 5 +++++ builtin/worktree.c | 34 ++++++++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 2 +- t/t2028-worktree-move.sh | 14 ++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +5 -0
@@ -13,6 +13,7 @@ SYNOPSIS 'git worktree list' [--porcelain] 'git worktree lock' [--reason <string>] <path> 'git worktree prune' [-n] [-v] [--expire <expire>] +'git worktree unlock' <path> DESCRIPTION -----------
@@ -73,6 +74,10 @@ prune:: Prune working tree information in $GIT_DIR/worktrees. +unlock:: + +Unlock a worktree, allowing it to be pruned, moved or deleted. + OPTIONS -------
builtin/worktree.c +34 -0
@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {N_ ("git worktree list [<options>]" ),N_ ("git worktree lock [<options>] <path>" ),N_ ("git worktree prune [<options>]" ), +N_ ("git worktree unlock <path>" ), NULL };
@@ -500,6 +501,37 @@ static int lock_worktree(int ac, const char **av, const char *prefix)return 0 ; } +static int unlock_worktree (int ac,const char **av,const char *prefix) +{ +struct option options[] = { +OPT_END () + }; +struct worktree **worktrees, *wt; +struct strbuf dst =STRBUF_INIT ; +int ret; + + ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + +strbuf_addstr (&dst,prefix_filename (prefix, +strlen (prefix), + av[0 ])); + + worktrees =get_worktrees (); + wt =find_worktree_by_path (worktrees, dst.buf); +strbuf_release (&dst); +if (!wt) +die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); +if (!is_worktree_locked (wt)) +die (_ ("not locked" )); + ret =unlink_or_warn (git_common_path ("worktrees/%s/locked" , wt->id)); +free_worktrees (worktrees); +return ret; +} +int cmd_worktree (int ac,const char **av,const char *prefix) {struct option options[] = {
@@ -518,5 +550,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)return list (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"lock" ))return lock_worktree (ac -1 , av +1 , prefix); +if (!strcmp (av[1 ],"unlock" )) +return unlock_worktree (ac -1 , av +1 , prefix);usage_with_options (worktree_usage, options); }
contrib/completion/git-completion.bash +1 -1
@@ -2597,7 +2597,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list lock prune" + local subcommands="add list lock prune unlock" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
t/t2028-worktree-move.sh +14 -0
@@ -51,4 +51,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' ' test_cmp expected .git/worktrees/source/locked ' +test_expect_success 'unlock main worktree' ' + test_must_fail git worktree unlock . +' + +test_expect_success 'unlock linked worktree' ' + git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + +test_expect_success 'unlock worktree twice' ' + test_must_fail git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + test_done
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v2 5/5] worktree: add "unlock" command
Quoted 5 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
builtin/worktree.c +1
-0@@ -500,6 +501,37 @@ static int lock_worktree(int ac, const char **av, const char *prefix) +static int unlock_worktree (int ac,const char **av,const char *prefix)
builtin/worktree.c static int lock_worktree(int ac, const char **av, const char *prefix) · line 511
Quoted 10 lines
builtin/worktree.c +10
-0+{ + [...] + wt =find_worktree_by_path (worktrees, dst.buf); +strbuf_release (&dst); +if (!wt) +die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); +if (!is_worktree_locked (wt)) +die (_ ("not locked" ));
Could be _("'%s' is not locked"), but not worth a re-roll.
Quoted 4 lines
builtin/worktree.c +4
-0+ ret =unlink_or_warn (git_common_path ("worktrees/%s/locked" , wt->id)); +free_worktrees (worktrees); +return ret; +}
Re: [PATCH v2 0/5] worktree lock/unlock
In reply to Nguyễn Thái Ngọc DuyQuoted 3 lines · Nguyễn Thái Ngọc Duy
This should address all of Eric's comments (thanks!). An extra change I made is
free_worktrees()at the end of {,un}lock_worktree() to avoid leaking. This series depends on nd/worktree-cleanup-post-head-protection.
Thanks, this addresses all my comments from the previous round (aside from the suggestion to add a 'locked' field to 'struct worktree' and populate it automatically, which you elected to defer for the present).
I re-read the entire series and, aside from the typo in the documentation update of patch 4/5, everything looks fine, and the series is:
Re: [PATCH v2 0/5] worktree lock/unlock
Quoted 9 lines · Eric Sunshine
Quoted 3 lines · Nguyễn Thái Ngọc Duy
This should address all of Eric's comments (thanks!). An extra change I made is
free_worktrees()at the end of {,un}lock_worktree() to avoid leaking. This series depends on nd/worktree-cleanup-post-head-protection.Thanks, this addresses all my comments from the previous round (aside from the suggestion to add a 'locked' field to 'struct worktree' and populate it automatically, which you elected to defer for the present).
If there's another re-roll (likely so), I'm tempted to do that too as it's quite clear now that "locked" belongs in struct worktree.
Signature · 1 line
Duy
v3 cover worktree lock/unlock
In reply to Nguyễn Thái Ngọc Duyv3 changes almost all patches so I didn't bother with interdiff
[1/6] worktree.c: add find_worktree()
This replaces the old find_wortree_by_path(). It now takes both prefix and command line argument so it can do some intelligent things with them if needed.
[2/6] worktree.c: find_worktree() learns to identify worktrees by basename
This is from Eric's suggestion. Here we only care about the "argument" argument and ignore the "prefix" argument from 1/6
[3/6] worktree.c: add is_main_worktree()
This is the only unchanged patch since v2!
[4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()
This used to be is_worktree_locked()
[5/6] worktree: add "lock" command
Besides Eric's comments in v2, the syntax line "worktree lock <path>" is changed to "worktree lock <worktree>", where <worktree> could be either a path, or a base name, or something more in future. I'm aware that we don't like showing "worktrees" in user documents, favoring working trees. But I think <worktree> is ok in this case.
[6/6] worktree: add "unlock" command
Pretty much addressing comments from v2 and some more changes because of the new 1/6 and 4/6.
Documentation/git-worktree.txt | 33 +++++++++++++---- builtin/worktree.c | 66 ++++++++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 ++- t/t2028-worktree-move.sh (new +x) | 62 ++++++++++++++++++++++++++++++++ worktree.c | 53 +++++++++++++++++++++++++++ worktree.h | 14 ++++++++ 6 files changed, 226 insertions(+), 7 deletions(-) create mode 100755 t/t2028-worktree-move.sh
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v3 1/6 worktree.c: add find_worktree()
So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. The current implementation identifies worktrees by path (*). In future, the function could learn to identify by $(basename $path) or tags...
(*) We could probably go cheaper with comparing inode number (and probably more reliable than paths when unicode enters the game). But not all systems have good inode that so let's stick to something simple for now.
--- worktree.c | 15 +++++++++++++++ worktree.h | 8 ++++++++ 2 files changed, 23 insertions(+)
worktree.c +15 -0
@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +struct worktree *find_worktree (struct worktree **list, +const char *prefix, +const char *arg) +{ +char *path; + + arg =prefix_filename (prefix,strlen (prefix), arg); + path =xstrdup (real_path (arg)); +for (; *list; list++) +if (!fspathcmp (path,real_path ((*list)->path))) +break ; +free (path); +return *list; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +8 -0
@@ -29,6 +29,14 @@ extern struct worktree **get_worktrees(void);*/ extern const char *get_worktree_git_dir (const struct worktree *wt); +/* +* Search a worktree that can be unambiguously identified by +* "arg". "prefix" must not be NULL. +*/ +extern struct worktree *find_worktree (struct worktree **list, +const char *prefix, +const char *arg); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v3 2/6 worktree.c: find_worktree() learns to identify worktrees by basename
In reply to Nguyễn Thái Ngọc DuyThis allows the user to do something like "worktree lock foo" instead of "worktree lock <path/to/foo>". With completion support it could be quite convenient. While this base name search can be done in the same worktree iteration loop, the code is split into a separate function for clarity.
--- worktree.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
worktree.c +21 -0
@@ -214,12 +214,33 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +static struct worktree *find_worktree_by_basename (struct worktree **list, +const char *base_name) +{ +struct worktree *found =NULL ; +int nr_found =0 ; + +for (; *list && nr_found <2 ; list++) { +char *path =xstrdup ((*list)->path); +if (!fspathcmp (base_name,basename (path))) { + found = *list; + nr_found++; + } +free (path); + } +return nr_found ==1 ? found :NULL ; +} +struct worktree *find_worktree (struct worktree **list,const char *prefix,const char *arg) { +struct worktree *wt;char *path; +if ((wt =find_worktree_by_basename (list, arg))) +return wt; + arg =prefix_filename (prefix,strlen (prefix), arg); path =xstrdup (real_path (arg));for (; *list; list++)
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename
Quoted 4 lines · Nguyễn Thái Ngọc Duy
This allows the user to do something like "worktree lock foo" instead of "worktree lock <path/to/foo>". With completion support it could be quite convenient. While this base name search can be done in the same worktree iteration loop, the code is split into a separate function for clarity.
Makes me wonder if you want to do this without calling basename(3) function at all. I do not think such a feature would cost that much over what we see in this patch.
That is, wouldn't you rather see "worktree lock to/foo" work when 'foo' is ambiguous but 'to/foo' is not?
Quoted 45 lines
Suggested-by Eric Sunshine <[email protected]>Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>--- worktree.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)worktree.c +21
-0@@ -214,12 +214,33 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +static struct worktree *find_worktree_by_basename (struct worktree **list, +const char *base_name) +{ +struct worktree *found =NULL ; +int nr_found =0 ; + +for (; *list && nr_found <2 ; list++) { +char *path =xstrdup ((*list)->path); +if (!fspathcmp (base_name,basename (path))) { + found = *list; + nr_found++; + } +free (path); + } +return nr_found ==1 ? found :NULL ; +} +struct worktree *find_worktree (struct worktree **list,const char *prefix,const char *arg) { +struct worktree *wt;char *path; +if ((wt =find_worktree_by_basename (list, arg))) +return wt; + arg =prefix_filename (prefix,strlen (prefix), arg); path =xstrdup (real_path (arg));for (; *list; list++)
Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename
Quoted 7 lines · Junio C Hamano
Quoted 4 lines · Nguyễn Thái Ngọc Duy
This allows the user to do something like "worktree lock foo" instead of "worktree lock <path/to/foo>". With completion support it could be quite convenient. While this base name search can be done in the same worktree iteration loop, the code is split into a separate function for clarity.
Quoted 6 lines
Makes me wonder if you want to do this without calling basename(3) function at all. I do not think such a feature would cost that much over what we see in this patch.
That is, wouldn't you rather see "worktree lock to/foo" work when 'foo' is ambiguous but 'to/foo' is not?
I don't know. I suppose if people have to make basename $path the same because of some weird build settings, e.g. abc/git and def/git, then this basename selection becomes useless.
I had similar thought though, if you only have a worktree named "foo" then "fo" or even "f" should be unambiguous and can also identify a worktree, similar to short sha-1. But I discarded that idea because of the higher chances that typos can select a wrong worktree. We would need to convert or match both '/' and '\' in "to/foo" case because of Windows, so it's not much easier than basename().
Signature · 1 line
Duy
Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename
Quoted 11 lines · Duy Nguyen
Quoted 2 lines
That is, wouldn't you rather see "worktree lock to/foo" work when 'foo' is ambiguous but 'to/foo' is not?
I don't know. I suppose if people have to make
basename $paththe same because of some weird build settings, e.g. abc/git and def/git, then this basename selection becomes useless.I had similar thought though, if you only have a worktree named "foo" then "fo" or even "f" should be unambiguous and can also identify a worktree, similar to short sha-1. But I discarded that idea because of the higher chances that typos can select a wrong worktree.
I do not think abbreviation of "foo" down to "fo" is sensible at all. I would instead suggest "path-component-wise tail match", i.e. "to/foo" would match "path/to/foo", "path/TO/FOO" (on fold-case filesystems), but not "pathto/foo".
Quoted 3 lines
We would
need to convert or match both '/' and '\' in "to/foo" case because of Windows, so it's not much easier thanbasename().
I never said "easier to implement". But can this codepath get backslashed paths in the first place? I somehow thought that normalization would happen a lot before the control reaches here.
You'll be calling into fspathcmp() anyway; shouldn't the function know that '/' and '\' are equivalent on some platforms, or is it legal to only call fspathcmp() on a single path component without directory separator?
Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename
Quoted 4 lines · Junio C Hamano
Quoted 3 lines
We would
need to convert or match both '/' and '\' in "to/foo" case because of Windows, so it's not much easier thanbasename().
Quoted 8 lines
I never said "easier to implement". But can this codepath get backslashed paths in the first place? I somehow thought that normalization would happen a lot before the control reaches here.
You'll be calling into
fspathcmp()anyway; shouldn't the function know that '/' and '\' are equivalent on some platforms, or is it legal to only callfspathcmp()on a single path component without directory separator?
We still need to calculate the length to compare, which could be problematic when utf-8 is involved, or some other encoding. If we always split at '/' boundary though (e.g. "abc/def/ghi", "def/ghi" or "ghi" but never "ef/ghi") then it should be ok.
Signature · 1 line
Duy
Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename
Quoted 9 lines · Duy Nguyen
Quoted 8 lines · Junio C Hamano
Quoted 3 lines
We would
need to convert or match both '/' and '\' in "to/foo" case because of Windows, so it's not much easier thanbasename().I never said "easier to implement". But can this codepath get backslashed paths in the first place? I somehow thought that normalization would happen a lot before the control reaches here.
Quoted 7 lines
Quoted 4 lines
You'll be calling into
fspathcmp()anyway; shouldn't the function know that '/' and '\' are equivalent on some platforms, or is it legal to only callfspathcmp()on a single path component without directory separator?We still need to calculate the length to compare, which could be problematic when utf-8 is involved, or some other encoding.
Hmph. I was unaware that fspathcmp() used here does more than byte-for-byte processing, which would cause problems due to encoding issues when you hand code the comparison.
+static struct worktree *find_worktree_by_basename(struct worktree **list,
+ const char *base_name)
+{
+ struct worktree *found = NULL;
+ int nr_found = 0;
+
+ for (; *list && nr_found < 2; list++) {
+ char *path = xstrdup((*list)->path);
+ if (!fspathcmp(base_name, basename(path))) {
+ found = *list;
+ nr_found++;
+ }
+ free(path);
+ }
+ return nr_found == 1 ? found : NULL;
+}
Quoted 2 lines
If we always split at '/' boundary though (e.g. "abc/def/ghi", "def/ghi" or "ghi" but never "ef/ghi") then it should be ok.
Does "basename()" used here know '/' and '\' can both be a directory separator, or does worktree->path have a normalized representation of the path, i.e. '/' is the only directory separator?
Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename
Quoted 18 lines · Junio C Hamano
worktree.c +16
-0+static struct worktree *find_worktree_by_basename (struct worktree **list, +const char *base_name) +{ +struct worktree *found =NULL ; +int nr_found =0 ; + +for (; *list && nr_found <2 ; list++) { +char *path =xstrdup ((*list)->path); +if (!fspathcmp (base_name,basename (path))) { + found = *list; + nr_found++; + } +free (path); + } +return nr_found ==1 ? found :NULL ; +}
Quoted 6 lines
Quoted 2 lines
If we always split at '/' boundary though (e.g. "abc/def/ghi", "def/ghi" or "ghi" but never "ef/ghi") then it should be ok.
Does "
basename()" used here know '/' and '\' can both be a directory separator, or doesworktree->pathhave a normalized representation of the path, i.e. '/' is the only directory separator?
basename() does (or I think so because Windows has its own version). worktree->path always uses '/' but the command line option can come with either '/' or '\'. Probably safest to accept both.
Signature · 1 line
Duy
Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename
Quoted 3 lines · Duy Nguyen
basename()does (or I think so because Windows has its own version).worktree->pathalways uses '/' but the command line option can come with either '/' or '\'. Probably safest to accept both.
OK. Going beyond basename() was merely "This might be an easy nice-to-have enhancement", not "basename() is not sufficient because of such and such issues", so the patch is fine as-is.
Thanks.
Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename
In reply to Duy NguyenHi,
Quoted 9 lines · Duy Nguyen
Quoted 3 lines · Junio C Hamano
Does "
basename()" used here know '/' and '\' can both be a directory separator, or doesworktree->pathhave a normalized representation of the path, i.e. '/' is the only directory separator?
basename()does (or I think so because Windows has its own version).worktree->pathalways uses '/' but the command line option can come with either '/' or '\'. Probably safest to accept both.
It is very easy to see what the Windows version of basename() does by looking at our very own test code:
https://github.com/git/git/blob/v2.8.3/test-path-utils.c#L71-L110
Short answer: basename() knows that both '/' and '\' are directory separators.
Ciao,
Dscho
PATCH v3 3/6 worktree.c: add is_main_worktree()
In reply to Nguyễn Thái Ngọc DuyMain worktree _is_ different. You can lock a linked worktree but not the main one, for example. Provide an API for checking that.
--- worktree.c | 5 +++++ worktree.h | 5 +++++ 2 files changed, 10 insertions(+)
worktree.c +5 -0
@@ -250,6 +250,11 @@ struct worktree *find_worktree(struct worktree **list,return *list; } +int is_main_worktree (const struct worktree *wt) +{ +return !wt->id; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +5 -0
@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,const char *prefix, const char *arg); +/* +* Return true if the given worktree is the main one. +*/ +extern int is_main_worktree (const struct worktree *wt); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v3 3/6] worktree.c: add is_main_worktree()
Quoted 2 lines · Nguyễn Thái Ngọc Duy
Main worktree _is_ different. You can lock a linked worktree but not the main one, for example. Provide an API for checking that.
You haven't defined what it means to "lock" a worktree, so that example does not sound convincing at all, until the reader sees what happens later in the series. I'll just take your word for it and keep reading for now ;-)
Quoted 39 lines
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>--- worktree.c | 5 +++++ worktree.h | 5 +++++ 2 files changed, 10 insertions(+)worktree.c +5
-0@@ -250,6 +250,11 @@ struct worktree *find_worktree(struct worktree **list,return *list; } +int is_main_worktree (const struct worktree *wt) +{ +return !wt->id; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {worktree.h +5
-0@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,const char *prefix, const char *arg); +/* +* Return true if the given worktree is the main one. +*/ +extern int is_main_worktree (const struct worktree *wt); +/* * Free up the memory for worktree(s) */
PATCH v3 4/6 worktree.c: retrieve lock status (and optionally reason) in get_worktrees()
In reply to Nguyễn Thái Ngọc DuyWe need this later to avoid double locking a worktree, or unlocking one when it's not even locked.
--- worktree.c | 12 ++++++++++++ worktree.h | 1 + 2 files changed, 13 insertions(+)
worktree.c +12 -0
@@ -98,6 +98,7 @@ static struct worktree *get_main_worktree(void) worktree->is_detached = is_detached; worktree->is_current =0 ;add_head_info (&head_ref, worktree); + worktree->lock_reason =NULL ; done:strbuf_release (&path);
@@ -144,6 +145,17 @@ static struct worktree *get_linked_worktree(const char *id) worktree->is_current =0 ;add_head_info (&head_ref, worktree); +strbuf_reset (&path); +strbuf_addf (&path,"%s/worktrees/%s/locked" ,get_git_common_dir (), id); +if (file_exists (path.buf)) { +struct strbuf lock_reason =STRBUF_INIT ; +if (strbuf_read_file (&lock_reason, path.buf,0 ) <0 ) +die_errno (_ ("failed to read '%s'" ), path.buf); +strbuf_trim (&lock_reason); + worktree->lock_reason =strbuf_detach (&lock_reason,NULL ); + }else + worktree->lock_reason =NULL ; + done:strbuf_release (&path);strbuf_release (&worktree_path);
worktree.h +1 -0
@@ -5,6 +5,7 @@ struct worktree {char *path;char *id;char *head_ref; +char *lock_reason;/* NULL means not locked */ unsigned char head_sha1[20 ];int is_detached;int is_bare;
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v3 4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()
Quoted 2 lines · Nguyễn Thái Ngọc Duy
We need this later to avoid double locking a worktree, or unlocking one when it's not even locked.
Shouldn't this be done lazily?
If a user is working in worktree B and is not doing anything funky, she would not care why worktree A and C are locked, even though she might care the fact that they are locked.
Quoted 48 lines
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>--- worktree.c | 12 ++++++++++++ worktree.h | 1 + 2 files changed, 13 insertions(+)worktree.c +12
-0@@ -98,6 +98,7 @@ static struct worktree *get_main_worktree(void) worktree->is_detached = is_detached; worktree->is_current =0 ;add_head_info (&head_ref, worktree); + worktree->lock_reason =NULL ; done:strbuf_release (&path);@@ -144,6 +145,17 @@ static struct worktree *get_linked_worktree(const char *id) worktree->is_current =0 ;add_head_info (&head_ref, worktree); +strbuf_reset (&path); +strbuf_addf (&path,"%s/worktrees/%s/locked" ,get_git_common_dir (), id); +if (file_exists (path.buf)) { +struct strbuf lock_reason =STRBUF_INIT ; +if (strbuf_read_file (&lock_reason, path.buf,0 ) <0 ) +die_errno (_ ("failed to read '%s'" ), path.buf); +strbuf_trim (&lock_reason); + worktree->lock_reason =strbuf_detach (&lock_reason,NULL ); + }else + worktree->lock_reason =NULL ; + done:strbuf_release (&path);strbuf_release (&worktree_path);worktree.h +1
-0@@ -5,6 +5,7 @@ struct worktree {char *path;char *id;char *head_ref; +char *lock_reason;/* NULL means not locked */ unsigned char head_sha1[20 ];int is_detached;int is_bare;
Re: [PATCH v3 4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()
Quoted 10 lines · Junio C Hamano
Quoted 2 lines · Nguyễn Thái Ngọc Duy
We need this later to avoid double locking a worktree, or unlocking one when it's not even locked.
Shouldn't this be done lazily?
If a user is working in worktree B and is not doing anything funky, she would not care why worktree A and C are locked, even though she might care the fact that they are locked.
You and Eric will have to settle this. It was done lazily in v2, but Eric convinced me this lock thing will be needed for many worktree commands (list, lock, unlock, move, remove and maybe even prune) that it makes more sense to make it a field in struct worktree instead. For a dozen worktrees, it does not really matter. But get_worktrees() is also being used outside "git worktree" command, some of those new callers may get picky. Maybe a common ground is adding a flag to get_worktrees() to select what fields to be filled?
Signature · 1 line
Duy
Re: [PATCH v3 4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()
Quoted 12 lines · Duy Nguyen
Quoted 10 lines · Junio C Hamano
Quoted 2 lines · Nguyễn Thái Ngọc Duy
We need this later to avoid double locking a worktree, or unlocking one when it's not even locked.
Shouldn't this be done lazily?
If a user is working in worktree B and is not doing anything funky, she would not care why worktree A and C are locked, even though she might care the fact that they are locked.
Quoted 8 lines
You and Eric will have to settle this. It was done lazily in v2, but Eric convinced me this lock thing will be needed for many worktree commands (list, lock, unlock, move, remove and maybe even prune) that it makes more sense to make it a field in struct worktree instead. For a dozen worktrees, it does not really matter. But
get_worktrees()is also being used outside "git worktree" command, some of those new callers may get picky. Maybe a common ground is adding a flag toget_worktrees()to select what fields to be filled?
NO.
When you tell callers that they have DIRECT access to fields, then they need to keep track of what flag they (or their callers) passed when get_worktrees() was called, which is a road to insanity.
By "lazily", I didn't mean "do not have a field there; instead every time read from the file and return the value". A lazy interface would give a function to read the "is it locked and if so why" information as the sole interface, whose implementation would _not_ do the reading when a worktree structure is instantiated, the first call would do the reading, and it is up to the implementation to cache the result of that first call's work in the structure and answer the subsequent calls from the cache, or do the same reading for subsequent calls.
PATCH v3 5/6 worktree: add "lock" command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 28 +++++++++++++++----- builtin/worktree.c | 38 +++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 +++- t/t2028-worktree-move.sh (new +x) | 48 ++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 7 deletions(-) create mode 100755 t/t2028-worktree-move.sh
Documentation/git-worktree.txt +22 -6
@@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] 'git worktree list' [--porcelain] +'git worktree lock' [--reason <string>] <worktree> 'git worktree prune' [-n] [-v] [--expire <expire>] DESCRIPTION
@@ -38,9 +39,8 @@ section "DETAILS" for more information. If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from -being pruned by creating a file named 'locked' alongside the other -administrative files, optionally containing a plain text reason that -pruning should be suppressed. See section "DETAILS" for more information. +being pruned by issuing the `git worktree lock` command, optionally +specifying `--reason` to explain why the working tree is locked. COMMANDS --------
@@ -61,6 +61,14 @@ each of the linked worktrees. The output details include if the worktree is bare, the revision currently checked out, and the branch currently checked out (or 'detached HEAD' if none). +lock:: + +If a working tree is on a portable device or network share which +is not always mounted, lock it to prevent its administrative +files from being pruned automatically. This also prevents it from +being moved or deleted. Optionally, specify a reason for the lock +with `--reason`. + prune:: Prune working tree information in $GIT_DIR/worktrees.
@@ -110,6 +118,15 @@ OPTIONS --expire <time>:: With `prune`, only expire unused working trees older than <time>. +--reason <string>: + With `lock`, an explanation why the worktree is locked. + +<worktree>:: + Working trees can be identified by path, either relative or + absolute. Alternatively, if the last component in the + worktree's path is unique among working trees, it can be used + as well. + DETAILS ------- Each linked working tree has a private sub-directory in the repository's
@@ -150,7 +167,8 @@ instead. To prevent a $GIT_DIR/worktrees entry from being pruned (which can be useful in some situations, such as when the -entry's working tree is stored on a portable device), add a file named +entry's working tree is stored on a portable device), use the +`git worktree lock` command, which adds a file named 'locked' to the entry's directory. The file contains the reason in plain text. For example, if a linked working tree's `.git` file points to `/path/main/.git/worktrees/test-next` then a file named
@@ -226,8 +244,6 @@ performed manually, such as: - `remove` to remove a linked working tree and its administrative files (and warn if the working tree is dirty) - `mv` to move or rename a working tree and update its administrative files -- `lock` to prevent automatic pruning of administrative files (for instance, - for a working tree on a portable device) GIT ---
builtin/worktree.c +38 -0
@@ -14,6 +14,7 @@static const char *const worktree_usage[] = {N_ ("git worktree add [<options>] <path> [<branch>]" ),N_ ("git worktree list [<options>]" ), +N_ ("git worktree lock [<options>] <path>" ),N_ ("git worktree prune [<options>]" ),NULL };
@@ -459,6 +460,41 @@ static int list(int ac, const char **av, const char *prefix)return 0 ; } +static int lock_worktree (int ac,const char **av,const char *prefix) +{ +const char *reason ="" , *old_reason; +struct option options[] = { +OPT_STRING (0 ,"reason" , &reason,N_ ("string" ), +N_ ("reason for locking" )), +OPT_END () + }; +struct worktree **worktrees, *wt; + + ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + + worktrees =get_worktrees (); + wt =find_worktree (worktrees, prefix, av[0 ]); +if (!wt) +die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); + + old_reason = wt->lock_reason; +if (old_reason) { +if (*old_reason) +die (_ ("'%s' is already locked, reason: %s" ), + av[0 ], old_reason); +die (_ ("'%s' is already locked" ), av[0 ]); + } + +write_file (git_common_path ("worktrees/%s/locked" , wt->id), +"%s" , reason); +free_worktrees (worktrees); +return 0 ; +} +int cmd_worktree (int ac,const char **av,const char *prefix) {struct option options[] = {
@@ -475,5 +511,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)return prune (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"list" ))return list (ac -1 , av +1 , prefix); +if (!strcmp (av[1 ],"lock" )) +return lock_worktree (ac -1 , av +1 , prefix);usage_with_options (worktree_usage, options); }
contrib/completion/git-completion.bash +4 -1
@@ -2597,7 +2597,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list prune" + local subcommands="add list lock prune" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
@@ -2609,6 +2609,9 @@ _git_worktree () list,--*) __gitcomp "--porcelain" ;; + lock,--*) + __gitcomp "--reason" + ;; prune,--*) __gitcomp "--dry-run --expire --verbose" ;;
t/t2028-worktree-move.sh new file +48 -0
48 lines not on this page · raw message
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v3 5/6] worktree: add "lock" command
Quoted 28 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +13
-0@@ -14,6 +14,7 @@static const char *const worktree_usage[] = {N_ ("git worktree add [<options>] <path> [<branch>]" ),N_ ("git worktree list [<options>]" ), +N_ ("git worktree lock [<options>] <path>" ),N_ ("git worktree prune [<options>]" ),NULL };@@ -459,6 +460,41 @@ static int list(int ac, const char **av, const char *prefix)return 0 ; } +static int lock_worktree (int ac,const char **av,const char *prefix) +{ +const char *reason ="" , *old_reason; +struct option options[] = { +OPT_STRING (0 ,"reason" , &reason,N_ ("string" ), +N_ ("reason for locking" )), +OPT_END () + }; +struct worktree **worktrees, *wt; + + ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 )
builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 484
Quoted 10 lines
builtin/worktree.c +10
-0+usage_with_options (worktree_usage, options); + + worktrees =get_worktrees (); + wt =find_worktree (worktrees, prefix, av[0 ]); +if (!wt) +die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); + + old_reason = wt->lock_reason;
This use pattern suggests that the reading of lock_reason should be done lazily, doesn't it? The other user of the "is it locked?" information, which is builtin/worktree.c::prune_worktree(), does not even use the get_worktrees() interface and instead it just checks if the lock marker git_path("worktrees/*/locked") exists.
Perhaps you want one of these as a public interface:
int worktree_is_locked(const char *id, const char **reason); int worktree_is_locked(struct worktree *wt, const char **reason);
where the caller can learn if a worktree is locked, and optionally why?
Re: [PATCH v3 5/6] worktree: add "lock" command
In reply to Nguyễn Thái Ngọc DuyDocumentation/git-worktree.txt SYNOPSIS · line 15
Quoted 6 lines · Nguyễn Thái Ngọc Duy
Documentation/git-worktree.txt +1
-0@@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] 'git worktree list' [--porcelain] +'git worktree lock' [--reason <string>] <worktree> 'git worktree prune' [-n] [-v] [--expire <expire>]
This seems to be based on an unseen version that lists 'list' before 'prune' here and other places. I'd appreciate it if you either make sure a series that introduces a new feature applies cleanly to my tree before sending it out, or clarify to what state the series is designed to apply, when you send a patch series next time.
Documentation/git-worktree.txt OPTIONS · line 121
Quoted 5 lines
Documentation/git-worktree.txt +1
-0@@ -110,6 +118,15 @@ OPTIONS --expire <time>:: With `prune`, only expire unused working trees older than <time>. +--reason <string>:
Double-colon, I think (locally fixable).
PATCH v3 6/6 worktree: add "unlock" command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 5 +++++ builtin/worktree.c | 28 ++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 2 +- t/t2028-worktree-move.sh | 14 ++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +5 -0
@@ -13,6 +13,7 @@ SYNOPSIS 'git worktree list' [--porcelain] 'git worktree lock' [--reason <string>] <worktree> 'git worktree prune' [-n] [-v] [--expire <expire>] +'git worktree unlock' <worktree> DESCRIPTION -----------
@@ -73,6 +74,10 @@ prune:: Prune working tree information in $GIT_DIR/worktrees. +unlock:: + +Unlock a worktree, allowing it to be pruned, moved or deleted. + OPTIONS -------
builtin/worktree.c +28 -0
@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {N_ ("git worktree list [<options>]" ),N_ ("git worktree lock [<options>] <path>" ),N_ ("git worktree prune [<options>]" ), +N_ ("git worktree unlock <path>" ), NULL };
@@ -495,6 +496,31 @@ static int lock_worktree(int ac, const char **av, const char *prefix)return 0 ; } +static int unlock_worktree (int ac,const char **av,const char *prefix) +{ +struct option options[] = { +OPT_END () + }; +struct worktree **worktrees, *wt; +int ret; + + ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + + worktrees =get_worktrees (); + wt =find_worktree (worktrees, prefix, av[0 ]); +if (!wt) +die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); +if (!wt->lock_reason) +die (_ ("'%s' is not locked" ), av[0 ]); + ret =unlink_or_warn (git_common_path ("worktrees/%s/locked" , wt->id)); +free_worktrees (worktrees); +return ret; +} +int cmd_worktree (int ac,const char **av,const char *prefix) {struct option options[] = {
@@ -513,5 +539,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)return list (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"lock" ))return lock_worktree (ac -1 , av +1 , prefix); +if (!strcmp (av[1 ],"unlock" )) +return unlock_worktree (ac -1 , av +1 , prefix);usage_with_options (worktree_usage, options); }
contrib/completion/git-completion.bash +1 -1
@@ -2597,7 +2597,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list lock prune" + local subcommands="add list lock prune unlock" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
t/t2028-worktree-move.sh +14 -0
@@ -45,4 +45,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' ' test_cmp expected .git/worktrees/source/locked ' +test_expect_success 'unlock main worktree' ' + test_must_fail git worktree unlock . +' + +test_expect_success 'unlock linked worktree' ' + git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + +test_expect_success 'unlock worktree twice' ' + test_must_fail git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + test_done
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v3 6/6] worktree: add "unlock" command
Quoted 13 lines · Nguyễn Thái Ngọc Duy
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>--- Documentation/git-worktree.txt | 5 +++++ builtin/worktree.c | 28 ++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 2 +- t/t2028-worktree-move.sh | 14 ++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-)Documentation/git-worktree.txt +0
-0
Documentation/git-worktree.txt SYNOPSIS · line 16
Quoted 4 lines
Documentation/git-worktree.txt +1
-0@@ -13,6 +13,7 @@ SYNOPSIS 'git worktree list' [--porcelain] 'git worktree lock' [--reason <string>] <worktree> 'git worktree prune' [-n] [-v] [--expire <expire>] +'git worktree unlock' <worktree>
Again, this seems to be built on an unseen version. I think I fixed these up (and also the "missing second colon in the doc"), but please double check what I push out later today.
Thanks.
Re: [PATCH v3 6/6] worktree: add "unlock" command
Quoted 3 lines · Junio C Hamano
Again, this seems to be built on an unseen version. I think I fixed these up (and also the "missing second colon in the doc"), but please double check what I push out later today.
Ah, this comes on top of 0409e0b6 (worktree: simplify prefixing paths, 2016-05-22). I do not necessarily agree with 7b722d9 (I think "add, list, prune followed by lock, unlock" would be a more sensible order), but I'll leave them as-is.
Thanks. Queued.
Re: [PATCH v3 6/6] worktree: add "unlock" command
In reply to Nguyễn Thái Ngọc Duybuiltin/worktree.c static int lock_worktree(int ac, const char **av, const char *prefix) · line 518
Quoted 4 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +4
-0+if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); +if (!wt->lock_reason) +die (_ ("'%s' is not locked" ), av[0 ]);
Exactly the same comment about the lack of need for lock_reason field as 5/6 appiles here. Also, as "lock" does not allow you to lock the primary tree, do you even need is_main_worktree() check?
That is:
if (!worktree_is_locked(wt, &reason))
die(_("'%s' is not locked), av[0]);should be sufficient, no?
Re: [PATCH v3 6/6] worktree: add "unlock" command
Quoted 11 lines · Junio C Hamano
builtin/worktree.c static int lock_worktree(int ac, const char **av, const char *prefix) · line 518
Quoted 4 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +4
-0+if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); +if (!wt->lock_reason) +die (_ ("'%s' is not locked" ), av[0 ]);Exactly the same comment about the lack of need for
lock_reasonfield as 5/6 appiles here. Also, as "lock" does not allow you to lock the primary tree, do you even needis_main_worktree()check?
Quoted 6 lines
That is:
if (!worktree_is_locked(wt, &reason))
die(_("'%s' is not locked), av[0]);should be sufficient, no?
It crossed my mind but I went for separate checks anyway because it gives better explanation.
Assigning either lock or unlock status to main worktree does not work well either. If you assume worktree is always unlocked, you still need to check main worktree in lock, move and remove as all of them would only proceed if it's an unlocked linked worktree. If you assume the opposite, you need is_main_worktree() check here and "worktree list" may print the confusing "locked" status on main worktree (I assume we may add that later).
Signature · 1 line
Duy
v4 cover worktree lock/unlock
In reply to Nguyễn Thái Ngọc Duyv4
- fixes some documentation bugs in v3
- updates is_main_worktree commit message a bit
- brings back is_worktree_locked() as API with value lazily obtained and cached in worktree->lock_reason and fixes a memory leak
- extends basename search to path suffix search. I moved this patch to the bottom so that the relevant documentation part is included in the patch (previously it was part of the "add lock command" patch).
This series is based on nd/worktree-cleanup-post-head-protection
Nguyễn Thái Ngọc Duy (6):
worktree.c: add find_worktree() worktree.c: add is_main_worktree() worktree.c: add is_worktree_locked()
worktree: add "lock" command
worktree: add "unlock" command worktree.c: find_worktree() search by path suffix
Documentation/git-worktree.txt | 36 +++++++++++++--- builtin/worktree.c | 66 ++++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 ++- t/t2028-worktree-move.sh (new +x) | 62 ++++++++++++++++++++++++++++ worktree.c | 75 ++++++++++++++++++++++++++++++++++ worktree.h | 20 +++++++++ 6 files changed, 257 insertions(+), 7 deletions(-) create mode 100755 t/t2028-worktree-move.sh
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v4 1/6 worktree.c: add find_worktree()
So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. The current implementation identifies worktrees by path (*). In future, the function could learn to identify by $(basename $path) or tags...
(*) We could probably go cheaper with comparing inode number (and probably more reliable than paths when unicode enters the game). But not all systems have good inode that so let's stick to something simple for now.
--- worktree.c | 15 +++++++++++++++ worktree.h | 8 ++++++++ 2 files changed, 23 insertions(+)
worktree.c +15 -0
@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +struct worktree *find_worktree (struct worktree **list, +const char *prefix, +const char *arg) +{ +char *path; + + arg =prefix_filename (prefix,strlen (prefix), arg); + path =xstrdup (real_path (arg)); +for (; *list; list++) +if (!fspathcmp (path,real_path ((*list)->path))) +break ; +free (path); +return *list; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +8 -0
@@ -29,6 +29,14 @@ extern struct worktree **get_worktrees(void);*/ extern const char *get_worktree_git_dir (const struct worktree *wt); +/* +* Search a worktree that can be unambiguously identified by +* "arg". "prefix" must not be NULL. +*/ +extern struct worktree *find_worktree (struct worktree **list, +const char *prefix, +const char *arg); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v4 1/6] worktree.c: add find_worktree()
Quoted 25 lines · Nguyễn Thái Ngọc Duy
So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. The current implementation identifies worktrees by path (*). In future, the function could learn to identify by $(basename $path) or tags...
(*) We could probably go cheaper with comparing inode number (and probably more reliable than paths when unicode enters the game). But not all systems have good inode that so let's stick to something simple for now.
Helped-by Eric Sunshine <[email protected]>Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>--- worktree.c | 15 +++++++++++++++ worktree.h | 8 ++++++++ 2 files changed, 23 insertions(+)worktree.c +0
-0@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); }
worktree.c const char *get_worktree_git_dir(const struct worktree *wt) · line 226
Quoted 10 lines
worktree.c +10
-0+struct worktree *find_worktree (struct worktree **list, +const char *prefix, +const char *arg) +{ +char *path; + + arg =prefix_filename (prefix,strlen (prefix), arg); + path =xstrdup (real_path (arg)); +for (; *list; list++) +if (!fspathcmp (path,real_path ((*list)->path)))
The results of the call to real_path() should probably be cached in the worktree structure, since real_path() is relatively expensive (it calls chdir(), lstat(), readlink() etc.), so you don't want to re-compute the same result time-after-time ...
worktree.c const char *get_worktree_git_dir(const struct worktree *wt) · line 231
Quoted 5 lines
worktree.c +5
-0+break ; +free (path); +return *list; +} +
ATB,
Ramsay Jones
Re: [PATCH v4 1/6] worktree.c: add find_worktree()
Quoted 28 lines · Ramsay Jones
Quoted 25 lines · Nguyễn Thái Ngọc Duy
So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. The current implementation identifies worktrees by path (*). In future, the function could learn to identify by $(basename $path) or tags...
(*) We could probably go cheaper with comparing inode number (and probably more reliable than paths when unicode enters the game). But not all systems have good inode that so let's stick to something simple for now.
Helped-by Eric Sunshine <[email protected]>Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>--- worktree.c | 15 +++++++++++++++ worktree.h | 8 ++++++++ 2 files changed, 23 insertions(+)worktree.c +0
-0@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); }
Quoted 15 lines
worktree.c const char *get_worktree_git_dir(const struct worktree *wt) · line 226
Quoted 10 lines
worktree.c +10
-0+struct worktree *find_worktree (struct worktree **list, +const char *prefix, +const char *arg) +{ +char *path; + + arg =prefix_filename (prefix,strlen (prefix), arg); + path =xstrdup (real_path (arg)); +for (; *list; list++) +if (!fspathcmp (path,real_path ((*list)->path)))The results of the call to
real_path()should probably be cached in the worktree structure, sincereal_path()is relatively expensive (it callschdir(),lstat(),readlink()etc.), so you don't want to re-compute the same result time-after-time ...
Urgh.. I missed this after sending out v5. Because find_worktree is probably called once or twice per process, I don't think we need to optimize this yet. If nr. worktrees goes up to hundreds then this is one of many items we need to do to make worktree list fast.
Signature · 1 line
Duy
PATCH v4 2/6 worktree.c: add is_main_worktree()
In reply to Nguyễn Thái Ngọc DuyMain worktree _is_ different. You can lock (*) a linked worktree but not the main one, for example. Provide an API for checking that.
(*) Add the file $GIT_DIR/worktrees/xxx/locked to avoid worktree xxx from being removed or moved.
--- worktree.c | 5 +++++ worktree.h | 5 +++++ 2 files changed, 10 insertions(+)
worktree.c +5 -0
@@ -229,6 +229,11 @@ struct worktree *find_worktree(struct worktree **list,return *list; } +int is_main_worktree (const struct worktree *wt) +{ +return !wt->id; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +5 -0
@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,const char *prefix, const char *arg); +/* +* Return true if the given worktree is the main one. +*/ +extern int is_main_worktree (const struct worktree *wt); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v4 3/6 worktree.c: add is_worktree_locked()
In reply to Nguyễn Thái Ngọc DuyWe need this later to avoid double locking a worktree, or unlocking one when it's not even locked.
--- worktree.c | 26 ++++++++++++++++++++++++++ worktree.h | 7 +++++++ 2 files changed, 33 insertions(+)
worktree.c +26 -0
@@ -5,6 +5,8 @@#include "dir.h" #include "wt-status.h" +static const char *lock_field_uninitialized ="value is not important" ; +void free_worktrees (struct worktree **worktrees) {int i =0 ;
@@ -13,6 +15,8 @@ void free_worktrees(struct worktree **worktrees)free (worktrees[i]->path);free (worktrees[i]->id);free (worktrees[i]->head_ref); +if (worktrees[i]->lock_reason != lock_field_uninitialized) +free (worktrees[i]->lock_reason);free (worktrees[i]); }free (worktrees);
@@ -98,6 +102,7 @@ static struct worktree *get_main_worktree(void) worktree->is_detached = is_detached; worktree->is_current =0 ;add_head_info (&head_ref, worktree); + worktree->lock_reason = (char *)lock_field_uninitialized; done:strbuf_release (&path);
@@ -143,6 +148,7 @@ static struct worktree *get_linked_worktree(const char *id) worktree->is_detached = is_detached; worktree->is_current =0 ;add_head_info (&head_ref, worktree); + worktree->lock_reason = (char *)lock_field_uninitialized; done:strbuf_release (&path);
@@ -234,6 +240,26 @@ int is_main_worktree(const struct worktree *wt)return !wt->id; } +const char *is_worktree_locked (struct worktree *wt) +{ +if (wt->lock_reason == lock_field_uninitialized) { +struct strbuf path =STRBUF_INIT ; + +strbuf_addstr (&path,worktree_git_path (wt,"locked" )); +if (file_exists (path.buf)) { +struct strbuf lock_reason =STRBUF_INIT ; +if (strbuf_read_file (&lock_reason, path.buf,0 ) <0 ) +die_errno (_ ("failed to read '%s'" ), path.buf); +strbuf_trim (&lock_reason); + wt->lock_reason =strbuf_detach (&lock_reason,NULL ); + }else + wt->lock_reason =NULL ; +strbuf_release (&path); + } + +return wt->lock_reason; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +7 -0
@@ -5,6 +5,7 @@ struct worktree {char *path;char *id;char *head_ref; +char *lock_reason;/* internal use */ unsigned char head_sha1[20 ];int is_detached;int is_bare;
@@ -42,6 +43,12 @@ extern struct worktree *find_worktree(struct worktree **list,*/ extern int is_main_worktree (const struct worktree *wt); +/* +* Return the reason string if the given worktree is locked or NULL +* otherwise. +*/ +extern const char *is_worktree_locked (struct worktree *wt); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v4 4/6 worktree: add "lock" command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 26 +++++++++++++----- builtin/worktree.c | 38 +++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 +++- t/t2028-worktree-move.sh (new +x) | 48 ++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 7 deletions(-) create mode 100755 t/t2028-worktree-move.sh
Documentation/git-worktree.txt +20 -6
@@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] 'git worktree list' [--porcelain] +'git worktree lock' [--reason <string>] <worktree> 'git worktree prune' [-n] [-v] [--expire <expire>] DESCRIPTION
@@ -38,9 +39,8 @@ section "DETAILS" for more information. If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from -being pruned by creating a file named 'locked' alongside the other -administrative files, optionally containing a plain text reason that -pruning should be suppressed. See section "DETAILS" for more information. +being pruned by issuing the `git worktree lock` command, optionally +specifying `--reason` to explain why the working tree is locked. COMMANDS --------
@@ -62,6 +62,14 @@ each of the linked worktrees. The output details include if the worktree is bare, the revision currently checked out, and the branch currently checked out (or 'detached HEAD' if none). +lock:: + +If a working tree is on a portable device or network share which +is not always mounted, lock it to prevent its administrative +files from being pruned automatically. This also prevents it from +being moved or deleted. Optionally, specify a reason for the lock +with `--reason`. + prune:: Prune working tree information in $GIT_DIR/worktrees.
@@ -111,6 +119,13 @@ OPTIONS --expire <time>:: With `prune`, only expire unused working trees older than <time>. +--reason <string>:: + With `lock`, an explanation why the working tree is locked. + +<worktree>:: + Working trees can be identified by path, either relative or + absolute. + DETAILS ------- Each linked working tree has a private sub-directory in the repository's
@@ -151,7 +166,8 @@ instead. To prevent a $GIT_DIR/worktrees entry from being pruned (which can be useful in some situations, such as when the -entry's working tree is stored on a portable device), add a file named +entry's working tree is stored on a portable device), use the +`git worktree lock` command, which adds a file named 'locked' to the entry's directory. The file contains the reason in plain text. For example, if a linked working tree's `.git` file points to `/path/main/.git/worktrees/test-next` then a file named
@@ -227,8 +243,6 @@ performed manually, such as: - `remove` to remove a linked working tree and its administrative files (and warn if the working tree is dirty) - `mv` to move or rename a working tree and update its administrative files -- `lock` to prevent automatic pruning of administrative files (for instance, - for a working tree on a portable device) GIT ---
builtin/worktree.c +38 -0
@@ -14,6 +14,7 @@static const char *const worktree_usage[] = {N_ ("git worktree add [<options>] <path> [<branch>]" ),N_ ("git worktree list [<options>]" ), +N_ ("git worktree lock [<options>] <path>" ),N_ ("git worktree prune [<options>]" ),NULL };
@@ -462,6 +463,41 @@ static int list(int ac, const char **av, const char *prefix)return 0 ; } +static int lock_worktree (int ac,const char **av,const char *prefix) +{ +const char *reason ="" , *old_reason; +struct option options[] = { +OPT_STRING (0 ,"reason" , &reason,N_ ("string" ), +N_ ("reason for locking" )), +OPT_END () + }; +struct worktree **worktrees, *wt; + + ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + + worktrees =get_worktrees (); + wt =find_worktree (worktrees, prefix, av[0 ]); +if (!wt) +die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); + + old_reason =is_worktree_locked (wt); +if (old_reason) { +if (*old_reason) +die (_ ("'%s' is already locked, reason: %s" ), + av[0 ], old_reason); +die (_ ("'%s' is already locked" ), av[0 ]); + } + +write_file (git_common_path ("worktrees/%s/locked" , wt->id), +"%s" , reason); +free_worktrees (worktrees); +return 0 ; +} +int cmd_worktree (int ac,const char **av,const char *prefix) {struct option options[] = {
@@ -478,5 +514,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)return prune (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"list" ))return list (ac -1 , av +1 , prefix); +if (!strcmp (av[1 ],"lock" )) +return lock_worktree (ac -1 , av +1 , prefix);usage_with_options (worktree_usage, options); }
contrib/completion/git-completion.bash +4 -1
@@ -2598,7 +2598,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list prune" + local subcommands="add list lock prune" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
@@ -2610,6 +2610,9 @@ _git_worktree () list,--*) __gitcomp "--porcelain" ;; + lock,--*) + __gitcomp "--reason" + ;; prune,--*) __gitcomp "--dry-run --expire --verbose" ;;
t/t2028-worktree-move.sh new file +48 -0
48 lines not on this page · raw message
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v4 5/6 worktree: add "unlock" command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 5 +++++ builtin/worktree.c | 28 ++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 2 +- t/t2028-worktree-move.sh | 14 ++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +5 -0
@@ -13,6 +13,7 @@ SYNOPSIS 'git worktree list' [--porcelain] 'git worktree lock' [--reason <string>] <worktree> 'git worktree prune' [-n] [-v] [--expire <expire>] +'git worktree unlock' <worktree> DESCRIPTION -----------
@@ -74,6 +75,10 @@ prune:: Prune working tree information in $GIT_DIR/worktrees. +unlock:: + +Unlock a working tree, allowing it to be pruned, moved or deleted. + OPTIONS -------
builtin/worktree.c +28 -0
@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {N_ ("git worktree list [<options>]" ),N_ ("git worktree lock [<options>] <path>" ),N_ ("git worktree prune [<options>]" ), +N_ ("git worktree unlock <path>" ), NULL };
@@ -498,6 +499,31 @@ static int lock_worktree(int ac, const char **av, const char *prefix)return 0 ; } +static int unlock_worktree (int ac,const char **av,const char *prefix) +{ +struct option options[] = { +OPT_END () + }; +struct worktree **worktrees, *wt; +int ret; + + ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + + worktrees =get_worktrees (); + wt =find_worktree (worktrees, prefix, av[0 ]); +if (!wt) +die (_ ("'%s' is not a working directory" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("'%s' is a main working directory" ), av[0 ]); +if (!is_worktree_locked (wt)) +die (_ ("'%s' is not locked" ), av[0 ]); + ret =unlink_or_warn (git_common_path ("worktrees/%s/locked" , wt->id)); +free_worktrees (worktrees); +return ret; +} +int cmd_worktree (int ac,const char **av,const char *prefix) {struct option options[] = {
@@ -516,5 +542,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)return list (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"lock" ))return lock_worktree (ac -1 , av +1 , prefix); +if (!strcmp (av[1 ],"unlock" )) +return unlock_worktree (ac -1 , av +1 , prefix);usage_with_options (worktree_usage, options); }
contrib/completion/git-completion.bash +1 -1
@@ -2598,7 +2598,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list lock prune" + local subcommands="add list lock prune unlock" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
t/t2028-worktree-move.sh +14 -0
@@ -45,4 +45,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' ' test_cmp expected .git/worktrees/source/locked ' +test_expect_success 'unlock main worktree' ' + test_must_fail git worktree unlock . +' + +test_expect_success 'unlock linked worktree' ' + git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + +test_expect_success 'unlock worktree twice' ' + test_must_fail git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + test_done
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v4 6/6 worktree.c: find_worktree() search by path suffix
In reply to Nguyễn Thái Ngọc DuyThis allows the user to do something like "worktree lock foo" or "worktree lock to/foo" instead of "worktree lock /long/path/to/foo" if it's unambiguous. With completion support it could be quite convenient.
--- Documentation/git-worktree.txt | 5 +++++ worktree.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+)
Documentation/git-worktree.txt +5 -0
@@ -130,6 +130,11 @@ OPTIONS <worktree>:: Working trees can be identified by path, either relative or absolute. ++ +If the last path components in the working tree's path is unique among +working trees, it can be used to identify worktrees. For example if +you only have to working trees at "/abc/def/ghi" and "/abc/def/ggg", +then "ghi" or "def/ghi" is enough to point to the former working tree. DETAILS -------
worktree.c +29 -0
@@ -220,12 +220,41 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +static struct worktree *find_worktree_by_suffix (struct worktree **list, +const char *suffix) +{ +struct worktree *found =NULL ; +int nr_found =0 , suffixlen; + + suffixlen =strlen (suffix); +if (!suffixlen) +return NULL ; + +for (; *list && nr_found <2 ; list++) { +const char *path = (*list)->path; +int pathlen =strlen (path); +int start = pathlen - suffixlen; + +/* suffix must start at directory boundary */ +if ((!start || (start >0 &&is_dir_sep (path[start -1 ]))) && + !fspathcmp (suffix, path + start)) { + found = *list; + nr_found++; + } + } +return nr_found ==1 ? found :NULL ; +} +struct worktree *find_worktree (struct worktree **list,const char *prefix,const char *arg) { +struct worktree *wt;char *path; +if ((wt =find_worktree_by_suffix (list, arg))) +return wt; + arg =prefix_filename (prefix,strlen (prefix), arg); path =xstrdup (real_path (arg));for (; *list; list++)
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v4 0/6] worktree lock/unlock
In reply to Nguyễn Thái Ngọc DuyQuoted 9 lines · Nguyễn Thái Ngọc Duy
v4
- fixes some documentation bugs in v3
- updates
is_main_worktreecommit message a bit- brings back
is_worktree_locked()as API with value lazily obtained and cached inworktree->lock_reasonand fixes a memory leak
Quoted 6 lines
- extends basename search to path suffix search. I moved this patch to the bottom so that the relevant documentation part is included in the patch (previously it was part of the "add lock command" patch).
This series is based on nd/worktree-cleanup-post-head-protection
Will replace the one in 'pu'. A few things I noticed:
* Documentation and messages talk about "working tree" and "working directory" when they refer to "worktree", which is not a normal "working tree". I think this was done as an attempt to avoid being too precise and technical in phrasing, but I suspect that is a false reader-friendliness. At least, "working directory" in messages should be "worktree", e.g.
wt = find_worktree(...);
if (!wt)
die("'%s' is not a working directory", av[0]);does not make much sense (imagine "git wt lock ." and being told that '.' is not a working directory).
* You are probably trying to be frugal by not introducing a separate bit for is_lock_checked to struct worktre and instead using a sentinel value for lock_reason, but I think that is a false economy, after seeing that you have to cast away constness in number of places and having to be extra careful not to free that sentinel value.
Thanks.
v5 cover worktree lock/unlock
In reply to Nguyễn Thái Ngọc Duyv5 fixes some error messages mentioning "working directory" instead of "working tree" and split the double use of "lock_reason" field in "struct worktree". This series depends on nd/worktree-cleanup-post-head-protection.
Diff from v4
-- 8< --
builtin/worktree.c +4 -4
@@ -478,9 +478,9 @@ static int lock_worktree(int ac, const char **av, const char *prefix) worktrees =get_worktrees (); wt =find_worktree (worktrees, prefix, av[0 ]);if (!wt) -die (_ ("'%s' is not a working directory" ), av[0 ]); +die (_ ("'%s' is not a working tree" ), av[0 ]);if (is_main_worktree (wt)) -die (_ ("'%s' is a main working directory" ), av[0 ]); +die (_ ("The main working tree cannot be locked or unlocked" )); old_reason =is_worktree_locked (wt);if (old_reason) {
@@ -511,9 +511,9 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) worktrees =get_worktrees (); wt =find_worktree (worktrees, prefix, av[0 ]);if (!wt) -die (_ ("'%s' is not a working directory" ), av[0 ]); +die (_ ("'%s' is not a working tree" ), av[0 ]);if (is_main_worktree (wt)) -die (_ ("'%s' is a main working directory" ), av[0 ]); +die (_ ("The main working tree cannot be locked or unlocked" ));if (!is_worktree_locked (wt))die (_ ("'%s' is not locked" ), av[0 ]); ret =unlink_or_warn (git_common_path ("worktrees/%s/locked" , wt->id));
worktree.c +9 -7
@@ -5,8 +5,6 @@#include "dir.h" #include "wt-status.h" -static const char *lock_field_uninitialized ="value is not important" ; -void free_worktrees (struct worktree **worktrees) {int i =0 ;
@@ -15,8 +13,7 @@ void free_worktrees(struct worktree **worktrees)free (worktrees[i]->path);free (worktrees[i]->id);free (worktrees[i]->head_ref); -if (worktrees[i]->lock_reason != lock_field_uninitialized) -free (worktrees[i]->lock_reason); +free (worktrees[i]->lock_reason);free (worktrees[i]); }free (worktrees);
@@ -102,7 +99,8 @@ static struct worktree *get_main_worktree(void) worktree->is_detached = is_detached; worktree->is_current =0 ;add_head_info (&head_ref, worktree); - worktree->lock_reason = (char *)lock_field_uninitialized; + worktree->lock_reason =NULL ; + worktree->lock_reason_valid =0 ; done:strbuf_release (&path);
@@ -148,7 +146,8 @@ static struct worktree *get_linked_worktree(const char *id) worktree->is_detached = is_detached; worktree->is_current =0 ;add_head_info (&head_ref, worktree); - worktree->lock_reason = (char *)lock_field_uninitialized; + worktree->lock_reason =NULL ; + worktree->lock_reason_valid =0 ; done:strbuf_release (&path);
@@ -271,7 +270,9 @@ int is_main_worktree(const struct worktree *wt)const char *is_worktree_locked (struct worktree *wt) { -if (wt->lock_reason == lock_field_uninitialized) { +assert (!is_main_worktree (wt)); + +if (!wt->lock_reason_valid) {struct strbuf path =STRBUF_INIT ;strbuf_addstr (&path,worktree_git_path (wt,"locked" ));
@@ -283,6 +284,7 @@ const char *is_worktree_locked(struct worktree *wt) wt->lock_reason =strbuf_detach (&lock_reason,NULL ); }else wt->lock_reason =NULL ; + wt->lock_reason_valid =1 ;strbuf_release (&path); }
worktree.h +1 -0
@@ -10,6 +10,7 @@ struct worktree {int is_detached;int is_bare;int is_current; +int lock_reason_valid; };/* Functions for acting on the information about worktrees. */
-- >8 --
Nguyễn Thái Ngọc Duy (6):
worktree.c: add find_worktree() worktree.c: add is_main_worktree() worktree.c: add is_worktree_locked()
worktree: add "lock" command
worktree: add "unlock" command worktree.c: find_worktree() search by path suffix
Documentation/git-worktree.txt | 36 +++++++++++++--- builtin/worktree.c | 66 +++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 ++- t/t2028-worktree-move.sh (new +x) | 62 +++++++++++++++++++++++++++ worktree.c | 77 ++++++++++++++++++++++++++++++++++ worktree.h | 21 ++++++++++ 6 files changed, 260 insertions(+), 7 deletions(-) create mode 100755 t/t2028-worktree-move.sh
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v5 1/6 worktree.c: add find_worktree()
So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. The current implementation identifies worktrees by path (*). In future, the function could learn to identify by $(basename $path) or tags...
(*) We could probably go cheaper with comparing inode number (and probably more reliable than paths when unicode enters the game). But not all systems have good inode that so let's stick to something simple for now.
--- worktree.c | 15 +++++++++++++++ worktree.h | 8 ++++++++ 2 files changed, 23 insertions(+)
worktree.c +15 -0
@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +struct worktree *find_worktree (struct worktree **list, +const char *prefix, +const char *arg) +{ +char *path; + + arg =prefix_filename (prefix,strlen (prefix), arg); + path =xstrdup (real_path (arg)); +for (; *list; list++) +if (!fspathcmp (path,real_path ((*list)->path))) +break ; +free (path); +return *list; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +8 -0
@@ -29,6 +29,14 @@ extern struct worktree **get_worktrees(void);*/ extern const char *get_worktree_git_dir (const struct worktree *wt); +/* +* Search a worktree that can be unambiguously identified by +* "arg". "prefix" must not be NULL. +*/ +extern struct worktree *find_worktree (struct worktree **list, +const char *prefix, +const char *arg); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v5 2/6 worktree.c: add is_main_worktree()
In reply to Nguyễn Thái Ngọc DuyMain worktree _is_ different. You can lock (*) a linked worktree but not the main one, for example. Provide an API for checking that.
(*) Add the file $GIT_DIR/worktrees/xxx/locked to avoid worktree xxx from being removed or moved.
--- worktree.c | 5 +++++ worktree.h | 5 +++++ 2 files changed, 10 insertions(+)
worktree.c +5 -0
@@ -229,6 +229,11 @@ struct worktree *find_worktree(struct worktree **list,return *list; } +int is_main_worktree (const struct worktree *wt) +{ +return !wt->id; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +5 -0
@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,const char *prefix, const char *arg); +/* +* Return true if the given worktree is the main one. +*/ +extern int is_main_worktree (const struct worktree *wt); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v5 3/6 worktree.c: add is_worktree_locked()
In reply to Nguyễn Thái Ngọc DuyWe need this later to avoid double locking a worktree, or unlocking one when it's not even locked.
--- worktree.c | 28 ++++++++++++++++++++++++++++ worktree.h | 8 ++++++++ 2 files changed, 36 insertions(+)
worktree.c +28 -0
@@ -13,6 +13,7 @@ void free_worktrees(struct worktree **worktrees)free (worktrees[i]->path);free (worktrees[i]->id);free (worktrees[i]->head_ref); +free (worktrees[i]->lock_reason);free (worktrees[i]); }free (worktrees);
@@ -98,6 +99,8 @@ static struct worktree *get_main_worktree(void) worktree->is_detached = is_detached; worktree->is_current =0 ;add_head_info (&head_ref, worktree); + worktree->lock_reason =NULL ; + worktree->lock_reason_valid =0 ; done:strbuf_release (&path);
@@ -143,6 +146,8 @@ static struct worktree *get_linked_worktree(const char *id) worktree->is_detached = is_detached; worktree->is_current =0 ;add_head_info (&head_ref, worktree); + worktree->lock_reason =NULL ; + worktree->lock_reason_valid =0 ; done:strbuf_release (&path);
@@ -234,6 +239,29 @@ int is_main_worktree(const struct worktree *wt)return !wt->id; } +const char *is_worktree_locked (struct worktree *wt) +{ +assert (!is_main_worktree (wt)); + +if (!wt->lock_reason_valid) { +struct strbuf path =STRBUF_INIT ; + +strbuf_addstr (&path,worktree_git_path (wt,"locked" )); +if (file_exists (path.buf)) { +struct strbuf lock_reason =STRBUF_INIT ; +if (strbuf_read_file (&lock_reason, path.buf,0 ) <0 ) +die_errno (_ ("failed to read '%s'" ), path.buf); +strbuf_trim (&lock_reason); + wt->lock_reason =strbuf_detach (&lock_reason,NULL ); + }else + wt->lock_reason =NULL ; + wt->lock_reason_valid =1 ; +strbuf_release (&path); + } + +return wt->lock_reason; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +8 -0
@@ -5,10 +5,12 @@ struct worktree {char *path;char *id;char *head_ref; +char *lock_reason;/* internal use */ unsigned char head_sha1[20 ];int is_detached;int is_bare;int is_current; +int lock_reason_valid; };/* Functions for acting on the information about worktrees. */
@@ -42,6 +44,12 @@ extern struct worktree *find_worktree(struct worktree **list,*/ extern int is_main_worktree (const struct worktree *wt); +/* +* Return the reason string if the given worktree is locked or NULL +* otherwise. +*/ +extern const char *is_worktree_locked (struct worktree *wt); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v5 4/6 worktree: add "lock" command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 26 +++++++++++++----- builtin/worktree.c | 38 +++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 +++- t/t2028-worktree-move.sh (new +x) | 48 ++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 7 deletions(-) create mode 100755 t/t2028-worktree-move.sh
Documentation/git-worktree.txt +20 -6
@@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] 'git worktree list' [--porcelain] +'git worktree lock' [--reason <string>] <worktree> 'git worktree prune' [-n] [-v] [--expire <expire>] DESCRIPTION
@@ -38,9 +39,8 @@ section "DETAILS" for more information. If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from -being pruned by creating a file named 'locked' alongside the other -administrative files, optionally containing a plain text reason that -pruning should be suppressed. See section "DETAILS" for more information. +being pruned by issuing the `git worktree lock` command, optionally +specifying `--reason` to explain why the working tree is locked. COMMANDS --------
@@ -61,6 +61,14 @@ each of the linked worktrees. The output details include if the worktree is bare, the revision currently checked out, and the branch currently checked out (or 'detached HEAD' if none). +lock:: + +If a working tree is on a portable device or network share which +is not always mounted, lock it to prevent its administrative +files from being pruned automatically. This also prevents it from +being moved or deleted. Optionally, specify a reason for the lock +with `--reason`. + prune:: Prune working tree information in $GIT_DIR/worktrees.
@@ -110,6 +118,13 @@ OPTIONS --expire <time>:: With `prune`, only expire unused working trees older than <time>. +--reason <string>:: + With `lock`, an explanation why the working tree is locked. + +<worktree>:: + Working trees can be identified by path, either relative or + absolute. + DETAILS ------- Each linked working tree has a private sub-directory in the repository's
@@ -150,7 +165,8 @@ instead. To prevent a $GIT_DIR/worktrees entry from being pruned (which can be useful in some situations, such as when the -entry's working tree is stored on a portable device), add a file named +entry's working tree is stored on a portable device), use the +`git worktree lock` command, which adds a file named 'locked' to the entry's directory. The file contains the reason in plain text. For example, if a linked working tree's `.git` file points to `/path/main/.git/worktrees/test-next` then a file named
@@ -226,8 +242,6 @@ performed manually, such as: - `remove` to remove a linked working tree and its administrative files (and warn if the working tree is dirty) - `mv` to move or rename a working tree and update its administrative files -- `lock` to prevent automatic pruning of administrative files (for instance, - for a working tree on a portable device) GIT ---
builtin/worktree.c +38 -0
@@ -14,6 +14,7 @@static const char *const worktree_usage[] = {N_ ("git worktree add [<options>] <path> [<branch>]" ),N_ ("git worktree list [<options>]" ), +N_ ("git worktree lock [<options>] <path>" ),N_ ("git worktree prune [<options>]" ),NULL };
@@ -459,6 +460,41 @@ static int list(int ac, const char **av, const char *prefix)return 0 ; } +static int lock_worktree (int ac,const char **av,const char *prefix) +{ +const char *reason ="" , *old_reason; +struct option options[] = { +OPT_STRING (0 ,"reason" , &reason,N_ ("string" ), +N_ ("reason for locking" )), +OPT_END () + }; +struct worktree **worktrees, *wt; + + ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + + worktrees =get_worktrees (); + wt =find_worktree (worktrees, prefix, av[0 ]); +if (!wt) +die (_ ("'%s' is not a working tree" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("The main working tree cannot be locked or unlocked" )); + + old_reason =is_worktree_locked (wt); +if (old_reason) { +if (*old_reason) +die (_ ("'%s' is already locked, reason: %s" ), + av[0 ], old_reason); +die (_ ("'%s' is already locked" ), av[0 ]); + } + +write_file (git_common_path ("worktrees/%s/locked" , wt->id), +"%s" , reason); +free_worktrees (worktrees); +return 0 ; +} +int cmd_worktree (int ac,const char **av,const char *prefix) {struct option options[] = {
@@ -475,5 +511,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)return prune (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"list" ))return list (ac -1 , av +1 , prefix); +if (!strcmp (av[1 ],"lock" )) +return lock_worktree (ac -1 , av +1 , prefix);usage_with_options (worktree_usage, options); }
contrib/completion/git-completion.bash +4 -1
@@ -2597,7 +2597,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list prune" + local subcommands="add list lock prune" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
@@ -2609,6 +2609,9 @@ _git_worktree () list,--*) __gitcomp "--porcelain" ;; + lock,--*) + __gitcomp "--reason" + ;; prune,--*) __gitcomp "--dry-run --expire --verbose" ;;
t/t2028-worktree-move.sh new file +48 -0
48 lines not on this page · raw message
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v5 5/6 worktree: add "unlock" command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 5 +++++ builtin/worktree.c | 28 ++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 2 +- t/t2028-worktree-move.sh | 14 ++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +5 -0
@@ -13,6 +13,7 @@ SYNOPSIS 'git worktree list' [--porcelain] 'git worktree lock' [--reason <string>] <worktree> 'git worktree prune' [-n] [-v] [--expire <expire>] +'git worktree unlock' <worktree> DESCRIPTION -----------
@@ -73,6 +74,10 @@ prune:: Prune working tree information in $GIT_DIR/worktrees. +unlock:: + +Unlock a working tree, allowing it to be pruned, moved or deleted. + OPTIONS -------
builtin/worktree.c +28 -0
@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {N_ ("git worktree list [<options>]" ),N_ ("git worktree lock [<options>] <path>" ),N_ ("git worktree prune [<options>]" ), +N_ ("git worktree unlock <path>" ), NULL };
@@ -495,6 +496,31 @@ static int lock_worktree(int ac, const char **av, const char *prefix)return 0 ; } +static int unlock_worktree (int ac,const char **av,const char *prefix) +{ +struct option options[] = { +OPT_END () + }; +struct worktree **worktrees, *wt; +int ret; + + ac =parse_options (ac, av, prefix, options, worktree_usage,0 ); +if (ac !=1 ) +usage_with_options (worktree_usage, options); + + worktrees =get_worktrees (); + wt =find_worktree (worktrees, prefix, av[0 ]); +if (!wt) +die (_ ("'%s' is not a working tree" ), av[0 ]); +if (is_main_worktree (wt)) +die (_ ("The main working tree cannot be locked or unlocked" )); +if (!is_worktree_locked (wt)) +die (_ ("'%s' is not locked" ), av[0 ]); + ret =unlink_or_warn (git_common_path ("worktrees/%s/locked" , wt->id)); +free_worktrees (worktrees); +return ret; +} +int cmd_worktree (int ac,const char **av,const char *prefix) {struct option options[] = {
@@ -513,5 +539,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)return list (ac -1 , av +1 , prefix);if (!strcmp (av[1 ],"lock" ))return lock_worktree (ac -1 , av +1 , prefix); +if (!strcmp (av[1 ],"unlock" )) +return unlock_worktree (ac -1 , av +1 , prefix);usage_with_options (worktree_usage, options); }
contrib/completion/git-completion.bash +1 -1
@@ -2597,7 +2597,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list lock prune" + local subcommands="add list lock prune unlock" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
t/t2028-worktree-move.sh +14 -0
@@ -45,4 +45,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' ' test_cmp expected .git/worktrees/source/locked ' +test_expect_success 'unlock main worktree' ' + test_must_fail git worktree unlock . +' + +test_expect_success 'unlock linked worktree' ' + git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + +test_expect_success 'unlock worktree twice' ' + test_must_fail git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + test_done
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH v5 6/6 worktree.c: find_worktree() search by path suffix
In reply to Nguyễn Thái Ngọc DuyThis allows the user to do something like "worktree lock foo" or "worktree lock to/foo" instead of "worktree lock /long/path/to/foo" if it's unambiguous.
With completion support it could be quite convenient. While this base name search can be done in the same worktree iteration loop, the code is split into a separate function for clarity.
--- Documentation/git-worktree.txt | 5 +++++ worktree.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+)
Documentation/git-worktree.txt +5 -0
@@ -129,6 +129,11 @@ OPTIONS <worktree>:: Working trees can be identified by path, either relative or absolute. ++ +If the last path components in the working tree's path is unique among +working trees, it can be used to identify worktrees. For example if +you only have to working trees at "/abc/def/ghi" and "/abc/def/ggg", +then "ghi" or "def/ghi" is enough to point to the former working tree. DETAILS -------
worktree.c +29 -0
@@ -219,12 +219,41 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +static struct worktree *find_worktree_by_suffix (struct worktree **list, +const char *suffix) +{ +struct worktree *found =NULL ; +int nr_found =0 , suffixlen; + + suffixlen =strlen (suffix); +if (!suffixlen) +return NULL ; + +for (; *list && nr_found <2 ; list++) { +const char *path = (*list)->path; +int pathlen =strlen (path); +int start = pathlen - suffixlen; + +/* suffix must start at directory boundary */ +if ((!start || (start >0 &&is_dir_sep (path[start -1 ]))) && + !fspathcmp (suffix, path + start)) { + found = *list; + nr_found++; + } + } +return nr_found ==1 ? found :NULL ; +} +struct worktree *find_worktree (struct worktree **list,const char *prefix,const char *arg) { +struct worktree *wt;char *path; +if ((wt =find_worktree_by_suffix (list, arg))) +return wt; + arg =prefix_filename (prefix,strlen (prefix), arg); path =xstrdup (real_path (arg));for (; *list; list++)
Signature · 1 line
2.8.2.524.g6ff3d78
Re: [PATCH v4 0/6] worktree lock/unlock
In reply to Nguyễn Thái Ngọc DuyQuoted 1 line · Nguyễn Thái Ngọc Duy
Minor problem:
t2028 fails, when the test is run from a directory that is
a softlink.
(In my case
/Users/tb/projects/git/git.pu
is a softlink to
/Users/tb/NoBackup/projects/git/git.pu/
[master (root-commit) 2519212] init
Author: A U Thor <[email protected]>
1 file changed, 1 insertion(+) create mode 100644 init.t
Preparing source (identifier source)
HEAD is now at 2519212 init
actual 2016-06-27 09:50:19.000000000 +0000 renamed from expected 2016-06-27 09:50:19.000000000 +0000 +2 -2
@@ -1,2 +1,2 @@ -worktree /Users/tb/projects/git/git.pu/t/trash directory.t2028-worktree-move -worktree /Users/tb/projects/git/git.pu/t/trash directory.t2028-worktree-move/source +worktree /Users/tb/NoBackup/projects/git/git.pu/t/trash directory.t2028-worktree-move +worktree /Users/tb/NoBackup/projects/git/git.pu/t/trash directory.t2028-worktree-move/source
not ok 1 - setup
#
PATCH 1/1 fixup! worktree: add "lock" command
---
Torsten, this seems to fix the symlink problem for me. How many times have I got similar reports from you and still managed to forget ...
t/t2028-worktree-move.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
t/t2028-worktree-move.sh +2 -2
@@ -9,8 +9,8 @@ test_expect_success 'setup' ' git worktree add source && git worktree list --porcelain | grep "^worktree" >actual && cat <<-EOF >expected && - worktree $TRASH_DIRECTORY - worktree $TRASH_DIRECTORY/source + worktree $(pwd) + worktree $(pwd)/source EOF test_cmp expected actual '
Signature · 1 line
2.8.2.531.gd073806
PATCH v3 6/13 path.c: refactor and add worktree_git_path()
In reply to Nguyễn Thái Ngọc Duydo_git_path(), which is the common code for all git_path* functions, is modified to take a worktree struct and can produce paths for any worktree.
worktree_git_path() is the first function that makes use of this. It can be used to write code that can examine any worktree. For example, wt_status_get_state() will be converted using this to take am/rebase/... state of any worktree.
--- path.c | 24 ++++++++++++++++++------ worktree.h | 8 ++++++++ 2 files changed, 26 insertions(+), 6 deletions(-)
path.c +18 -6
@@ -5,6 +5,7 @@#include "strbuf.h" #include "string-list.h" #include "dir.h" +#include "worktree.h" static int get_st_mode_bits (const char *path,int *mode) {
@@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)update_common_dir (buf, git_dir_len,NULL ); } -static void do_git_path (struct strbuf *buf,const char *fmt,va_list args) +static void do_git_path (const struct worktree *wt,struct strbuf *buf, +const char *fmt,va_list args) {int gitdir_len; -strbuf_addstr (buf,get_git_dir ()); +strbuf_addstr (buf,get_worktree_git_dir (wt));if (buf->len && !is_dir_sep (buf->buf[buf->len -1 ]))strbuf_addch (buf,'/' ); gitdir_len = buf->len;
@@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)va_list args;strbuf_reset (buf);va_start (args, fmt); -do_git_path (buf, fmt, args); +do_git_path (NULL , buf, fmt, args);va_end (args);return buf->buf; }
@@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) {va_list args;va_start (args, fmt); -do_git_path (sb, fmt, args); +do_git_path (NULL , sb, fmt, args);va_end (args); }
@@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)struct strbuf *pathname =get_pathname ();va_list args;va_start (args, fmt); -do_git_path (pathname, fmt, args); +do_git_path (NULL , pathname, fmt, args);va_end (args);return pathname->buf; }
@@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)struct strbuf path =STRBUF_INIT ;va_list args;va_start (args, fmt); -do_git_path (&path, fmt, args); +do_git_path (NULL , &path, fmt, args);va_end (args);return strbuf_detach (&path,NULL ); }
@@ -454,6 +456,16 @@ const char *mkpath(const char *fmt, ...)return cleanup_path (pathname->buf); } +const char *worktree_git_path (const struct worktree *wt,const char *fmt, ...) +{ +struct strbuf *pathname =get_pathname (); +va_list args; +va_start (args, fmt); +do_git_path (wt, pathname, fmt, args); +va_end (args); +return pathname->buf; +} +static void do_submodule_path (struct strbuf *buf,const char *path,const char *fmt,va_list args) {
worktree.h +8 -0
@@ -42,4 +42,12 @@ extern void free_worktrees(struct worktree **);extern const struct worktree *find_shared_symref (const char *symref,const char *target); +/* +* Similar to git_path() but can produce paths for a specified +* worktree instead of current one +*/ +extern const char *worktree_git_path (const struct worktree *wt, +const char *fmt, ...) + __attribute__((format (printf,2 ,3 ))); +#endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v3 7/13 wt-status.c: split rebase detection out of wt_status_get_state()
In reply to Nguyễn Thái Ngọc Duyworktree.c:find_shared_symref() later needs to know if a branch is being rebased, and only rebase, no cherry-pick, do detached branch... Split this code so it can be used independently from other in-progress tests.
--- wt-status.c | 23 +++++++++++++++++------ wt-status.h | 1 + 2 files changed, 18 insertions(+), 6 deletions(-)
wt-status.c +17 -6
@@ -1360,15 +1360,11 @@ static void wt_status_get_detached_from(struct wt_status_state *state)strbuf_release (&cb.buf); } -void wt_status_get_state (struct wt_status_state *state, -int get_detached_from) +int wt_status_check_rebase (struct wt_status_state *state) {struct stat st; -unsigned char sha1[20 ]; -if (!stat (git_path_merge_head (), &st)) { - state->merge_in_progress =1 ; - }else if (!stat (git_path ("rebase-apply" ), &st)) { +if (!stat (git_path ("rebase-apply" ), &st)) {if (!stat (git_path ("rebase-apply/applying" ), &st)) { state->am_in_progress =1 ;if (!stat (git_path ("rebase-apply/patch" ), &st) && !st.st_size)
@@ -1385,6 +1381,21 @@ void wt_status_get_state(struct wt_status_state *state, state->rebase_in_progress =1 ; state->branch =read_and_strip_branch ("rebase-merge/head-name" ); state->onto =read_and_strip_branch ("rebase-merge/onto" ); + }else +return 0 ; +return 1 ; +} + +void wt_status_get_state (struct wt_status_state *state, +int get_detached_from) +{ +struct stat st; +unsigned char sha1[20 ]; + +if (!stat (git_path_merge_head (), &st)) { + state->merge_in_progress =1 ; + }else if (wt_status_check_rebase (state)) { + ;/* all set */ }else if (!stat (git_path_cherry_pick_head (), &st) && !get_sha1 ("CHERRY_PICK_HEAD" , sha1)) { state->cherry_pick_in_progress =1 ;
wt-status.h +1 -0
@@ -100,6 +100,7 @@ void wt_status_prepare(struct wt_status *s);void wt_status_print (struct wt_status *s);void wt_status_collect (struct wt_status *s);void wt_status_get_state (struct wt_status_state *state,int get_detached_from); +int wt_status_check_rebase (struct wt_status_state *state);void wt_shortstatus_print (struct wt_status *s);void wt_porcelain_print (struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v3 8/13 wt-status.c: make wt_status_check_rebase() work on any worktree
In reply to Nguyễn Thái Ngọc DuyThis is a preparation step for find_shared_symref() to detect if any worktree is being rebased.
--- wt-status.c | 33 ++++++++++++++++++++------------- wt-status.h | 5 ++++- 2 files changed, 24 insertions(+), 14 deletions(-)
wt-status.c +20 -13
@@ -15,6 +15,7 @@#include "column.h" #include "strbuf.h" #include "utf8.h" +#include "worktree.h" static const char cut_line[] ="------------------------ >8 ------------------------\n" ;
@@ -1262,13 +1263,13 @@ static void show_bisect_in_progress(struct wt_status *s,/* * Extract branch information from rebase/bisect */ -static char *read_and_strip_branch (const char *path) +static char *get_branch (const struct worktree *wt,const char *path) {struct strbuf sb =STRBUF_INIT ;unsigned char sha1[20 ];const char *branch_name; -if (strbuf_read_file (&sb,git_path ("%s" , path),0 ) <=0 ) +if (strbuf_read_file (&sb,worktree_git_path (wt,"%s" , path),0 ) <=0 ) goto got_nothing;while (sb.len && sb.buf[sb.len -1 ] =='\n' )
@@ -1295,6 +1296,11 @@ got_nothing:return NULL ; } +static char *read_and_strip_branch (const char *path) +{ +return get_branch (NULL , path); +} +struct grab_1st_switch_cbdata {struct strbuf buf;unsigned char nsha1[20 ];
@@ -1360,27 +1366,28 @@ static void wt_status_get_detached_from(struct wt_status_state *state)strbuf_release (&cb.buf); } -int wt_status_check_rebase (struct wt_status_state *state) +int wt_status_check_rebase (const struct worktree *wt, +struct wt_status_state *state) {struct stat st; -if (!stat (git_path ("rebase-apply" ), &st)) { -if (!stat (git_path ("rebase-apply/applying" ), &st)) { +if (!stat (worktree_git_path (wt,"rebase-apply" ), &st)) { +if (!stat (worktree_git_path (wt,"rebase-apply/applying" ), &st)) { state->am_in_progress =1 ; -if (!stat (git_path ("rebase-apply/patch" ), &st) && !st.st_size) +if (!stat (worktree_git_path (wt,"rebase-apply/patch" ), &st) && !st.st_size) state->am_empty_patch =1 ; }else { state->rebase_in_progress =1 ; - state->branch =read_and_strip_branch ("rebase-apply/head-name" ); - state->onto =read_and_strip_branch ("rebase-apply/onto" ); + state->branch =get_branch (wt,"rebase-apply/head-name" ); + state->onto =get_branch (wt,"rebase-apply/onto" ); } - }else if (!stat (git_path ("rebase-merge" ), &st)) { -if (!stat (git_path ("rebase-merge/interactive" ), &st)) + }else if (!stat (worktree_git_path (wt,"rebase-merge" ), &st)) { +if (!stat (worktree_git_path (wt,"rebase-merge/interactive" ), &st)) state->rebase_interactive_in_progress =1 ;else state->rebase_in_progress =1 ; - state->branch =read_and_strip_branch ("rebase-merge/head-name" ); - state->onto =read_and_strip_branch ("rebase-merge/onto" ); + state->branch =get_branch (wt,"rebase-merge/head-name" ); + state->onto =get_branch (wt,"rebase-merge/onto" ); }else return 0 ;return 1 ;
@@ -1394,7 +1401,7 @@ void wt_status_get_state(struct wt_status_state *state,if (!stat (git_path_merge_head (), &st)) { state->merge_in_progress =1 ; - }else if (wt_status_check_rebase (state)) { + }else if (wt_status_check_rebase (NULL , state)) { ;/* all set */ }else if (!stat (git_path_cherry_pick_head (), &st) && !get_sha1 ("CHERRY_PICK_HEAD" , sha1)) {
wt-status.h +4 -1
@@ -6,6 +6,8 @@#include "color.h" #include "pathspec.h" +struct worktree ; +enum color_wt_status {WT_STATUS_HEADER =0 ,WT_STATUS_UPDATED ,
@@ -100,7 +102,8 @@ void wt_status_prepare(struct wt_status *s);void wt_status_print (struct wt_status *s);void wt_status_collect (struct wt_status *s);void wt_status_get_state (struct wt_status_state *state,int get_detached_from); -int wt_status_check_rebase (struct wt_status_state *state); +int wt_status_check_rebase (const struct worktree *wt, +struct wt_status_state *state);void wt_shortstatus_print (struct wt_status *s);void wt_porcelain_print (struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v3 9/13 worktree.c: avoid referencing to worktrees[i] multiple times
In reply to Nguyễn Thái Ngọc Duy--- worktree.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
worktree.c +4 -2
@@ -229,10 +229,12 @@ const struct worktree *find_shared_symref(const char *symref, worktrees =get_worktrees ();for (i =0 ; worktrees[i]; i++) { +struct worktree *wt = worktrees[i]; +strbuf_reset (&path);strbuf_reset (&sb);strbuf_addf (&path,"%s/%s" , -get_worktree_git_dir (worktrees[i]), +get_worktree_git_dir (wt), symref);if (parse_ref (path.buf, &sb,NULL )) {
@@ -240,7 +242,7 @@ const struct worktree *find_shared_symref(const char *symref, }if (!strcmp (sb.buf, target)) { - existing = worktrees[i]; + existing = wt;break ; } }
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v3 10/13 worktree.c: check whether branch is rebased in another worktree
In reply to Nguyễn Thái Ngọc DuyThis function find_shared_symref() is used in a couple places:
1) in builtin/branch.c: it's used to detect if a branch is checked out elsewhere and refuse to delete the branch.
2) in builtin/notes.c: it's used to detect if a note is being merged in another worktree
3) in branch.c, the function die_if_checked_out() is actually used by "git checkout" and "git worktree add" to see if a branch is already checked out elsewhere and refuse the operation.
In cases 1 and 3, if a rebase is ongoing, "HEAD" will be in detached mode, find_shared_symref() fails to detect it and declares "no branch is checked out here", which is not really what we want.
This patch tightens the test. If the given symref is "HEAD", we try to detect if rebase is ongoing. If so return the branch being rebased. This makes checkout and branch delete operations safer because you can't checkout a branch being rebased in another place, or delete it.
Special case for checkout. If the current branch is being rebased, git-rebase.sh may use "git checkout" to abort and return back to the original branch. The updated test in find_shared_symref() will prevent that and "git rebase --abort" will fail as a result. find_shared_symref() and die_if_checked_out() have to learn a new option ignore_current_worktree to loosen the test a bit.
--- branch.c | 4 ++-- branch.h | 2 +- builtin/checkout.c | 2 +- builtin/worktree.c | 4 ++-- t/t2025-worktree-add.sh | 38 ++++++++++++++++++++++++++++++++++++++ worktree.c | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 6 deletions(-)
branch.c +2 -2
@@ -334,12 +334,12 @@ void remove_branch_state(void)unlink (git_path_squash_msg ()); } -void die_if_checked_out (const char *branch) +void die_if_checked_out (const char *branch,int ignore_current_worktree) {const struct worktree *wt; wt =find_shared_symref ("HEAD" , branch); -if (!wt) +if (!wt || (ignore_current_worktree && wt->is_current))return ;skip_prefix (branch,"refs/heads/" , &branch); die(_("'%s' is already checked out at '%s'"),
branch.h +1 -1
@@ -58,7 +58,7 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);* worktree and die (with a message describing its checkout location) if * it is. */ -extern void die_if_checked_out (const char *branch); +extern void die_if_checked_out (const char *branch,int ignore_current_worktree);/* * Update all per-worktree HEADs pointing at the old ref to point the new ref.
builtin/checkout.c +1 -1
@@ -1111,7 +1111,7 @@ static int checkout_branch(struct checkout_opts *opts,char *head_ref =resolve_refdup ("HEAD" ,0 , sha1, &flag);if (head_ref && (!(flag &REF_ISSYMREF ) ||strcmp (head_ref, new->path))) -die_if_checked_out (new->path); +die_if_checked_out (new->path,1 );free (head_ref); }
builtin/worktree.c +2 -2
@@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname,if (!opts->detach && !strbuf_check_branch_ref (&symref, refname) &&ref_exists (symref.buf)) {/* it's a branch */ if (!opts->force) -die_if_checked_out (symref.buf); +die_if_checked_out (symref.buf,0 ); }else {/* must be a commit */ commit =lookup_commit_reference_by_name (refname);if (!commit)
@@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix)if (!opts.force && !strbuf_check_branch_ref (&symref, opts.new_branch) &&ref_exists (symref.buf)) -die_if_checked_out (symref.buf); +die_if_checked_out (symref.buf,0 );strbuf_release (&symref); }
t/t2025-worktree-add.sh +38 -0
@@ -4,6 +4,8 @@ test_description='test git worktree add' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + test_expect_success 'setup' ' test_commit init '
@@ -225,4 +227,40 @@ test_expect_success '"add" worktree with --checkout' ' test_cmp init.t swamp2/init.t ' +test_expect_success 'put a worktree under rebase' ' + git worktree add under-rebase && + ( + cd under-rebase && + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + git worktree list | grep "under-rebase.*detached HEAD" + ) +' + +test_expect_success 'add a worktree, checking out a rebased branch' ' + test_must_fail git worktree add new-rebase under-rebase && + ! test -d new-rebase +' + +test_expect_success 'checking out a rebased branch from another worktree' ' + git worktree add new-place && + test_must_fail git -C new-place checkout under-rebase +' + +test_expect_success 'not allow to delete a branch under rebase' ' + ( + cd under-rebase && + test_must_fail git branch -D under-rebase + ) +' + +test_expect_success 'check out from current worktree branch ok' ' + ( + cd under-rebase && + git checkout under-rebase && + git checkout - && + git rebase --abort + ) +' + test_done
worktree.c +32 -0
@@ -3,6 +3,7 @@#include "strbuf.h" #include "worktree.h" #include "dir.h" +#include "wt-status.h" void free_worktrees (struct worktree **worktrees) {
@@ -215,6 +216,30 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } +static int is_worktree_being_rebased (const struct worktree *wt, +const char *target) +{ +struct wt_status_state state; +int found_rebase; + +memset (&state,0 ,sizeof (state)); + found_rebase =wt_status_check_rebase (wt, &state) && + ((state.rebase_in_progress || + state.rebase_interactive_in_progress) && + state.branch && +starts_with (target,"refs/heads/" ) && + !strcmp (state.branch, target +strlen ("refs/heads/" ))); +free (state.branch); +free (state.onto); +return found_rebase; +} + +/* +* note: this function should be able to detect shared symref even if +* HEAD is temporarily detached (e.g. in the middle of rebase or +* bisect). New commands that do similar things should update this +* function as well. +*/ const struct worktree *find_shared_symref (const char *symref,const char *target) {
@@ -231,6 +256,13 @@ const struct worktree *find_shared_symref(const char *symref,for (i =0 ; worktrees[i]; i++) {struct worktree *wt = worktrees[i]; +if (wt->is_detached && !strcmp (symref,"HEAD" )) { +if (is_worktree_being_rebased (wt, target)) { + existing = wt; +break ; + } + } +strbuf_reset (&path);strbuf_reset (&sb); strbuf_addf(&path, "%s/%s",
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v3 11/13 wt-status.c: split bisect detection out of wt_status_get_state()
In reply to Nguyễn Thái Ngọc DuyAnd make it work with any given worktree, in preparation for (again) find_shared_symref(). read_and_strip_branch() is deleted because it's no longer used.
--- wt-status.c | 23 ++++++++++++++--------- wt-status.h | 2 ++ 2 files changed, 16 insertions(+), 9 deletions(-)
wt-status.c +14 -9
@@ -1296,11 +1296,6 @@ got_nothing:return NULL ; } -static char *read_and_strip_branch (const char *path) -{ -return get_branch (NULL , path); -} -struct grab_1st_switch_cbdata {struct strbuf buf;unsigned char nsha1[20 ];
@@ -1393,6 +1388,19 @@ int wt_status_check_rebase(const struct worktree *wt,return 1 ; } +int wt_status_check_bisect (const struct worktree *wt, +struct wt_status_state *state) +{ +struct stat st; + +if (!stat (worktree_git_path (wt,"BISECT_LOG" ), &st)) { + state->bisect_in_progress =1 ; + state->branch =get_branch (wt,"BISECT_START" ); +return 1 ; + } +return 0 ; +} +void wt_status_get_state (struct wt_status_state *state,int get_detached_from) {
@@ -1408,10 +1416,7 @@ void wt_status_get_state(struct wt_status_state *state, state->cherry_pick_in_progress =1 ;hashcpy (state->cherry_pick_head_sha1, sha1); } -if (!stat (git_path ("BISECT_LOG" ), &st)) { - state->bisect_in_progress =1 ; - state->branch =read_and_strip_branch ("BISECT_START" ); - } +wt_status_check_bisect (NULL , state);if (!stat (git_path_revert_head (), &st) && !get_sha1 ("REVERT_HEAD" , sha1)) { state->revert_in_progress =1 ;
wt-status.h +2 -0
@@ -104,6 +104,8 @@ void wt_status_collect(struct wt_status *s);void wt_status_get_state (struct wt_status_state *state,int get_detached_from);int wt_status_check_rebase (const struct worktree *wt,struct wt_status_state *state); +int wt_status_check_bisect (const struct worktree *wt, +struct wt_status_state *state);void wt_shortstatus_print (struct wt_status *s);void wt_porcelain_print (struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
PATCH v3 12/13 worktree.c: check whether branch is bisected in another worktree
In reply to Nguyễn Thái Ngọc DuySimilar to the rebase case, we want to detect if "HEAD" in some worktree is being bisected because
1) we do not want to checkout this branch in another worktree, after bisect is done it will want to go back to this branch
2) we do not want to delete the branch is either or git bisect will fail to return to the (long gone) branch
--- t/t2025-worktree-add.sh | 13 +++++++++++++ worktree.c | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+)
t/t2025-worktree-add.sh +13 -0
@@ -263,4 +263,17 @@ test_expect_success 'check out from current worktree branch ok' ' ) ' +test_expect_success 'checkout a branch under bisect' ' + git worktree add under-bisect && + ( + cd under-bisect && + git bisect start && + git bisect bad && + git bisect good HEAD~2 && + git worktree list | grep "under-bisect.*detached HEAD" && + test_must_fail git worktree add new-bisect under-bisect && + ! test -d new-bisect + ) +' + test_done
worktree.c +19 -0
@@ -234,6 +234,21 @@ static int is_worktree_being_rebased(const struct worktree *wt,return found_rebase; } +static int is_worktree_being_bisected (const struct worktree *wt, +const char *target) +{ +struct wt_status_state state; +int found_rebase; + +memset (&state,0 ,sizeof (state)); + found_rebase =wt_status_check_bisect (wt, &state) && + state.branch && +starts_with (target,"refs/heads/" ) && + !strcmp (state.branch, target +strlen ("refs/heads/" )); +free (state.branch); +return found_rebase; +} +/* * note: this function should be able to detect shared symref even if * HEAD is temporarily detached (e.g. in the middle of rebase or
@@ -261,6 +276,10 @@ const struct worktree *find_shared_symref(const char *symref, existing = wt;break ; } +if (is_worktree_being_bisected (wt, target)) { + existing = wt; +break ; + } }strbuf_reset (&path);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
Re: [PATCH v3 12/13] worktree.c: check whether branch is bisected in another worktree
Quoted 8 lines · Nguyễn Thái Ngọc Duy
Similar to the rebase case, we want to detect if "HEAD" in some worktree is being bisected because
1) we do not want to checkout this branch in another worktree, after bisect is done it will want to go back to this branch
2) we do not want to delete the branch is either or git bisect will fail to return to the (long gone) branch
I'm very far behind with my reviews and I see that this series is already in 'next', so perhaps these comments are too late...
t/t2025-worktree-add.sh test_expect_success 'check out from current worktree branch ok' ' · line 271
Quoted 13 lines
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
t/t2025-worktree-add.sh +9
-0@@ -263,4 +263,17 @@ test_expect_success 'check out from current worktree branch ok' ' +test_expect_success 'checkout a branch under bisect' ' + git worktree add under-bisect && + ( + cd under-bisect && + git bisect start && + git bisect bad && + git bisect good HEAD~2 && + git worktree list | grep "under-bisect.*detached HEAD" && + test_must_fail git worktree add new-bisect under-bisect &&
Nit: I realize that the checking 'worktree add' is sufficient, but it's a bit confusing to read in the commit message about how deleting the branch would be bad, but then see it testing only 'add'.
worktree.c static int is_worktree_being_rebased(const struct worktree *wt, · line 238
Quoted 10 lines
t/t2025-worktree-add.sh +3
-0+ ! test -d new-bisect + ) +'worktree.c +5
-0@@ -234,6 +234,21 @@ static int is_worktree_being_rebased(const struct worktree *wt, +static int is_worktree_being_bisected (const struct worktree *wt, +const char *target) +{ +struct wt_status_state state; +int found_rebase;
s/rebase/bisect/ perhaps?
Quoted 8 lines
worktree.c +8
-0+memset (&state,0 ,sizeof (state)); + found_rebase =wt_status_check_bisect (wt, &state) && + state.branch && +starts_with (target,"refs/heads/" ) && + !strcmp (state.branch, target +strlen ("refs/heads/" )); +free (state.branch); +return found_rebase; +}
PATCH v3 13/13 branch: do not rename a branch under bisect or rebase
In reply to Nguyễn Thái Ngọc DuyThe branch name in that case could be saved in rebase's head_name or bisect's BISECT_START files. Ideally we should try to update them as well. But it's trickier (*). Let's play safe and see if the user complains about inconveniences before doing that.
(*) If we do it, bisect and rebase need to provide an API to rename branches. We can't do it in worktree.c or builtin/branch.c because when other people change rebase/bisect code, they may not be aware of this code and accidentally break it (e.g. rename the branch file, or refer to the branch in new files). It's a lot more work.
--- builtin/branch.c | 25 +++++++++++++++++++++++++ t/t2025-worktree-add.sh | 8 ++++++++ worktree.c | 8 ++++---- worktree.h | 3 +++ 4 files changed, 40 insertions(+), 4 deletions(-)
builtin/branch.c +25 -0
@@ -524,6 +524,29 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortinref_array_clear (&array); } +static void reject_rebase_or_bisect_branch (const char *target) +{ +struct worktree **worktrees =get_worktrees (); +int i; + +for (i =0 ; worktrees[i]; i++) { +struct worktree *wt = worktrees[i]; + +if (!wt->is_detached) +continue ; + +if (is_worktree_being_rebased (wt, target)) +die (_ ("Branch %s is being rebased at %s" ), + target, wt->path); + +if (is_worktree_being_bisected (wt, target)) +die (_ ("Branch %s is being bisected at %s" ), + target, wt->path); + } + +free_worktrees (worktrees); +} +static void rename_branch (const char *oldname,const char *newname,int force) {struct strbuf oldref =STRBUF_INIT , newref =STRBUF_INIT , logmsg =STRBUF_INIT ;
@@ -553,6 +576,8 @@ static void rename_branch(const char *oldname, const char *newname, int force)validate_new_branchname (newname, &newref, force, clobber_head_ok); +reject_rebase_or_bisect_branch (oldref.buf); +strbuf_addf (&logmsg,"Branch: renamed %s to %s" , oldref.buf, newref.buf);
t/t2025-worktree-add.sh +8 -0
@@ -254,6 +254,10 @@ test_expect_success 'not allow to delete a branch under rebase' ' ) ' +test_expect_success 'rename a branch under rebase not allowed' ' + test_must_fail git branch -M under-rebase rebase-with-new-name +' + test_expect_success 'check out from current worktree branch ok' ' ( cd under-rebase &&
@@ -276,4 +280,8 @@ test_expect_success 'checkout a branch under bisect' ' ) ' +test_expect_success 'rename a branch under bisect not allowed' ' + test_must_fail git branch -M under-bisect bisect-with-new-name +' + test_done
worktree.c +4 -4
@@ -216,8 +216,8 @@ const char *get_worktree_git_dir(const struct worktree *wt)return git_common_path ("worktrees/%s" , wt->id); } -static int is_worktree_being_rebased (const struct worktree *wt, -const char *target) +int is_worktree_being_rebased (const struct worktree *wt, +const char *target) {struct wt_status_state state;int found_rebase;
@@ -234,8 +234,8 @@ static int is_worktree_being_rebased(const struct worktree *wt,return found_rebase; } -static int is_worktree_being_bisected (const struct worktree *wt, -const char *target) +int is_worktree_being_bisected (const struct worktree *wt, +const char *target) {struct wt_status_state state;int found_rebase;
worktree.h +3 -0
@@ -42,6 +42,9 @@ extern void free_worktrees(struct worktree **);extern const struct worktree *find_shared_symref (const char *symref,const char *target); +int is_worktree_being_rebased (const struct worktree *wt,const char *target); +int is_worktree_being_bisected (const struct worktree *wt,const char *target); +/* * Similar to git_path() but can produce paths for a specified * worktree instead of current one
Signature · 1 line
2.8.0.rc0.210.gd302cd2