threads / patch / 41838

v3, 2 partsrefs: add a new function set_worktree_head_symref

Subject: [PATCH v3 1/2] refs: add a new function set_worktree_head_symref

## tl;dr

8 messages between Mar 27, 2016 and Apr 8, 2016. Diffs are folded; open one to read it.

replies: 7people: 4as markdown or json

Kazuki Yamaguchi· Mar 27, 2016, 14:37 UTC · lore

[PATCH v3 0/2] update all per-worktree HEADs when renaming a branch

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
Kazuki Yamaguchi· Mar 27, 2016, 14:37 UTC · re: Kazuki Yamaguchi · lore

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 <k@rhe.jp>
---
 refs.h               |  8 ++++++++
 refs/files-backend.c | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)
Show changes to 2 files +43 −0

refs.h, refs/files-backend.c

diff --git a/refs.h b/refs.h
index 2f3decb432cf..f11154cf5faf 100644
--- a/refs.h
+++ b/refs.h
@@ -306,6 +306,14 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg
 
 extern int create_symref(const char *refname, const char *target, const char *logmsg);
 
+/*
+ * Update HEAD of the specified gitdir.
+ * Similar to create_symref("relative-git-dir/HEAD", target, NULL), but this is
+ * able to update the main working tree's HEAD wherever $GIT_DIR points to.
+ * Return 0 if successful, non-zero otherwise.
+ * */
+extern int set_worktree_head_symref(const char *gitdir, const char *target);
+
 enum action_on_err {
 	UPDATE_REFS_MSG_ON_ERR,
 	UPDATE_REFS_DIE_ON_ERR,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 81f68f846b69..ec237efec35d 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2894,6 +2894,41 @@ int create_symref(const char *refname, const char *target, const char *logmsg)
 	return ret;
 }
 
