]> git.ipfire.org Git - thirdparty/git.git/commitdiff
checkout: forbid "-B <branch>" from touching a branch used elsewhere
authorJunio C Hamano <gitster@pobox.com>
Thu, 23 Nov 2023 06:00:31 +0000 (15:00 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 13 Dec 2023 15:48:17 +0000 (07:48 -0800)
"git checkout -B <branch> [<start-point>]", being a "forced" version
of "-b", switches to the <branch>, after optionally resetting its
tip to the <start-point>, even if the <branch> is in use in another
worktree, which is somewhat unexpected.

Protect the <branch> using the same logic that forbids "git checkout
<branch>" from touching a branch that is in use elsewhere.

This is a breaking change that may deserve backward compatibliity
warning in the Release Notes.  The "--ignore-other-worktrees" option
can be used as an escape hatch if the finger memory of existing
users depend on the current behaviour of "-B".

Reported-by: Willem Verstraeten <willem.verstraeten@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-checkout.txt
Documentation/git-switch.txt
builtin/checkout.c
t/t2060-switch.sh
t/t2400-worktree-add.sh

index 240c54639e8e85b5a3ecf1fb8fec0266614ec9b5..55a50b5b23a18fe2b991555253d781676806e987 100644 (file)
@@ -63,7 +63,9 @@ $ git checkout <branch>
 ------------
 +
 that is to say, the branch is not reset/created unless "git checkout" is
-successful.
+successful (e.g., when the branch is in use in another worktree, not
+just the current branch stays the same, but the branch is not reset to
+the start-point, either).
 
 'git checkout' --detach [<branch>]::
 'git checkout' [--detach] <commit>::
index c60fc9c138b5981ce5727efa4482350e699dbac5..6137421ede01315a8c14465b7a88fb189f1a4d7f 100644 (file)
@@ -59,13 +59,18 @@ out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 -c <new-branch>::
 --create <new-branch>::
        Create a new branch named `<new-branch>` starting at
-       `<start-point>` before switching to the branch. This is a
-       convenient shortcut for:
+       `<start-point>` before switching to the branch. This is the
+       transactional equivalent of
 +
 ------------
 $ git branch <new-branch>
 $ git switch <new-branch>
 ------------
++
+that is to say, the branch is not reset/created unless "git switch" is
+successful (e.g., when the branch is in use in another worktree, not
+just the current branch stays the same, but the branch is not reset to
+the start-point, either).
 
 -C <new-branch>::
 --force-create <new-branch>::
index b4ab972c5ac8e1bccd6e4558855a3f4c8737ec2b..8a8ad23e98ea8f13cf8d470c97cb64e415b996ca 100644 (file)
@@ -1600,6 +1600,13 @@ static int checkout_branch(struct checkout_opts *opts,
        if (new_branch_info->path && !opts->force_detach && !opts->new_branch)
                die_if_switching_to_a_branch_in_use(opts, new_branch_info->path);
 
+       /* "git checkout -B <branch>" */
+       if (opts->new_branch_force) {
+               char *full_ref = xstrfmt("refs/heads/%s", opts->new_branch);
+               die_if_switching_to_a_branch_in_use(opts, full_ref);
+               free(full_ref);
+       }
+
        if (!new_branch_info->commit && opts->new_branch) {
                struct object_id rev;
                int flag;
index e247a4735bbc26b01fecd600ed3e529bea16f464..c91c4db9361133e4e790f2c0201c5f41c14eaff1 100755 (executable)
@@ -170,8 +170,10 @@ test_expect_success 'switch back when temporarily detached and checked out elsew
        # we test in both worktrees to ensure that works
        # as expected with "first" and "next" worktrees
        test_must_fail git -C wt1 switch shared &&
+       test_must_fail git -C wt1 switch -C shared &&
        git -C wt1 switch --ignore-other-worktrees shared &&
        test_must_fail git -C wt2 switch shared &&
+       test_must_fail git -C wt2 switch -C shared &&
        git -C wt2 switch --ignore-other-worktrees shared
 '
 
index df4aff7825c9f8430d7449d1bdf2b0d5045d397a..5d5064e63de5f29c4fa14028932b736c24704345 100755 (executable)
@@ -126,6 +126,28 @@ test_expect_success 'die the same branch is already checked out' '
        )
 '
 
+test_expect_success 'refuse to reset a branch in use elsewhere' '
+       (
+               cd here &&
+
+               # we know we are on detached HEAD but just in case ...
+               git checkout --detach HEAD &&
+               git rev-parse --verify HEAD >old.head &&
+
+               git rev-parse --verify refs/heads/newmain >old.branch &&
+               test_must_fail git checkout -B newmain 2>error &&
+               git rev-parse --verify refs/heads/newmain >new.branch &&
+               git rev-parse --verify HEAD >new.head &&
+
+               grep "already used by worktree at" error &&
+               test_cmp old.branch new.branch &&
+               test_cmp old.head new.head &&
+
+               # and we must be still on the same detached HEAD state
+               test_must_fail git symbolic-ref HEAD
+       )
+'
+
 test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' '
        head=$(git -C there rev-parse --git-path HEAD) &&
        ref=$(git -C there symbolic-ref HEAD) &&