From 2233066e778e32dfab0471ea2ad8d1c7a94b7e39 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 27 Mar 2016 23:37:13 +0900 Subject: refs: add a new function set_worktree_head_symref 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 Acked-by: David Turner Signed-off-by: Junio C Hamano --- refs.h | 9 +++++++++ refs/files-backend.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/refs.h b/refs.h index 2f3decb432..9230d47142 100644 --- a/refs.h +++ b/refs.h @@ -306,6 +306,15 @@ 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 can update the main working tree's HEAD regardless of where + * $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 81f68f846b..ec237efec3 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; -- cgit v1.2.3 From 70999e9ceca47e03b8900bfb310b2f804125811e Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 27 Mar 2016 23:37:14 +0900 Subject: 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 Signed-off-by: Junio C Hamano --- branch.c | 23 +++++++++++++++++++++++ branch.h | 7 +++++++ builtin/branch.c | 3 +-- t/t3200-branch.sh | 23 ++++++++++++++++++++++- 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/branch.c b/branch.c index c50ea42172..4162443707 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 78ad4387cd..d69163daf7 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 7b45b6bd6b..b173831317 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 a897248490..f7d438bd7d 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' ' -- cgit v1.2.3 From 18eb3a9ce7c544e74d424b942c5a5c9720c20112 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 8 Apr 2016 17:03:07 +0900 Subject: set_worktree_head_symref(): fix error message Emit an informative error when failed to hold lock of HEAD. 2233066e (refs: add a new function set_worktree_head_symref, 2016-03-27) added set_worktree_head_symref(), but this is missing a call to unable_to_lock_message() after hold_lock_file_for_update() fails, so it emits an empty error message: % git branch -m oldname newname error: error: HEAD of working tree /path/to/wt is not updated fatal: Branch renamed to newname, but HEAD is not updated! Thanks to Eric Sunshine for pointing this out. Signed-off-by: Kazuki Yamaguchi Signed-off-by: Junio C Hamano --- refs/files-backend.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index ec237efec3..ea78ce9d90 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2898,7 +2898,6 @@ 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; @@ -2906,6 +2905,8 @@ int set_worktree_head_symref(const char *gitdir, const char *target) strbuf_addf(&head_path, "%s/HEAD", absolute_path(gitdir)); if (hold_lock_file_for_update(&head_lock, head_path.buf, LOCK_NO_DEREF) < 0) { + struct strbuf err = STRBUF_INIT; + unable_to_lock_message(head_path.buf, errno, &err); error("%s", err.buf); strbuf_release(&err); strbuf_release(&head_path); -- cgit v1.2.3