When renaming a branch, the current code only updates the current
working tree's HEAD, but it should update .git/HEAD of all checked out
working trees.
This is the current behavior, /path/to/wt's HEAD is not updated:
% git worktree list
/path/to 2c3c5f2 [master]
/path/to/wt 2c3c5f2 [oldname]
% git branch -m master master2
% git worktree list
/path/to 2c3c5f2 [master2]
/path/to/wt 2c3c5f2 [oldname]
% git branch -m oldname newname
% git worktree list
/path/to 2c3c5f2 [master2]
/path/to/wt 0000000 [oldname]
This patch fixes this issue by updating all relevant worktree HEADs
when renaming a branch.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
builtin/branch.c | 4 ++--
t/t3200-branch.sh | 14 +++++++++++++-
worktree.c | 38 ++++++++++++++++++++++++++++++++++++++
worktree.h | 7 +++++++
4 files changed, 60 insertions(+), 3 deletions(-)
@@ -552,8 +553,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)if(recovery)warning(_("Renamed a misnamed branch '%s' away"),oldref.buf+11);-/* no need to pass logmsg here as HEAD didn't really move */-if(!strcmp(oldname,head)&&create_symref("HEAD",newref.buf,NULL))+if(update_worktrees_head_symref(oldref.buf,newref.buf))die(_("Branch renamed to %s, but HEAD is not updated!"),newname);strbuf_addf(&oldsection,"branch.%s",oldref.buf+11);
@@ -126,7 +126,19 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out' test_expect_success'git branch -M baz bam should succeed when baz is checked out''gitcheckout-bbaz&&gitbranchbam&&-gitbranch-Mbazbam+gitbranch-Mbazbam&&+test$(gitrev-parse--abbrev-refHEAD)=bam+'++test_expect_success'git branch -M baz bam should succeed when baz is checked out as linked working tree''+gitcheckoutmaster&&+gitbranch&&+gitworktreeadd-bbazbazdir&&+gitbranch-Mbazbam&&+(+cdbazdir&&+test$(gitrev-parse--abbrev-refHEAD)=bam+)' test_expect_success'git branch -M master should work when master is checked out''
@@ -217,3 +217,41 @@ char *find_shared_symref(const char *symref, const char *target)returnexisting;}++intupdate_worktrees_head_symref(constchar*oldref,constchar*newref)+{+interror=0;+structstrbufpath=STRBUF_INIT;+structstrbuforigref=STRBUF_INIT;+inti;+structworktree**worktrees=get_worktrees();++for(i=0;worktrees[i];i++){+if(worktrees[i]->is_detached)+continue;++strbuf_reset(&path);+strbuf_reset(&origref);+strbuf_addf(&path,"%s/HEAD",worktrees[i]->git_dir);++if(parse_ref(path.buf,&origref,NULL))+continue;++if(!strcmp(origref.buf,oldref)){+intprefix_len=strlen(absolute_path(get_git_common_dir()))+1;+constchar*symref=path.buf+prefix_len;++/* no need to pass logmsg here as HEAD didn't really move */+if(create_symref(symref,newref,NULL)){+error=-1;+break;+}+}+}++strbuf_release(&path);+strbuf_release(&origref);+free_worktrees(worktrees);++returnerror;+}
From: Eric Sunshine <hidden> Date: 2016-06-15 23:08:51
On Mon, Mar 21, 2016 at 5:50 AM, Kazuki Yamaguchi [off-list ref] wrote:
When renaming a branch, the current code only updates the current
working tree's HEAD, but it should update .git/HEAD of all checked out
working trees.
This is the current behavior, /path/to/wt's HEAD is not updated:
[...]
This patch fixes this issue by updating all relevant worktree HEADs
when renaming a branch.
Makes sense; seems like a genuine problem. Some comment below...
@@ -126,7 +126,19 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out' test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '+test_expect_success 'git branch -M baz bam should succeed when baz is checked out as linked working tree' '+ git checkout master &&+ git branch &&+ git worktree add -b baz bazdir &&+ git branch -M baz bam &&+ (+ cd bazdir &&+ test $(git rev-parse --abbrev-ref HEAD) = bam+ ) '
This can be done more easily without the subshell:
test $(git -C bazdir rev-parse ...) = bam
Can you also expand the test so that it verifies that the rename works
as expected when the branch is checked out in multiple worktrees,
rather than just one. Likewise, it would be nice to check branch
rename from within a worktree in which the branch is checked out (in
addition to the test above which does the rename from outside such a
worktree).
More below...
quoted hunk
diff --git a/worktree.c b/worktree.c
@@ -217,3 +217,41 @@ char *find_shared_symref(const char *symref, const char *target)+int update_worktrees_head_symref(const char *oldref, const char *newref)+{+ int error = 0;+ struct strbuf path = STRBUF_INIT;+ struct strbuf origref = STRBUF_INIT;+ int i;+ struct worktree **worktrees = get_worktrees();++ for (i = 0; worktrees[i]; i++) {+ if (worktrees[i]->is_detached)+ continue;++ strbuf_reset(&path);+ strbuf_reset(&origref);+ strbuf_addf(&path, "%s/HEAD", worktrees[i]->git_dir);++ if (parse_ref(path.buf, &origref, NULL))+ continue;++ if (!strcmp(origref.buf, oldref)) {+ int prefix_len = strlen(absolute_path(get_git_common_dir())) + 1;+ const char *symref = path.buf + prefix_len;++ /* no need to pass logmsg here as HEAD didn't really move */+ if (create_symref(symref, newref, NULL)) {+ error = -1;+ break;
Is aborting upon the first error desired behavior? (Genuine question.)
Would it make more sense to continue attempting the rename for the
remaining worktrees (and remember that an error was encountered)?
Related: Since you're now dealing with multiple worktrees, you can do
a better job of letting the user know in which worktree something went
wrong rather than merely emitting the relatively generic "Branch
renamed to %s, but HEAD is not updated!".
More below...
@@ -35,4 +35,11 @@ extern void free_worktrees(struct worktree **);+/*+ * Update all per-worktree HEADs pointing the old ref to point the new ref.+ * This will be used when renaming a branch. Returns 0 if successful,+ * non-zero otherwise.+ */+extern int update_worktrees_head_symref(const char *, const char *);
I guess I can understand the desire to libify this functionality,
however, it feels as if it is a feature of "branch" rather than
"worktree", hence perhaps it should reside in top-level branch.[hc]?
On Tue, Mar 22, 2016 at 12:41 AM, Eric Sunshine [off-list ref] wrote:
quoted
diff --git a/worktree.c b/worktree.c
@@ -217,3 +217,41 @@ char *find_shared_symref(const char *symref, const char *target)+int update_worktrees_head_symref(const char *oldref, const char *newref)+{+ int error = 0;+ struct strbuf path = STRBUF_INIT;+ struct strbuf origref = STRBUF_INIT;+ int i;+ struct worktree **worktrees = get_worktrees();++ for (i = 0; worktrees[i]; i++) {+ if (worktrees[i]->is_detached)+ continue;++ strbuf_reset(&path);+ strbuf_reset(&origref);+ strbuf_addf(&path, "%s/HEAD", worktrees[i]->git_dir);++ if (parse_ref(path.buf, &origref, NULL))+ continue;++ if (!strcmp(origref.buf, oldref)) {+ int prefix_len = strlen(absolute_path(get_git_common_dir())) + 1;+ const char *symref = path.buf + prefix_len;++ /* no need to pass logmsg here as HEAD didn't really move */+ if (create_symref(symref, newref, NULL)) {+ error = -1;+ break;
Is aborting upon the first error desired behavior? (Genuine question.)
Would it make more sense to continue attempting the rename for the
remaining worktrees (and remember that an error was encountered)?
Since all these HEADs stay at the same (or close) location, if one
fails, I think the rest will fail too. Which leads to a series of
warnings if we continue anyway. A more interesting approach is update
HEADs in a transaction, so we successfully update all or we update
none. But I do not know if ref transactions can be used for HEAD,
especially worktree HEADs. I'm ok with either abort here or continue
anyway, though.
--
Duy
@@ -126,7 +126,19 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out' test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '+test_expect_success 'git branch -M baz bam should succeed when baz is checked out as linked working tree' '+ git checkout master &&+ git branch &&+ git worktree add -b baz bazdir &&+ git branch -M baz bam &&+ (+ cd bazdir &&+ test $(git rev-parse --abbrev-ref HEAD) = bam+ ) '
This can be done more easily without the subshell:
test $(git -C bazdir rev-parse ...) = bam
Thank you for reviewing. And sorry for late response.
I didn't know -C option, thanks.
Can you also expand the test so that it verifies that the rename works
as expected when the branch is checked out in multiple worktrees,
rather than just one. Likewise, it would be nice to check branch
rename from within a worktree in which the branch is checked out (in
addition to the test above which does the rename from outside such a
worktree).
I'll add them.
And I noticed my patch is broken in the latter case (rename in a linked
working tree).
Since create_symref() calls resolve_ref_unsafe() and it uses $GIT_DIR
for worktree-specific refs thus my patch fails to update main tree's
HEAD when we are in a linked working tree.
I'm thinking about adding new flag to resolve_ref_unsafe(), to force
using $GIT_COMMON_DIR. This will at the same time allows to remove
parse_ref() in worktree.c.
quoted
diff --git a/worktree.c b/worktree.c
@@ -217,3 +217,41 @@ char *find_shared_symref(const char *symref, const char *target)+int update_worktrees_head_symref(const char *oldref, const char *newref)+{+ int error = 0;+ struct strbuf path = STRBUF_INIT;+ struct strbuf origref = STRBUF_INIT;+ int i;+ struct worktree **worktrees = get_worktrees();++ for (i = 0; worktrees[i]; i++) {+ if (worktrees[i]->is_detached)+ continue;++ strbuf_reset(&path);+ strbuf_reset(&origref);+ strbuf_addf(&path, "%s/HEAD", worktrees[i]->git_dir);++ if (parse_ref(path.buf, &origref, NULL))+ continue;++ if (!strcmp(origref.buf, oldref)) {+ int prefix_len = strlen(absolute_path(get_git_common_dir())) + 1;+ const char *symref = path.buf + prefix_len;++ /* no need to pass logmsg here as HEAD didn't really move */+ if (create_symref(symref, newref, NULL)) {+ error = -1;+ break;
Is aborting upon the first error desired behavior? (Genuine question.)
Would it make more sense to continue attempting the rename for the
remaining worktrees (and remember that an error was encountered)?
Related: Since you're now dealing with multiple worktrees, you can do
a better job of letting the user know in which worktree something went
wrong rather than merely emitting the relatively generic "Branch
renamed to %s, but HEAD is not updated!".
I think both is ok.
But continuing shouldn't be harm, so continuing might be better in terms
of that it can tell the user what files need to be fixed manually.
I'll try it.
quoted
+}
diff --git a/worktree.h b/worktree.h
@@ -35,4 +35,11 @@ extern void free_worktrees(struct worktree **);+/*+ * Update all per-worktree HEADs pointing the old ref to point the new ref.+ * This will be used when renaming a branch. Returns 0 if successful,+ * non-zero otherwise.+ */+extern int update_worktrees_head_symref(const char *, const char *);
I guess I can understand the desire to libify this functionality,
however, it feels as if it is a feature of "branch" rather than
"worktree", hence perhaps it should reside in top-level branch.[hc]?
I agree, I'll move it.
I chose worktree.c just because it has parse_ref().
On Tue, Mar 22, 2016 at 07:49:00AM +0700, Duy Nguyen wrote:
On Tue, Mar 22, 2016 at 12:41 AM, Eric Sunshine [off-list ref] wrote:
quoted
quoted
diff --git a/worktree.c b/worktree.c
@@ -217,3 +217,41 @@ char *find_shared_symref(const char *symref, const char *target)+int update_worktrees_head_symref(const char *oldref, const char *newref)+{+ int error = 0;+ struct strbuf path = STRBUF_INIT;+ struct strbuf origref = STRBUF_INIT;+ int i;+ struct worktree **worktrees = get_worktrees();++ for (i = 0; worktrees[i]; i++) {+ if (worktrees[i]->is_detached)+ continue;++ strbuf_reset(&path);+ strbuf_reset(&origref);+ strbuf_addf(&path, "%s/HEAD", worktrees[i]->git_dir);++ if (parse_ref(path.buf, &origref, NULL))+ continue;++ if (!strcmp(origref.buf, oldref)) {+ int prefix_len = strlen(absolute_path(get_git_common_dir())) + 1;+ const char *symref = path.buf + prefix_len;++ /* no need to pass logmsg here as HEAD didn't really move */+ if (create_symref(symref, newref, NULL)) {+ error = -1;+ break;
Is aborting upon the first error desired behavior? (Genuine question.)
Would it make more sense to continue attempting the rename for the
remaining worktrees (and remember that an error was encountered)?
Since all these HEADs stay at the same (or close) location, if one
fails, I think the rest will fail too. Which leads to a series of
warnings if we continue anyway. A more interesting approach is update
HEADs in a transaction, so we successfully update all or we update
none. But I do not know if ref transactions can be used for HEAD,
especially worktree HEADs. I'm ok with either abort here or continue
anyway, though.
--
Duy
Thanks for suggestion, but it looks like ref_transaction can be used
only for updating non-symbolic references. Extending it only for this
purpose seems too much...
If the new flag RESOLVE_REF_COMMON_DIR is passed to resolve_ref_unsafe,
it assumes the refname belongs to $GIT_COMMON_DIR.
resolve_ref_unsafe currently has no way to resolve worktree-specific
refs such as HEAD of the main working tree when we are in a linked
working tree.
worktree.c has a simplified one for this purpose, and this patch allows
removing it.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
refs.h | 4 ++
refs/files-backend.c | 5 ++-
worktree.c | 105 ++++++++++++++++-----------------------------------
3 files changed, 41 insertions(+), 73 deletions(-)
@@ -16,53 +16,19 @@ void free_worktrees(struct worktree **worktrees)free(worktrees);}-/*-*read'path_to_ref'into'ref'.Alsoifis_detachedisnotNULL,-*setis_detachedto1(0)iftherefisdetatched(isnotdetached).-*-*$GIT_COMMON_DIR/$symref(e.g.HEAD)ispracticallyoutside$GIT_DIRso-*forlinkedworktrees,`resolve_ref_unsafe()`won'twork(ituses-*git_path).Parsetherefourselves.-*-*return-1iftherefisnotaproperref,0otherwise(success)-*/-staticintparse_ref(char*path_to_ref,structstrbuf*ref,int*is_detached)-{-if(is_detached)-*is_detached=0;-if(!strbuf_readlink(ref,path_to_ref,0)){-/* HEAD is symbolic link */-if(!starts_with(ref->buf,"refs/")||-check_refname_format(ref->buf,0))-return-1;-}elseif(strbuf_read_file(ref,path_to_ref,0)>=0){-/* textual symref or detached */-if(!starts_with(ref->buf,"ref:")){-if(is_detached)-*is_detached=1;-}else{-strbuf_remove(ref,0,strlen("ref:"));-strbuf_trim(ref);-if(check_refname_format(ref->buf,0))-return-1;-}-}else-return-1;-return0;-}-/**-*Addthehead_sha1andhead_ref(ifnotdetached)tothegivenworktree+*Addtheis_detached,head_sha1andhead_ref(ifnotdetached)tothegivenworktree*/-staticvoidadd_head_info(structstrbuf*head_ref,structworktree*worktree)+staticvoidadd_head_info(constchar*head_ref,constunsignedchar*sha1,+structworktree*worktree){-if(head_ref->len){-if(worktree->is_detached){-get_sha1_hex(head_ref->buf,worktree->head_sha1);-}else{-resolve_ref_unsafe(head_ref->buf,0,worktree->head_sha1,NULL);-worktree->head_ref=strbuf_detach(head_ref,NULL);-}+worktree->is_detached=!is_null_sha1(sha1);+if(worktree->is_detached){+hashcpy(worktree->head_sha1,sha1);+worktree->head_ref=NULL;+}else{+resolve_ref_unsafe(head_ref,0,worktree->head_sha1,NULL);+worktree->head_ref=xstrdup(head_ref);}}
[1/5]
Adds RESOLVE_REF_COMMON_DIR to resolve_ref_unsafe(). The second - fourth
patch depend on this. At the same time, this allows us to remove
reimplementation of resolve_ref_unsafe() in worktree.c: parse_ref().
[2/5]
Adds REF_COMMON_DIR flag to lock_ref_sha1_basic().
[3/5]
Adds create_symref_common_dir(). Same as create_symref() except it
doesn't consider $GIT_DIR. create_symref_common_dir("HEAD", some) always
updates .git/HEAD. The next patch uses this.
[4/5]
Fixes the issue of git branch -m.
The behavior when one failed has changed from v1: print an error and
continue.
% git branch -m oldname newname
error: Unable to create '/path/to/.git/worktrees/wt/HEAD.lock': Permission denied
error: HEAD of working tree /path/to/wt is not updated.
error: Unable to create '/path/to/.git/worktrees/wt2/HEAD.lock': Permission denied
error: HEAD of working tree /path/to/wt2 is not updated.
fatal: Branch renamed to newname, but HEAD is not updated!
[5/5]
Fixes an issue of git branch -d, v1 didn't include this.
I noticed git branch -d has same issue and this is for it.
This patch is unrelated to the above 4 patches, but the cause is same.
This can be applied separately.
Kazuki Yamaguchi (5):
refs: add new flag RESOLVE_REF_COMMON_DIR to resolve_ref_unsafe
refs: add REF_COMMON_DIR flag
refs: add create_symref_common_dir as a variation of create_symref
branch -m: update all per-worktree HEADs
branch -d: refuse deleting a branch which is currently checked out
branch.c | 32 ++++++++++++++++
branch.h | 7 ++++
builtin/branch.c | 15 ++++----
refs.h | 11 ++++++
refs/files-backend.c | 34 ++++++++++++++---
t/t3200-branch.sh | 29 +++++++++++++-
worktree.c | 105 ++++++++++++++++-----------------------------------
7 files changed, 147 insertions(+), 86 deletions(-)
--
2.8.0.rc4.21.g05df949
Add a new function create_symref_common_dir. This function passes
REF_COMMON_DIR to lock_ref_sha1_basic, unlike create_symref, so to make
it possible to update main working tree's per-worktree symbolic refs
(HEAD) when we are in a linked working tree.
Assume we have a linked working tree and we are in it. If we call
create_symref("HEAD", "refs/heads/branch-a", NULL), this updates the
working tree's HEAD, located at .git/worktrees/tree-a/HEAD, rather than
the main working tree's HEAD, .git/HEAD.
The new function create_symref_common_dir always updates the main
working tree's HEAD regardless of where we are.
This will be needed when renaming a branch.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
refs.h | 3 +++
refs/files-backend.c | 17 ++++++++++++++---
2 files changed, 17 insertions(+), 3 deletions(-)
@@ -312,7 +312,10 @@ extern char *shorten_unambiguous_ref(const char *refname, int strict);/** rename ref, return 0 on success **/externintrename_ref(constchar*oldref,constchar*newref,constchar*logmsg);+/* create or update a symref */externintcreate_symref(constchar*refname,constchar*target,constchar*logmsg);+/* same as create_symref, but refname is always $GIT_COMMON_DIR/refname */+externintcreate_symref_common_dir(constchar*refname,constchar*target,constchar*logmsg);enumaction_on_err{UPDATE_REFS_MSG_ON_ERR,
When a branch is checked out by current working tree, deleting the
branch is forbidden. However when the branch is checked out only by
other working trees, deleting is allowed.
Use find_shared_symref() to check if the branch is in use, not just
comparing with the current working tree's HEAD.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
builtin/branch.c | 12 +++++++-----
t/t3200-branch.sh | 6 ++++++
2 files changed, 13 insertions(+), 5 deletions(-)
@@ -215,16 +216,17 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,intflags=0;strbuf_branchname(&bname,argv[i]);-if(kinds==FILTER_REFS_BRANCHES&&!strcmp(head,bname.buf)){+free(name);+name=mkpathdup(fmt,bname.buf);++if(kinds==FILTER_REFS_BRANCHES&&+find_shared_symref("HEAD",name)){error(_("Cannot delete the branch '%s' "-"which you are currently on."),bname.buf);+"which is currently checked out."),bname.buf);ret=1;continue;}-free(name);--name=mkpathdup(fmt,bname.buf);target=resolve_ref_unsafe(name,RESOLVE_REF_READING|RESOLVE_REF_NO_RECURSE
Add a flag to force using $GIT_COMMON_DIR, instead of selecting $GIT_DIR
or $GIT_COMMON_DIR by refname.
This allows updating worktree-specific refs of the main working tree
from a linked working tree. We will use this later.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
refs.h | 4 ++++
refs/files-backend.c | 12 ++++++++++--
2 files changed, 14 insertions(+), 2 deletions(-)
When renaming a branch, the current code only updates the current
working tree's HEAD, but it should update .git/HEAD of all checked out
working trees.
This is the current behavior, /path/to/wt's HEAD is not updated:
% git worktree list
/path/to 2c3c5f2 [master]
/path/to/wt 2c3c5f2 [oldname]
% git branch -m master master2
% git worktree list
/path/to 2c3c5f2 [master2]
/path/to/wt 2c3c5f2 [oldname]
% git branch -m oldname newname
% git worktree list
/path/to 2c3c5f2 [master2]
/path/to/wt 0000000 [oldname]
This patch fixes this issue by updating all relevant worktree HEADs
when renaming a branch.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
branch.c | 32 ++++++++++++++++++++++++++++++++
branch.h | 7 +++++++
builtin/branch.c | 3 +--
t/t3200-branch.sh | 23 ++++++++++++++++++++++-
4 files changed, 62 insertions(+), 3 deletions(-)
@@ -344,3 +344,35 @@ void die_if_checked_out(const char *branch)die(_("'%s' is already checked out at '%s'"),branch,existing);}}++intupdate_worktrees_head_symref(constchar*oldref,constchar*newref)+{+intret=0;+structstrbufsymref=STRBUF_INIT;+structworktree**worktrees=get_worktrees();+inti;+intcommon_prefix_len=strlen(absolute_path(get_git_common_dir()))+1;++for(i=0;worktrees[i];i++){+if(worktrees[i]->is_detached)+continue;++if(strcmp(oldref,worktrees[i]->head_ref))+continue;++strbuf_reset(&symref);+strbuf_addf(&symref,"%s/HEAD",worktrees[i]->git_dir);+strbuf_remove(&symref,0,common_prefix_len);++if(create_symref_common_dir(symref.buf,newref,NULL)){+ret=-1;+error(_("HEAD of working tree %s is not updated."),+worktrees[i]->path);+}+}++strbuf_release(&symref);+free_worktrees(worktrees);++returnret;+}
@@ -552,8 +552,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)if(recovery)warning(_("Renamed a misnamed branch '%s' away"),oldref.buf+11);-/* no need to pass logmsg here as HEAD didn't really move */-if(!strcmp(oldname,head)&&create_symref("HEAD",newref.buf,NULL))+if(update_worktrees_head_symref(oldref.buf,newref.buf))die(_("Branch renamed to %s, but HEAD is not updated!"),newname);strbuf_addf(&oldsection,"branch.%s",oldref.buf+11);
@@ -126,7 +126,28 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out' test_expect_success'git branch -M baz bam should succeed when baz is checked out''gitcheckout-bbaz&&gitbranchbam&&-gitbranch-Mbazbam+gitbranch-Mbazbam&&+test$(gitrev-parse--abbrev-refHEAD)=bam+'++test_expect_success'git branch -M baz bam should succeed when baz is checked out as linked working tree''+gitcheckoutmaster&&+gitworktreeadd-bbazbazdir&&+gitworktreeadd-fbazdir2baz&&+gitbranch-Mbazbam&&+test$(git-Cbazdirrev-parse--abbrev-refHEAD)=bam&&+test$(git-Cbazdir2rev-parse--abbrev-refHEAD)=bam+'++test_expect_success'git branch -M baz bam should succeed within a worktree in which baz is checked out''+gitcheckout-bbaz&&+gitworktreeadd-fbazdir3baz&&+(+cdbazdir3&&+gitbranch-Mbazbam&&+test$(gitrev-parse--abbrev-refHEAD)=bam+)&&+test$(gitrev-parse--abbrev-refHEAD)=bam' test_expect_success'git branch -M master should work when master is checked out''
Add a new function set_worktree_head_symref, to update HEAD symref for
the specified worktree.
To update HEAD of a linked working tree,
create_symref("worktrees/$work_tree/HEAD", "refs/heads/$branch", msg)
could be used. However when it comes to updating HEAD of the main
working tree, it is unusable because it uses $GIT_DIR for
worktree-specific symrefs (HEAD).
The new function takes git_dir (real directory) as an argument, and
updates HEAD of the working tree. This function will be used when
renaming a branch.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
refs.h | 8 ++++++++
refs/files-backend.c | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+)
@@ -2894,6 +2894,41 @@ int create_symref(const char *refname, const char *target, const char *logmsg)returnret;}+intset_worktree_head_symref(constchar*gitdir,constchar*target)+{+staticstructlock_filehead_lock;+structref_lock*lock;+structstrbuferr=STRBUF_INIT;+structstrbufhead_path=STRBUF_INIT;+constchar*head_rel;+intret;++strbuf_addf(&head_path,"%s/HEAD",absolute_path(gitdir));+if(hold_lock_file_for_update(&head_lock,head_path.buf,+LOCK_NO_DEREF)<0){+error("%s",err.buf);+strbuf_release(&err);+strbuf_release(&head_path);+return-1;+}++/* head_rel will be "HEAD" for the main tree, "worktrees/wt/HEAD" for+linkedtrees*/+head_rel=remove_leading_path(head_path.buf,+absolute_path(get_git_common_dir()));+/* to make use of create_symref_locked(), initialize ref_lock */+lock=xcalloc(1,sizeof(structref_lock));+lock->lk=&head_lock;+lock->ref_name=xstrdup(head_rel);+lock->orig_ref_name=xstrdup(head_rel);++ret=create_symref_locked(lock,head_rel,target,NULL);++unlock_ref(lock);/* will free lock */+strbuf_release(&head_path);+returnret;+}+intreflog_exists(constchar*refname){structstatst;
Changes from v2:
- The flags REF_COMMON_DIR and RESOLVE_REF_COMMON_DIR are removed.
- create_symref_common_dir() is removed and instead adds narrower
purpose function, set_worktree_head_symref().
[1/2]
Adds a new function set_worktree_head_symref(). This takes git_dir as
the first argument, and updates {git_dir}/HEAD.
The new function uses hold_lock_file_for_update() directly, instead of
through lock_ref_sha1_basic() which the old [v2 3/5] used.
[2/2] (from [v2 4/5])
Uses the new set_worktree_head_symref(), and the
update_worktrees_head_symref() function was renamed to
replace_each_worktree_head_symref(), to avoid confusion with
set_worktree_head_symref() added by [1/2].
Thanks,
Kazuki Yamaguchi (2):
refs: add a new function set_worktree_head_symref
branch -m: update all per-worktree HEADs
branch.c | 23 +++++++++++++++++++++++
branch.h | 7 +++++++
builtin/branch.c | 3 +--
refs.h | 8 ++++++++
refs/files-backend.c | 35 +++++++++++++++++++++++++++++++++++
t/t3200-branch.sh | 23 ++++++++++++++++++++++-
6 files changed, 96 insertions(+), 3 deletions(-)
--
2.8.0.rc4.21.g05df949
When renaming a branch, currently only the HEAD of current working tree
is updated, but it must update HEADs of all working trees which point at
the old branch.
This is the current behavior, /path/to/wt's HEAD is not updated:
% git worktree list
/path/to 2c3c5f2 [master]
/path/to/wt 2c3c5f2 [oldname]
% git branch -m master master2
% git worktree list
/path/to 2c3c5f2 [master2]
/path/to/wt 2c3c5f2 [oldname]
% git branch -m oldname newname
% git worktree list
/path/to 2c3c5f2 [master2]
/path/to/wt 0000000 [oldname]
This patch fixes this issue by updating all relevant worktree HEADs
when renaming a branch.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
branch.c | 23 +++++++++++++++++++++++
branch.h | 7 +++++++
builtin/branch.c | 3 +--
t/t3200-branch.sh | 23 ++++++++++++++++++++++-
4 files changed, 53 insertions(+), 3 deletions(-)
@@ -344,3 +344,26 @@ void die_if_checked_out(const char *branch)die(_("'%s' is already checked out at '%s'"),branch,existing);}}++intreplace_each_worktree_head_symref(constchar*oldref,constchar*newref)+{+intret=0;+structworktree**worktrees=get_worktrees();+inti;++for(i=0;worktrees[i];i++){+if(worktrees[i]->is_detached)+continue;+if(strcmp(oldref,worktrees[i]->head_ref))+continue;++if(set_worktree_head_symref(worktrees[i]->git_dir,newref)){+ret=-1;+error(_("HEAD of working tree %s is not updated"),+worktrees[i]->path);+}+}++free_worktrees(worktrees);+returnret;+}
@@ -552,8 +552,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)if(recovery)warning(_("Renamed a misnamed branch '%s' away"),oldref.buf+11);-/* no need to pass logmsg here as HEAD didn't really move */-if(!strcmp(oldname,head)&&create_symref("HEAD",newref.buf,NULL))+if(replace_each_worktree_head_symref(oldref.buf,newref.buf))die(_("Branch renamed to %s, but HEAD is not updated!"),newname);strbuf_addf(&oldsection,"branch.%s",oldref.buf+11);
@@ -126,7 +126,28 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out' test_expect_success'git branch -M baz bam should succeed when baz is checked out''gitcheckout-bbaz&&gitbranchbam&&-gitbranch-Mbazbam+gitbranch-Mbazbam&&+test$(gitrev-parse--abbrev-refHEAD)=bam+'++test_expect_success'git branch -M baz bam should succeed when baz is checked out as linked working tree''+gitcheckoutmaster&&+gitworktreeadd-bbazbazdir&&+gitworktreeadd-fbazdir2baz&&+gitbranch-Mbazbam&&+test$(git-Cbazdirrev-parse--abbrev-refHEAD)=bam&&+test$(git-Cbazdir2rev-parse--abbrev-refHEAD)=bam+'++test_expect_success'git branch -M baz bam should succeed within a worktree in which baz is checked out''+gitcheckout-bbaz&&+gitworktreeadd-fbazdir3baz&&+(+cdbazdir3&&+gitbranch-Mbazbam&&+test$(gitrev-parse--abbrev-refHEAD)=bam+)&&+test$(gitrev-parse--abbrev-refHEAD)=bam' test_expect_success'git branch -M master should work when master is checked out''
From: Eric Sunshine <hidden> Date: 2016-06-15 23:09:04
On Fri, Mar 25, 2016 at 2:28 PM, Kazuki Yamaguchi [off-list ref] wrote:
quoted hunk
When a branch is checked out by current working tree, deleting the
branch is forbidden. However when the branch is checked out only by
other working trees, deleting is allowed.
Use find_shared_symref() to check if the branch is in use, not just
comparing with the current working tree's HEAD.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
diff --git a/builtin/branch.c b/builtin/branch.c
@@ -215,16 +216,17 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int flags = 0; strbuf_branchname(&bname, argv[i]);- if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) {+ free(name);+ name = mkpathdup(fmt, bname.buf);++ if (kinds == FILTER_REFS_BRANCHES &&+ find_shared_symref("HEAD", name)) { error(_("Cannot delete the branch '%s' "- "which you are currently on."), bname.buf);+ "which is currently checked out."), bname.buf);
Would it be possible to do a better job of letting the user know what
went wrong by stating in which worktree(s) the branch is checked out?
My concern is that someone seeing this message might respond "huh? I
have 'master' checked out, so why is this telling me that 'foo' is
checked out", and not realize that 'foo' is in fact checked out in a
different worktree.
On Sun, Mar 27, 2016 at 01:52:18PM -0400, Eric Sunshine wrote:
On Fri, Mar 25, 2016 at 2:28 PM, Kazuki Yamaguchi [off-list ref] wrote:
quoted
When a branch is checked out by current working tree, deleting the
branch is forbidden. However when the branch is checked out only by
other working trees, deleting is allowed.
Use find_shared_symref() to check if the branch is in use, not just
comparing with the current working tree's HEAD.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
diff --git a/builtin/branch.c b/builtin/branch.c
@@ -215,16 +216,17 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int flags = 0; strbuf_branchname(&bname, argv[i]);- if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) {+ free(name);+ name = mkpathdup(fmt, bname.buf);++ if (kinds == FILTER_REFS_BRANCHES &&+ find_shared_symref("HEAD", name)) { error(_("Cannot delete the branch '%s' "- "which you are currently on."), bname.buf);+ "which is currently checked out."), bname.buf);
Would it be possible to do a better job of letting the user know what
went wrong by stating in which worktree(s) the branch is checked out?
My concern is that someone seeing this message might respond "huh? I
have 'master' checked out, so why is this telling me that 'foo' is
checked out", and not realize that 'foo' is in fact checked out in a
different worktree.
When a branch is checked out by current working tree, deleting the
branch is forbidden. However when the branch is checked out only by
other working trees, deleting is allowed.
Use find_shared_symref() to check if the branch is in use, not just
comparing with the current working tree's HEAD.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
% git worktree list
/path/to 2c3c5f2 [master]
/path/to/wt 2c3c5f2 [branch-a]
% git branch -d branch-a
error: Cannot delete the branch 'branch-a' which is currently checked out at '/path/to/wt'
builtin/branch.c | 22 ++++++++++++++--------
t/t3200-branch.sh | 6 ++++++
2 files changed, 20 insertions(+), 8 deletions(-)
@@ -215,16 +216,21 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,intflags=0;strbuf_branchname(&bname,argv[i]);-if(kinds==FILTER_REFS_BRANCHES&&!strcmp(head,bname.buf)){-error(_("Cannot delete the branch '%s' "-"which you are currently on."),bname.buf);-ret=1;-continue;-}-free(name);-name=mkpathdup(fmt,bname.buf);++if(kinds==FILTER_REFS_BRANCHES){+char*worktree=find_shared_symref("HEAD",name);+if(worktree){+error(_("Cannot delete the branch '%s' "+"which is currently checked out at '%s'"),+bname.buf,worktree);+free(worktree);+ret=1;+continue;+}+}+target=resolve_ref_unsafe(name,RESOLVE_REF_READING|RESOLVE_REF_NO_RECURSE
From: Eric Sunshine <hidden> Date: 2016-06-15 23:09:04
On Mon, Mar 28, 2016 at 3:22 AM, Kazuki Yamaguchi [off-list ref] wrote:
When a branch is checked out by current working tree, deleting the
branch is forbidden. However when the branch is checked out only by
other working trees, deleting is allowed.
It's not quite clear from this description that it is bad for deletion
to succeed in the second case. Perhaps:
s/deleting is allowed/deletion incorrectly succeeds/
would make it more clear.
Use find_shared_symref() to check if the branch is in use, not just
comparing with the current working tree's HEAD.
This version of the patch is nicer. Thanks. See a couple minor
comments below which may or may not be worth a re-roll (you decide).
Signed-off-by: Kazuki Yamaguchi <redacted>
---
% git worktree list
/path/to 2c3c5f2 [master]
/path/to/wt 2c3c5f2 [branch-a]
% git branch -d branch-a
error: Cannot delete the branch 'branch-a' which is currently checked out at '/path/to/wt'
@@ -215,16 +216,21 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int flags = 0; strbuf_branchname(&bname, argv[i]);- if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) {- error(_("Cannot delete the branch '%s' "- "which you are currently on."), bname.buf);- ret = 1;- continue;- }- free(name);- name = mkpathdup(fmt, bname.buf);++ if (kinds == FILTER_REFS_BRANCHES) {+ char *worktree = find_shared_symref("HEAD", name);+ if (worktree) {+ error(_("Cannot delete the branch '%s' "+ "which is currently checked out at '%s'"),
This could be stated more concisely as:
"Cannot delete branch '%s' checked out at '%s'"
+ bname.buf, worktree);
+ free(worktree);
Would it make sense to show all worktrees at which this branch is
checked out, rather than only one, or is that not worth the effort and
extra code ugliness?
On Mon, Mar 28, 2016 at 12:51:21PM -0400, Eric Sunshine wrote:
On Mon, Mar 28, 2016 at 3:22 AM, Kazuki Yamaguchi [off-list ref] wrote:
quoted
When a branch is checked out by current working tree, deleting the
branch is forbidden. However when the branch is checked out only by
other working trees, deleting is allowed.
It's not quite clear from this description that it is bad for deletion
to succeed in the second case. Perhaps:
s/deleting is allowed/deletion incorrectly succeeds/
would make it more clear.
Thanks.
quoted
Use find_shared_symref() to check if the branch is in use, not just
comparing with the current working tree's HEAD.
This version of the patch is nicer. Thanks. See a couple minor
comments below which may or may not be worth a re-roll (you decide).
quoted
Signed-off-by: Kazuki Yamaguchi <redacted>
---
% git worktree list
/path/to 2c3c5f2 [master]
/path/to/wt 2c3c5f2 [branch-a]
% git branch -d branch-a
error: Cannot delete the branch 'branch-a' which is currently checked out at '/path/to/wt'
@@ -215,16 +216,21 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int flags = 0; strbuf_branchname(&bname, argv[i]);- if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) {- error(_("Cannot delete the branch '%s' "- "which you are currently on."), bname.buf);- ret = 1;- continue;- }- free(name);- name = mkpathdup(fmt, bname.buf);++ if (kinds == FILTER_REFS_BRANCHES) {+ char *worktree = find_shared_symref("HEAD", name);+ if (worktree) {+ error(_("Cannot delete the branch '%s' "+ "which is currently checked out at '%s'"),
This could be stated more concisely as:
"Cannot delete branch '%s' checked out at '%s'"
I'll use it. Thanks.
quoted
+ bname.buf, worktree);
+ free(worktree);
Would it make sense to show all worktrees at which this branch is
checked out, rather than only one, or is that not worth the effort and
extra code ugliness?
I thought one is enough.
I think the worktrees usually won't be more than one, considering
"git worktree add" requires additional option to check out an already
checked out branch. Also, since the branch is not actually deleted at
that time, the user can safely retry after checking "git worktree list".
Thanks,
When a branch is checked out by current working tree, deleting the
branch is forbidden. However when the branch is checked out only by
other working trees, deleting incorrectly succeeds.
Use find_shared_symref() to check if the branch is in use, not just
comparing with the current working tree's HEAD.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Kazuki Yamaguchi <redacted>
---
Changes from v2:
- Amended commit message
- Dropped "which is" from error message
The previous versions of the patch are:
- [v1] http://thread.gmane.org/gmane.comp.version-control.git/289413/focus=289932
- [v2] http://thread.gmane.org/gmane.comp.version-control.git/289413/focus=290027
builtin/branch.c | 22 ++++++++++++++--------
t/t3200-branch.sh | 6 ++++++
2 files changed, 20 insertions(+), 8 deletions(-)
@@ -215,16 +216,21 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,intflags=0;strbuf_branchname(&bname,argv[i]);-if(kinds==FILTER_REFS_BRANCHES&&!strcmp(head,bname.buf)){-error(_("Cannot delete the branch '%s' "-"which you are currently on."),bname.buf);-ret=1;-continue;-}-free(name);-name=mkpathdup(fmt,bname.buf);++if(kinds==FILTER_REFS_BRANCHES){+char*worktree=find_shared_symref("HEAD",name);+if(worktree){+error(_("Cannot delete branch '%s' "+"checked out at '%s'"),+bname.buf,worktree);+free(worktree);+ret=1;+continue;+}+}+target=resolve_ref_unsafe(name,RESOLVE_REF_READING|RESOLVE_REF_NO_RECURSE
From: Eric Sunshine <hidden> Date: 2016-06-15 23:09:05
On Tue, Mar 29, 2016 at 5:28 AM, Kazuki Yamaguchi [off-list ref] wrote:
On Mon, Mar 28, 2016 at 12:51:21PM -0400, Eric Sunshine wrote:
quoted
On Mon, Mar 28, 2016 at 3:22 AM, Kazuki Yamaguchi [off-list ref] wrote:
quoted
+ if (kinds == FILTER_REFS_BRANCHES) {
+ char *worktree = find_shared_symref("HEAD", name);
+ if (worktree) {
+ error(_("Cannot delete the branch '%s' "
+ "which is currently checked out at '%s'"),
+ bname.buf, worktree);
+ free(worktree);
Would it make sense to show all worktrees at which this branch is
checked out, rather than only one, or is that not worth the effort and
extra code ugliness?
I thought one is enough.
I think the worktrees usually won't be more than one, considering
"git worktree add" requires additional option to check out an already
checked out branch. Also, since the branch is not actually deleted at
that time, the user can safely retry after checking "git worktree list".
Fair enough. A more thorough error message can be done a future
enhancement if there is a need for it.
From: Eric Sunshine <hidden> Date: 2016-06-15 23:09:05
On Tue, Mar 29, 2016 at 5:38 AM, Kazuki Yamaguchi [off-list ref] wrote:
When a branch is checked out by current working tree, deleting the
branch is forbidden. However when the branch is checked out only by
other working trees, deleting incorrectly succeeds.
Use find_shared_symref() to check if the branch is in use, not just
comparing with the current working tree's HEAD.
Helped-by: Eric Sunshine [off-list ref]
Signed-off-by: Kazuki Yamaguchi <redacted>
---
Changes from v2:
- Amended commit message
- Dropped "which is" from error message
Thanks, this version addresses my previous review comments and is:
Reviewed-by: Eric Sunshine [off-list ref]
@@ -215,16 +216,21 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,intflags=0;strbuf_branchname(&bname,argv[i]);-if(kinds==FILTER_REFS_BRANCHES&&!strcmp(head,bname.buf)){-error(_("Cannot delete the branch '%s' "-"which you are currently on."),bname.buf);-ret=1;-continue;-}-free(name);-name=mkpathdup(fmt,bname.buf);++if(kinds==FILTER_REFS_BRANCHES){+char*worktree=find_shared_symref("HEAD",name);+if(worktree){+error(_("Cannot delete branch '%s' "+"checked out at '%s'"),+bname.buf,worktree);+free(worktree);+ret=1;+continue;+}+}+target=resolve_ref_unsafe(name,RESOLVE_REF_READING|RESOLVE_REF_NO_RECURSE
From: Eric Sunshine <hidden> Date: 2016-06-16 02:18:42
On Sun, Mar 27, 2016 at 10:37 AM, Kazuki Yamaguchi [off-list ref] wrote:
quoted hunk
Add a new function set_worktree_head_symref, to update HEAD symref for
the specified worktree.
To update HEAD of a linked working tree,
create_symref("worktrees/$work_tree/HEAD", "refs/heads/$branch", msg)
could be used. However when it comes to updating HEAD of the main
working tree, it is unusable because it uses $GIT_DIR for
worktree-specific symrefs (HEAD).
The new function takes git_dir (real directory) as an argument, and
updates HEAD of the working tree. This function will be used when
renaming a branch.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
On Thu, Apr 07, 2016 at 05:20:10PM -0400, Eric Sunshine wrote:
On Sun, Mar 27, 2016 at 10:37 AM, Kazuki Yamaguchi [off-list ref] wrote:
quoted
Add a new function set_worktree_head_symref, to update HEAD symref for
the specified worktree.
To update HEAD of a linked working tree,
create_symref("worktrees/$work_tree/HEAD", "refs/heads/$branch", msg)
could be used. However when it comes to updating HEAD of the main
working tree, it is unusable because it uses $GIT_DIR for
worktree-specific symrefs (HEAD).
The new function takes git_dir (real directory) as an argument, and
updates HEAD of the working tree. This function will be used when
renaming a branch.
Signed-off-by: Kazuki Yamaguchi <redacted>
---
'err' has not been populated at this point, so I suspect that this
error message is likely to be rather uninformative.
Yes, unable_to_lock_message() is missing. Thank you for pointing it out.
You're welcome. As this patch is already in Junio's "next" branch, if
you post a fix, it should be incremental atop "ky/branch-m-worktree",
rather than as a re-roll of this series.