Skip to content

Commit 0e1bff4

Browse files
committed
Merge branch 'rj/avoid-switching-to-already-used-branch' into seen
A few subcommands have been taught to stop users from working on a branch that is being used in another worktree linked to the same repository. * rj/avoid-switching-to-already-used-branch: switch: reject if the branch is already checked out elsewhere (test) rebase: refuse to switch to a branch already checked out elsewhere (test) branch: fix die_if_checked_out() when ignore_current_worktree
2 parents cb1623c + 4564f46 commit 0e1bff4

File tree

5 files changed

+89
-30
lines changed

5 files changed

+89
-30
lines changed

branch.c

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -820,12 +820,18 @@ void remove_branch_state(struct repository *r, int verbose)
820820
void die_if_checked_out(const char *branch, int ignore_current_worktree)
821821
{
822822
struct worktree **worktrees = get_worktrees();
823-
const struct worktree *wt;
823+
int i;
824+
825+
for (i = 0; worktrees[i]; i++)
826+
{
827+
if (worktrees[i]->is_current && ignore_current_worktree)
828+
continue;
824829

825-
wt = find_shared_symref(worktrees, "HEAD", branch);
826-
if (wt && (!ignore_current_worktree || !wt->is_current)) {
827-
skip_prefix(branch, "refs/heads/", &branch);
828-
die(_("'%s' is already checked out at '%s'"), branch, wt->path);
830+
if (is_shared_symref(worktrees[i], "HEAD", branch)) {
831+
skip_prefix(branch, "refs/heads/", &branch);
832+
die(_("'%s' is already checked out at '%s'"),
833+
branch, worktrees[i]->path);
834+
}
829835
}
830836

831837
free_worktrees(worktrees);

t/t2060-switch.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,33 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
146146
test_cmp_config "" --default "" branch.main2.merge
147147
'
148148

149+
test_expect_success 'switch back when temporarily detached and checked out elsewhere ' '
150+
test_when_finished "
151+
git worktree remove wt1 &&
152+
git worktree remove wt2 &&
153+
git branch -d shared
154+
git checkout -
155+
" &&
156+
git checkout -b shared &&
157+
test_commit shared-first &&
158+
HASH1=$(git rev-parse --verify HEAD) &&
159+
test_commit shared-second &&
160+
test_commit shared-third &&
161+
HASH2=$(git rev-parse --verify HEAD) &&
162+
git worktree add wt1 -f shared &&
163+
git -C wt1 bisect start &&
164+
git -C wt1 bisect good $HASH1 &&
165+
git -C wt1 bisect bad $HASH2 &&
166+
git worktree add wt2 -f shared &&
167+
git -C wt2 bisect start &&
168+
git -C wt2 bisect good $HASH1 &&
169+
git -C wt2 bisect bad $HASH2 &&
170+
# we test in both worktrees to ensure that works
171+
# as expected with "first" and "next" worktrees
172+
test_must_fail git -C wt1 switch shared &&
173+
git -C wt1 switch --ignore-other-worktrees shared &&
174+
test_must_fail git -C wt2 switch shared &&
175+
git -C wt2 switch --ignore-other-worktrees shared
176+
'
177+
149178
test_done

t/t3400-rebase.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,20 @@ test_expect_success 'switch to branch checked out here' '
388388
git rebase main main
389389
'
390390

391+
test_expect_success 'switch to branch checked out elsewhere fails' '
392+
test_when_finished "
393+
git worktree remove wt1 &&
394+
git worktree remove wt2 &&
395+
git branch -d shared
396+
" &&
397+
git worktree add wt1 -b shared &&
398+
git worktree add wt2 -f shared &&
399+
# we test in both worktrees to ensure that works
400+
# as expected with "first" and "next" worktrees
401+
test_must_fail git -C wt1 rebase shared shared &&
402+
test_must_fail git -C wt2 rebase shared shared
403+
'
404+
391405
test_expect_success 'switch to branch not checked out' '
392406
git checkout main &&
393407
git branch other &&

worktree.c

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,33 @@ int is_worktree_being_bisected(const struct worktree *wt,
403403
* bisect). New commands that do similar things should update this
404404
* function as well.
405405
*/
406+
int is_shared_symref(const struct worktree *wt, const char *symref,
407+
const char *target)
408+
{
409+
const char *symref_target;
410+
struct ref_store *refs;
411+
int flags;
412+
413+
if (wt->is_bare)
414+
return 0;
415+
416+
if (wt->is_detached && !strcmp(symref, "HEAD")) {
417+
if (is_worktree_being_rebased(wt, target))
418+
return 1;
419+
if (is_worktree_being_bisected(wt, target))
420+
return 1;
421+
}
422+
423+
refs = get_worktree_ref_store(wt);
424+
symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
425+
NULL, &flags);
426+
if ((flags & REF_ISSYMREF) &&
427+
symref_target && !strcmp(symref_target, target))
428+
return 1;
429+
430+
return 0;
431+
}
432+
406433
const struct worktree *find_shared_symref(struct worktree **worktrees,
407434
const char *symref,
408435
const char *target)
@@ -411,31 +438,8 @@ const struct worktree *find_shared_symref(struct worktree **worktrees,
411438
int i = 0;
412439

413440
for (i = 0; worktrees[i]; i++) {
414-
struct worktree *wt = worktrees[i];
415-
const char *symref_target;
416-
struct ref_store *refs;
417-
int flags;
418-
419-
if (wt->is_bare)
420-
continue;
421-
422-
if (wt->is_detached && !strcmp(symref, "HEAD")) {
423-
if (is_worktree_being_rebased(wt, target)) {
424-
existing = wt;
425-
break;
426-
}
427-
if (is_worktree_being_bisected(wt, target)) {
428-
existing = wt;
429-
break;
430-
}
431-
}
432-
433-
refs = get_worktree_ref_store(wt);
434-
symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
435-
NULL, &flags);
436-
if ((flags & REF_ISSYMREF) &&
437-
symref_target && !strcmp(symref_target, target)) {
438-
existing = wt;
441+
if (is_shared_symref(worktrees[i], symref, target)) {
442+
existing = worktrees[i];
439443
break;
440444
}
441445
}

worktree.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ const struct worktree *find_shared_symref(struct worktree **worktrees,
149149
const char *symref,
150150
const char *target);
151151

152+
/*
153+
* Returns true if a symref points to a ref in a worktree.
154+
*/
155+
int is_shared_symref(const struct worktree *wt,
156+
const char *symref, const char *target);
157+
152158
/*
153159
* Similar to head_ref() for all HEADs _except_ one from the current
154160
* worktree, which is covered by head_ref().

0 commit comments

Comments
 (0)