When a branch does not yet have a commit, we call that an orphan branch. More technically, an orphan branch is a HEAD that points to a nonexistent ref. Orphan branches are not normal branches; can only be created, we cannot simply switch to them. The initial branch in an empty repository is an orphan branch. We can create new orphan branches using "switch --orphan". An orphan branch becomes an ordinary branch when the ref it points to is created, i.e. when the first commit for the branch is done or the branch is reset to an existing commit. When we are asked to create a new branch, orphan or not, we use validate_branchname() to check if a branch already exists in the repository with the same desired name for the new branch. In that function we use ref_exists(), so we are not considering any orphan branches as there is no ref related to an orphan branch. If we switch from an orphan branch to a different one, orphan or not, the initial orphan branch is simply lost. We cannot switch back to it, there is nothing to switch back to. Therefore, there is no major problem if we only check for valid refs when creating branches, orphans or not, as there is no orphan branches to consider in the worktree. Since 529fef20 (checkout: support checking out into a new working directory, 2014-12-01) we have a safe way to use multiple worktrees with a Git repository. This allows the possibility of having multiple (ordinary and orphan) branches simultaneously, and since we do not check for orphan branches in validate_branchname(), the same orphan branch name can be used multiple times. Once any one of them becomes an ordinary branch, or a new ordinary branch with the same name is created, all of them will be /switched/ to that new branch. It also opens the possibility to copy or rename a normal branch to a name currently used by an orphan branch, with similar results. Since 31ad6b61bd (branch: add branch_checked_out() helper, 2022-06-15) we have a convenient way to see if a branch is checked out in any worktree. Let's use branch_checked_out() in validate_branchname() to prevent using the same branch name multiple times, considering orphan branches too. Signed-off-by: Rubén Justo <rjusto@xxxxxxxxx> --- branch.c | 2 +- t/t2400-worktree-add.sh | 10 ++++++++++ t/t3200-branch.sh | 10 ++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/branch.c b/branch.c index d182756827..4029721806 100644 --- a/branch.c +++ b/branch.c @@ -367,7 +367,7 @@ int validate_branchname(const char *name, struct strbuf *ref) if (strbuf_check_branch_ref(ref, name)) die(_("'%s' is not a valid branch name"), name); - return ref_exists(ref->buf); + return ref_exists(ref->buf) || branch_checked_out(ref->buf); } static int initialized_checked_out_branches; diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index d587e0b20d..f1e4b605da 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -118,6 +118,16 @@ test_expect_success '"add" worktree creating new branch' ' ) ' +test_expect_success 'do not allow multiple worktrees with same orphan branch' ' + test_when_finished "git worktree remove --force detached-wt-A" && + test_when_finished "git worktree remove --force detached-wt-B" && + git worktree add --detach detached-wt-A && + git -C detached-wt-A checkout --orphan orphan-branch && + git worktree add --detach detached-wt-B && + test_must_fail git -C detached-wt-B checkout --orphan orphan-branch && + test_must_fail git checkout --orphan orphan-branch +' + test_expect_success 'die the same branch is already checked out' ' ( cd here && diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 5a169b68d6..68bc579fff 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -192,6 +192,16 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out i test_must_fail git branch -M bar wt ' +test_expect_success 'git branch -M/-C bar should fail when destination exists as orphan' ' + test_when_finished "git worktree remove --force orphan-worktree" && + git worktree add --detach orphan-worktree && + git -C orphan-worktree checkout --orphan orphan-branch && + test_must_fail git checkout --orphan orphan-branch && + test_must_fail git branch orphan-branch && + test_must_fail git branch -M orphan-branch && + test_must_fail git branch -C orphan-branch +' + test_expect_success 'git branch -M baz bam should succeed when baz is checked out' ' git checkout -b baz && git branch bam && -- 2.36.1