Skip to content

Commit f09e741

Browse files
committed
Merge branch 'jc/checkout-B-branch-in-use'
"git checkout -B <branch> [<start-point>]" allowed a branch that is in use in another worktree to be updated and checked out, which might be a bit unexpected. The rule has been tightened, which is a breaking change. "--ignore-other-worktrees" option is required to unbreak you, if you are used to the current behaviour that "-B" overrides the safety. * jc/checkout-B-branch-in-use: checkout: forbid "-B <branch>" from touching a branch used elsewhere checkout: refactor die_if_checked_out() caller
2 parents 055bb6e + b23285a commit f09e741

File tree

5 files changed

+63
-11
lines changed

5 files changed

+63
-11
lines changed

Documentation/git-checkout.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ $ git checkout <branch>
6363
------------
6464
+
6565
that is to say, the branch is not reset/created unless "git checkout" is
66-
successful.
66+
successful (e.g., when the branch is in use in another worktree, not
67+
just the current branch stays the same, but the branch is not reset to
68+
the start-point, either).
6769

6870
'git checkout' --detach [<branch>]::
6971
'git checkout' [--detach] <commit>::

Documentation/git-switch.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,18 @@ out at most one of `A` and `B`, in which case it defaults to `HEAD`.
5959
-c <new-branch>::
6060
--create <new-branch>::
6161
Create a new branch named `<new-branch>` starting at
62-
`<start-point>` before switching to the branch. This is a
63-
convenient shortcut for:
62+
`<start-point>` before switching to the branch. This is the
63+
transactional equivalent of
6464
+
6565
------------
6666
$ git branch <new-branch>
6767
$ git switch <new-branch>
6868
------------
69+
+
70+
that is to say, the branch is not reset/created unless "git switch" is
71+
successful (e.g., when the branch is in use in another worktree, not
72+
just the current branch stays the same, but the branch is not reset to
73+
the start-point, either).
6974

7075
-C <new-branch>::
7176
--force-create <new-branch>::

builtin/checkout.c

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,26 @@ static void die_if_some_operation_in_progress(void)
15181518
wt_status_state_free_buffers(&state);
15191519
}
15201520

1521+
/*
1522+
* die if attempting to checkout an existing branch that is in use
1523+
* in another worktree, unless ignore-other-wortrees option is given.
1524+
* The check is bypassed when the branch is already the current one,
1525+
* as it will not make things any worse.
1526+
*/
1527+
static void die_if_switching_to_a_branch_in_use(struct checkout_opts *opts,
1528+
const char *full_ref)
1529+
{
1530+
int flags;
1531+
char *head_ref;
1532+
1533+
if (opts->ignore_other_worktrees)
1534+
return;
1535+
head_ref = resolve_refdup("HEAD", 0, NULL, &flags);
1536+
if (head_ref && (!(flags & REF_ISSYMREF) || strcmp(head_ref, full_ref)))
1537+
die_if_checked_out(full_ref, 1);
1538+
free(head_ref);
1539+
}
1540+
15211541
static int checkout_branch(struct checkout_opts *opts,
15221542
struct branch_info *new_branch_info)
15231543
{
@@ -1578,14 +1598,15 @@ static int checkout_branch(struct checkout_opts *opts,
15781598
if (!opts->can_switch_when_in_progress)
15791599
die_if_some_operation_in_progress();
15801600

1581-
if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
1582-
!opts->ignore_other_worktrees) {
1583-
int flag;
1584-
char *head_ref = resolve_refdup("HEAD", 0, NULL, &flag);
1585-
if (head_ref &&
1586-
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new_branch_info->path)))
1587-
die_if_checked_out(new_branch_info->path, 1);
1588-
free(head_ref);
1601+
/* "git checkout <branch>" */
1602+
if (new_branch_info->path && !opts->force_detach && !opts->new_branch)
1603+
die_if_switching_to_a_branch_in_use(opts, new_branch_info->path);
1604+
1605+
/* "git checkout -B <branch>" */
1606+
if (opts->new_branch_force) {
1607+
char *full_ref = xstrfmt("refs/heads/%s", opts->new_branch);
1608+
die_if_switching_to_a_branch_in_use(opts, full_ref);
1609+
free(full_ref);
15891610
}
15901611

15911612
if (!new_branch_info->commit && opts->new_branch) {

t/t2060-switch.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,10 @@ test_expect_success 'switch back when temporarily detached and checked out elsew
170170
# we test in both worktrees to ensure that works
171171
# as expected with "first" and "next" worktrees
172172
test_must_fail git -C wt1 switch shared &&
173+
test_must_fail git -C wt1 switch -C shared &&
173174
git -C wt1 switch --ignore-other-worktrees shared &&
174175
test_must_fail git -C wt2 switch shared &&
176+
test_must_fail git -C wt2 switch -C shared &&
175177
git -C wt2 switch --ignore-other-worktrees shared
176178
'
177179

t/t2400-worktree-add.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,28 @@ test_expect_success 'die the same branch is already checked out' '
126126
)
127127
'
128128

129+
test_expect_success 'refuse to reset a branch in use elsewhere' '
130+
(
131+
cd here &&
132+
133+
# we know we are on detached HEAD but just in case ...
134+
git checkout --detach HEAD &&
135+
git rev-parse --verify HEAD >old.head &&
136+
137+
git rev-parse --verify refs/heads/newmain >old.branch &&
138+
test_must_fail git checkout -B newmain 2>error &&
139+
git rev-parse --verify refs/heads/newmain >new.branch &&
140+
git rev-parse --verify HEAD >new.head &&
141+
142+
grep "already used by worktree at" error &&
143+
test_cmp old.branch new.branch &&
144+
test_cmp old.head new.head &&
145+
146+
# and we must be still on the same detached HEAD state
147+
test_must_fail git symbolic-ref HEAD
148+
)
149+
'
150+
129151
test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' '
130152
head=$(git -C there rev-parse --git-path HEAD) &&
131153
ref=$(git -C there symbolic-ref HEAD) &&

0 commit comments

Comments
 (0)