+int set_worktree_head_symref(const char *gitdir, const char *target)
+{
+	static struct lock_file head_lock;
+	struct ref_lock *lock;
+	struct strbuf err = STRBUF_INIT;
+	struct strbuf head_path = STRBUF_INIT;
+	const char *head_rel;
+	int ret;
+
+	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
+	   linked trees */
+	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(struct ref_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);
+	return ret;
+}
+
 int reflog_exists(const char *refname)
 {
 	struct stat st;
-- 
2.8.0.rc4.21.g05df949
Junio C Hamano· Mar 28, 2016, 17:48 UTC · re: Kazuki Yamaguchi · lore

Re: [PATCH v3 1/2] refs: add a new function set_worktree_head_symref

Kazuki Yamaguchi <k@rhe.jp> writes:
Show 15 quoted lines
> 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 <k@rhe.jp>
> ---

I suspect that this helper function should be in the common layer (refs.c) to be redirected to specific backend(s).

David & Michael, what do you think?
Show 69 quoted lines
>  refs.h               |  8 ++++++++
>  refs/files-backend.c | 35 +++++++++++++++++++++++++++++++++++
>  2 files changed, 43 insertions(+)
>
> diff --git a/refs.h b/refs.h
> index 2f3decb432cf..f11154cf5faf 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -306,6 +306,14 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg
>  
>  extern int create_symref(const char *refname, const char *target, const char *logmsg);
>  
> +/*
> + * Update HEAD of the specified gitdir.
> + * Similar to create_symref("relative-git-dir/HEAD", target, NULL), but this is
> + * able to update the main working tree's HEAD wherever $GIT_DIR points to.
> + * Return 0 if successful, non-zero otherwise.
> + * */
> +extern int set_worktree_head_symref(const char *gitdir, const char *target);
> +
>  enum action_on_err {
>  	UPDATE_REFS_MSG_ON_ERR,
>  	UPDATE_REFS_DIE_ON_ERR,
> diff --git a/refs/files-backend.c b/refs/files-backend.c
> index 81f68f846b69..ec237efec35d 100644
> --- a/refs/files-backend.c
> +++ b/refs/files-backend.c
> @@ -2894,6 +2894,41 @@ int create_symref(const char *refname, const char *target, const char *logmsg)
>  	return ret;
>  }
>  
> +int set_worktree_head_symref(const char *gitdir, const char *target)
> +{
> +	static struct lock_file head_lock;
> +	struct ref_lock *lock;
> +	struct strbuf err = STRBUF_INIT;
> +	struct strbuf head_path = STRBUF_INIT;
> +	const char *head_rel;
> +	int ret;
> +
> +	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
> +	   linked trees */
> +	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(struct ref_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);
> +	return ret;
> +}
> +
>  int reflog_exists(const char *refname)
>  {
>  	struct stat st;
David Turner· Mar 29, 2016, 02:23 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 1/2] refs: add a new function set_worktree_head_symref

On Mon, 2016-03-28 at 10:48 -0700, Junio C Hamano wrote:
Show 24 quoted lines
> Kazuki Yamaguchi <k@rhe.jp> writes:
> 
> > 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 <k@rhe.jp>
> > ---
> 
> I suspect that this helper function should be in the common layer
> (refs.c) to be redirected to specific backend(s).
> 
> David & Michael, what do you think?

HEAD is a per-worktree ref, so it's always handled by the files backend. So this looks fine to me.

Show 6 quoted lines
> > +/*
> > + * Update HEAD of the specified gitdir.
> > + * Similar to create_symref("relative-git-dir/HEAD", target,
> > NULL), but this is
> > + * able to update the main working tree's HEAD wherever $GIT_DIR
> > points to.

nit: "able to update the main working tree's HEAD regardless of where GIT_DIR points to" would be clearer.

Eric Sunshine· Apr 7, 2016, 21:20 UTC · re: Kazuki Yamaguchi · lore

Re: [PATCH v3 1/2] refs: add a new function set_worktree_head_symref

On Sun, Mar 27, 2016 at 10:37 AM, Kazuki Yamaguchi <k@rhe.jp> wrote:
Show 30 quoted lines
> 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 <k@rhe.jp>
> ---
> diff --git a/refs/files-backend.c b/refs/files-backend.c
> @@ -2894,6 +2894,41 @@ int create_symref(const char *refname, const char *target, const char *logmsg)
> +int set_worktree_head_symref(const char *gitdir, const char *target)
> +{
> +       static struct lock_file head_lock;
> +       struct ref_lock *lock;
> +       struct strbuf err = STRBUF_INIT;
> +       struct strbuf head_path = STRBUF_INIT;
> +       const char *head_rel;
> +       int ret;
> +
> +       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);

'err' has not been populated at this point, so I suspect that this error message is likely to be rather uninformative.

Show 22 quoted lines
> +               strbuf_release(&err);
> +               strbuf_release(&head_path);
> +               return -1;
> +       }
> +
> +       /* head_rel will be "HEAD" for the main tree, "worktrees/wt/HEAD" for
> +          linked trees */
> +       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(struct ref_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);
> +       return ret;
> +}
> +
Kazuki Yamaguchi· Apr 8, 2016, 06:37 UTC · re: Eric Sunshine · lore

Re: [PATCH v3 1/2] refs: add a new function set_worktree_head_symref

On Thu, Apr 07, 2016 at 05:20:10PM -0400, Eric Sunshine wrote:
Show 34 quoted lines
> On Sun, Mar 27, 2016 at 10:37 AM, Kazuki Yamaguchi <k@rhe.jp> wrote:
> > 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 <k@rhe.jp>
> > ---
> > diff --git a/refs/files-backend.c b/refs/files-backend.c
> > @@ -2894,6 +2894,41 @@ int create_symref(const char *refname, const char *target, const char *logmsg)
> > +int set_worktree_head_symref(const char *gitdir, const char *target)
> > +{
> > +       static struct lock_file head_lock;
> > +       struct ref_lock *lock;
> > +       struct strbuf err = STRBUF_INIT;
> > +       struct strbuf head_path = STRBUF_INIT;
> > +       const char *head_rel;
> > +       int ret;
> > +
> > +       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);
> 
> '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.
Show 23 quoted lines
> 
> > +               strbuf_release(&err);
> > +               strbuf_release(&head_path);
> > +               return -1;
> > +       }
> > +
> > +       /* head_rel will be "HEAD" for the main tree, "worktrees/wt/HEAD" for
> > +          linked trees */
> > +       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(struct ref_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);
> > +       return ret;
> > +}
> > +
Eric Sunshine· Apr 8, 2016, 06:42 UTC · re: Kazuki Yamaguchi · lore

