Skip to content

Commit 96a806f

Browse files
committed
Merge branch 'rj/avoid-switching-to-already-used-branch'
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 worktree: introduce is_shared_symref()
2 parents c79786c + 894ea94 commit 96a806f

File tree

5 files changed

+89
-37
lines changed

5 files changed

+89
-37
lines changed

branch.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -821,12 +821,16 @@ void remove_branch_state(struct repository *r, int verbose)
821821
void die_if_checked_out(const char *branch, int ignore_current_worktree)
822822
{
823823
struct worktree **worktrees = get_worktrees();
824-
const struct worktree *wt;
825824

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

832836
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 checkout - ||:
154+
git branch -D shared ||:
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: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -404,44 +404,43 @@ int is_worktree_being_bisected(const struct worktree *wt,
404404
* bisect). New commands that do similar things should update this
405405
* function as well.
406406
*/
407-
const struct worktree *find_shared_symref(struct worktree **worktrees,
408-
const char *symref,
409-
const char *target)
407+
int is_shared_symref(const struct worktree *wt, const char *symref,
408+
const char *target)
410409
{
411-
const struct worktree *existing = NULL;
412-
int i = 0;
410+
const char *symref_target;
411+
struct ref_store *refs;
412+
int flags;
413413

414-
for (i = 0; worktrees[i]; i++) {
415-
struct worktree *wt = worktrees[i];
416-
const char *symref_target;
417-
struct ref_store *refs;
418-
int flags;
414+
if (wt->is_bare)
415+
return 0;
419416

420-
if (wt->is_bare)
421-
continue;
417+
if (wt->is_detached && !strcmp(symref, "HEAD")) {
418+
if (is_worktree_being_rebased(wt, target))
419+
return 1;
420+
if (is_worktree_being_bisected(wt, target))
421+
return 1;
422+
}
422423

423-
if (wt->is_detached && !strcmp(symref, "HEAD")) {
424-
if (is_worktree_being_rebased(wt, target)) {
425-
existing = wt;
426-
break;
427-
}
428-
if (is_worktree_being_bisected(wt, target)) {
429-
existing = wt;
430-
break;
431-
}
432-
}
424+
refs = get_worktree_ref_store(wt);
425+
symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
426+
NULL, &flags);
427+
if ((flags & REF_ISSYMREF) &&
428+
symref_target && !strcmp(symref_target, target))
429+
return 1;
433430

434-
refs = get_worktree_ref_store(wt);
435-
symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
436-
NULL, &flags);
437-
if ((flags & REF_ISSYMREF) &&
438-
symref_target && !strcmp(symref_target, target)) {
439-
existing = wt;
440-
break;
441-
}
442-
}
431+
return 0;
432+
}
443433

444-
return existing;
434+
const struct worktree *find_shared_symref(struct worktree **worktrees,
435+
const char *symref,
436+
const char *target)
437+
{
438+
439+
for (int i = 0; worktrees[i]; i++)
440+
if (is_shared_symref(worktrees[i], symref, target))
441+
return worktrees[i];
442+
443+
return NULL;
445444
}
446445

447446
int submodule_uses_worktrees(const char *path)

worktree.h

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

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

0 commit comments

Comments
 (0)