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()
This commit is contained in:
commit
96a806f87a
14
branch.c
14
branch.c
@ -821,12 +821,16 @@ void remove_branch_state(struct repository *r, int verbose)
|
|||||||
void die_if_checked_out(const char *branch, int ignore_current_worktree)
|
void die_if_checked_out(const char *branch, int ignore_current_worktree)
|
||||||
{
|
{
|
||||||
struct worktree **worktrees = get_worktrees();
|
struct worktree **worktrees = get_worktrees();
|
||||||
const struct worktree *wt;
|
|
||||||
|
|
||||||
wt = find_shared_symref(worktrees, "HEAD", branch);
|
for (int i = 0; worktrees[i]; i++) {
|
||||||
if (wt && (!ignore_current_worktree || !wt->is_current)) {
|
if (worktrees[i]->is_current && ignore_current_worktree)
|
||||||
skip_prefix(branch, "refs/heads/", &branch);
|
continue;
|
||||||
die(_("'%s' is already checked out at '%s'"), branch, wt->path);
|
|
||||||
|
if (is_shared_symref(worktrees[i], "HEAD", branch)) {
|
||||||
|
skip_prefix(branch, "refs/heads/", &branch);
|
||||||
|
die(_("'%s' is already checked out at '%s'"),
|
||||||
|
branch, worktrees[i]->path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
free_worktrees(worktrees);
|
free_worktrees(worktrees);
|
||||||
|
@ -146,4 +146,33 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
|
|||||||
test_cmp_config "" --default "" branch.main2.merge
|
test_cmp_config "" --default "" branch.main2.merge
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'switch back when temporarily detached and checked out elsewhere ' '
|
||||||
|
test_when_finished "
|
||||||
|
git worktree remove wt1 ||:
|
||||||
|
git worktree remove wt2 ||:
|
||||||
|
git checkout - ||:
|
||||||
|
git branch -D shared ||:
|
||||||
|
" &&
|
||||||
|
git checkout -b shared &&
|
||||||
|
test_commit shared-first &&
|
||||||
|
HASH1=$(git rev-parse --verify HEAD) &&
|
||||||
|
test_commit shared-second &&
|
||||||
|
test_commit shared-third &&
|
||||||
|
HASH2=$(git rev-parse --verify HEAD) &&
|
||||||
|
git worktree add wt1 -f shared &&
|
||||||
|
git -C wt1 bisect start &&
|
||||||
|
git -C wt1 bisect good $HASH1 &&
|
||||||
|
git -C wt1 bisect bad $HASH2 &&
|
||||||
|
git worktree add wt2 -f shared &&
|
||||||
|
git -C wt2 bisect start &&
|
||||||
|
git -C wt2 bisect good $HASH1 &&
|
||||||
|
git -C wt2 bisect bad $HASH2 &&
|
||||||
|
# we test in both worktrees to ensure that works
|
||||||
|
# as expected with "first" and "next" worktrees
|
||||||
|
test_must_fail git -C wt1 switch shared &&
|
||||||
|
git -C wt1 switch --ignore-other-worktrees shared &&
|
||||||
|
test_must_fail git -C wt2 switch shared &&
|
||||||
|
git -C wt2 switch --ignore-other-worktrees shared
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
@ -388,6 +388,20 @@ test_expect_success 'switch to branch checked out here' '
|
|||||||
git rebase main main
|
git rebase main main
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'switch to branch checked out elsewhere fails' '
|
||||||
|
test_when_finished "
|
||||||
|
git worktree remove wt1 &&
|
||||||
|
git worktree remove wt2 &&
|
||||||
|
git branch -d shared
|
||||||
|
" &&
|
||||||
|
git worktree add wt1 -b shared &&
|
||||||
|
git worktree add wt2 -f shared &&
|
||||||
|
# we test in both worktrees to ensure that works
|
||||||
|
# as expected with "first" and "next" worktrees
|
||||||
|
test_must_fail git -C wt1 rebase shared shared &&
|
||||||
|
test_must_fail git -C wt2 rebase shared shared
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'switch to branch not checked out' '
|
test_expect_success 'switch to branch not checked out' '
|
||||||
git checkout main &&
|
git checkout main &&
|
||||||
git branch other &&
|
git branch other &&
|
||||||
|
63
worktree.c
63
worktree.c
@ -404,44 +404,43 @@ int is_worktree_being_bisected(const struct worktree *wt,
|
|||||||
* bisect). New commands that do similar things should update this
|
* bisect). New commands that do similar things should update this
|
||||||
* function as well.
|
* function as well.
|
||||||
*/
|
*/
|
||||||
|
int is_shared_symref(const struct worktree *wt, const char *symref,
|
||||||
|
const char *target)
|
||||||
|
{
|
||||||
|
const char *symref_target;
|
||||||
|
struct ref_store *refs;
|
||||||
|
int flags;
|
||||||
|
|
||||||
|
if (wt->is_bare)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (wt->is_detached && !strcmp(symref, "HEAD")) {
|
||||||
|
if (is_worktree_being_rebased(wt, target))
|
||||||
|
return 1;
|
||||||
|
if (is_worktree_being_bisected(wt, target))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
refs = get_worktree_ref_store(wt);
|
||||||
|
symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
|
||||||
|
NULL, &flags);
|
||||||
|
if ((flags & REF_ISSYMREF) &&
|
||||||
|
symref_target && !strcmp(symref_target, target))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const struct worktree *find_shared_symref(struct worktree **worktrees,
|
const struct worktree *find_shared_symref(struct worktree **worktrees,
|
||||||
const char *symref,
|
const char *symref,
|
||||||
const char *target)
|
const char *target)
|
||||||
{
|
{
|
||||||
const struct worktree *existing = NULL;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (i = 0; worktrees[i]; i++) {
|
for (int i = 0; worktrees[i]; i++)
|
||||||
struct worktree *wt = worktrees[i];
|
if (is_shared_symref(worktrees[i], symref, target))
|
||||||
const char *symref_target;
|
return worktrees[i];
|
||||||
struct ref_store *refs;
|
|
||||||
int flags;
|
|
||||||
|
|
||||||
if (wt->is_bare)
|
return NULL;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (wt->is_detached && !strcmp(symref, "HEAD")) {
|
|
||||||
if (is_worktree_being_rebased(wt, target)) {
|
|
||||||
existing = wt;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (is_worktree_being_bisected(wt, target)) {
|
|
||||||
existing = wt;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refs = get_worktree_ref_store(wt);
|
|
||||||
symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
|
|
||||||
NULL, &flags);
|
|
||||||
if ((flags & REF_ISSYMREF) &&
|
|
||||||
symref_target && !strcmp(symref_target, target)) {
|
|
||||||
existing = wt;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return existing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int submodule_uses_worktrees(const char *path)
|
int submodule_uses_worktrees(const char *path)
|
||||||
|
@ -148,6 +148,12 @@ const struct worktree *find_shared_symref(struct worktree **worktrees,
|
|||||||
const char *symref,
|
const char *symref,
|
||||||
const char *target);
|
const char *target);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns true if a symref points to a ref in a worktree.
|
||||||
|
*/
|
||||||
|
int is_shared_symref(const struct worktree *wt,
|
||||||
|
const char *symref, const char *target);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Similar to head_ref() for all HEADs _except_ one from the current
|
* Similar to head_ref() for all HEADs _except_ one from the current
|
||||||
* worktree, which is covered by head_ref().
|
* worktree, which is covered by head_ref().
|
||||||
|
Loading…
Reference in New Issue
Block a user