Re: [PATCH v3 1/2] refs: add a new function set_worktree_head_symref

On Fri, Apr 8, 2016 at 2:37 AM, Kazuki Yamaguchi <k@rhe.jp> wrote:
Show 20 quoted lines
> On Thu, Apr 07, 2016 at 05:20:10PM -0400, Eric Sunshine wrote:
>> On Sun, Mar 27, 2016 at 10:37 AM, Kazuki Yamaguchi <k@rhe.jp> wrote:
>> > +int set_worktree_head_symref(const char *gitdir, const char *target)
>> > +{
>> > +       static struct lock_file head_lock;
>> > +       struct ref_lock *lock;
>> > +       struct strbuf err = STRBUF_INIT;
>> > +       struct strbuf head_path = STRBUF_INIT;
>> > +       const char *head_rel;
>> > +       int ret;
>> > +
>> > +       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);
>>
>> '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.

Kazuki Yamaguchi· Mar 27, 2016, 14:37 UTC · re: Kazuki Yamaguchi · lore

[PATCH v3 2/2] branch -m: update all per-worktree HEADs

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 <k@rhe.jp>
---
 branch.c          | 23 +++++++++++++++++++++++
 branch.h          |  7 +++++++
 builtin/branch.c  |  3 +--
 t/t3200-branch.sh | 23 ++++++++++++++++++++++-
 4 files changed, 53 insertions(+), 3 deletions(-)
Show changes to 4 files +53 −3

branch.c, branch.h, builtin/branch.c, t/t3200-branch.sh

diff --git a/branch.c b/branch.c
index c50ea42172ce..416244370783 100644
--- a/branch.c
+++ b/branch.c
@@ -344,3 +344,26 @@ void die_if_checked_out(const char *branch)
 		die(_("'%s' is already checked out at '%s'"), branch, existing);
 	}
 }
+
+int replace_each_worktree_head_symref(const char *oldref, const char *newref)
+{
+	int ret = 0;
+	struct worktree **worktrees = get_worktrees();
+	int i;
+
+	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);
+	return ret;
+}
diff --git a/branch.h b/branch.h
index 78ad4387cd32..d69163daf793 100644
--- a/branch.h
+++ b/branch.h
@@ -60,4 +60,11 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
  */
 extern void die_if_checked_out(const char *branch);
 
+/*
+ * Update all per-worktree HEADs pointing at 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 replace_each_worktree_head_symref(const char *oldref, const char *newref);
+
 #endif
diff --git a/builtin/branch.c b/builtin/branch.c
index 7b45b6bd6b80..b1738313175c 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -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);
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index a89724849065..f7d438bd7d1d 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -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' '
 	git checkout -b baz &&
 	git branch bam &&
-	git branch -M baz bam
+	git branch -M baz bam &&
+	test $(git rev-parse --abbrev-ref HEAD) = bam
+'
+
+test_expect_success 'git branch -M baz bam should succeed when baz is checked out as linked working tree' '
+	git checkout master &&
+	git worktree add -b baz bazdir &&
+	git worktree add -f bazdir2 baz &&
+	git branch -M baz bam &&
+	test $(git -C bazdir rev-parse --abbrev-ref HEAD) = bam &&
+	test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam
+'
+
+test_expect_success 'git branch -M baz bam should succeed within a worktree in which baz is checked out' '
+	git checkout -b baz &&
+	git worktree add -f bazdir3 baz &&
+	(
+		cd bazdir3 &&
+		git branch -M baz bam &&
+		test $(git rev-parse --abbrev-ref HEAD) = bam
+	) &&
+	test $(git rev-parse --abbrev-ref HEAD) = bam
 '
 
 test_expect_success 'git branch -M master should work when master is checked out' '
-- 
2.8.0.rc4.21.g05df949

← back to recent threads