diff options
-rw-r--r-- | branch.c | 23 | ||||
-rw-r--r-- | branch.h | 7 | ||||
-rw-r--r-- | builtin/branch.c | 3 | ||||
-rw-r--r-- | refs.h | 9 | ||||
-rw-r--r-- | refs/files-backend.c | 36 | ||||
-rwxr-xr-x | t/t3200-branch.sh | 23 |
6 files changed, 98 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); } } + +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; +} @@ -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 8885d9f8e2..de6df09ed3 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -558,8 +558,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); @@ -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..ea78ce9d90 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2894,6 +2894,42 @@ 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 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) { + 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); + 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; diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 508007fd37..f3e3b6cf2e 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' ' |