diff mbox series

branch: consider orphans when validate_branchname()

Message ID 4247f730-5592-de10-a238-5582eac2952d@gmail.com (mailing list archive)
State New, archived
Headers show
Series branch: consider orphans when validate_branchname() | expand

Commit Message

Rubén Justo Dec. 23, 2022, 12:58 a.m. UTC
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@gmail.com>
---
 branch.c                |  2 +-
 t/t2400-worktree-add.sh | 10 ++++++++++
 t/t3200-branch.sh       | 10 ++++++++++
 3 files changed, 21 insertions(+), 1 deletion(-)
diff mbox series

Patch

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 &&