Skip to content

Commit 5883034

Browse files
pcloudsgitster
authored andcommitted
checkout: reject if the branch is already checked out elsewhere
One branch obviously can't be checked out at two places (but detached heads are ok). Give the user a choice in this case: --detach, -b new-branch, switch branch in the other checkout first or simply 'cd' and continue to work there. Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 23af91d commit 5883034

File tree

2 files changed

+104
-7
lines changed

2 files changed

+104
-7
lines changed

builtin/checkout.c

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,11 @@ struct branch_info {
430430
const char *name; /* The short name used */
431431
const char *path; /* The full name of a real branch */
432432
struct commit *commit; /* The named commit */
433+
/*
434+
* if not null the branch is detached because it's already
435+
* checked out in this checkout
436+
*/
437+
char *checkout;
433438
};
434439

435440
static void setup_branch_path(struct branch_info *branch)
@@ -958,12 +963,78 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
958963
return NULL;
959964
}
960965

966+
static void check_linked_checkout(struct branch_info *new, const char *id)
967+
{
968+
struct strbuf sb = STRBUF_INIT;
969+
struct strbuf path = STRBUF_INIT;
970+
struct strbuf gitdir = STRBUF_INIT;
971+
const char *start, *end;
972+
973+
if (id)
974+
strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
975+
else
976+
strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
977+
978+
if (strbuf_read_file(&sb, path.buf, 0) < 0 ||
979+
!skip_prefix(sb.buf, "ref:", &start))
980+
goto done;
981+
while (isspace(*start))
982+
start++;
983+
end = start;
984+
while (*end && !isspace(*end))
985+
end++;
986+
if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0')
987+
goto done;
988+
if (id) {
989+
strbuf_reset(&path);
990+
strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
991+
if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
992+
goto done;
993+
strbuf_rtrim(&gitdir);
994+
} else
995+
strbuf_addstr(&gitdir, get_git_common_dir());
996+
die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf);
997+
done:
998+
strbuf_release(&path);
999+
strbuf_release(&sb);
1000+
strbuf_release(&gitdir);
1001+
}
1002+
1003+
static void check_linked_checkouts(struct branch_info *new)
1004+
{
1005+
struct strbuf path = STRBUF_INIT;
1006+
DIR *dir;
1007+
struct dirent *d;
1008+
1009+
strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
1010+
if ((dir = opendir(path.buf)) == NULL) {
1011+
strbuf_release(&path);
1012+
return;
1013+
}
1014+
1015+
/*
1016+
* $GIT_COMMON_DIR/HEAD is practically outside
1017+
* $GIT_DIR so resolve_ref_unsafe() won't work (it
1018+
* uses git_path). Parse the ref ourselves.
1019+
*/
1020+
check_linked_checkout(new, NULL);
1021+
1022+
while ((d = readdir(dir)) != NULL) {
1023+
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
1024+
continue;
1025+
check_linked_checkout(new, d->d_name);
1026+
}
1027+
strbuf_release(&path);
1028+
closedir(dir);
1029+
}
1030+
9611031
static int parse_branchname_arg(int argc, const char **argv,
9621032
int dwim_new_local_branch_ok,
9631033
struct branch_info *new,
9641034
struct tree **source_tree,
9651035
unsigned char rev[20],
966-
const char **new_branch)
1036+
const char **new_branch,
1037+
int force_detach)
9671038
{
9681039
int argcount = 0;
9691040
unsigned char branch_rev[20];
@@ -1085,6 +1156,16 @@ static int parse_branchname_arg(int argc, const char **argv,
10851156
else
10861157
new->path = NULL; /* not an existing branch */
10871158

1159+
if (new->path && !force_detach && !*new_branch) {
1160+
unsigned char sha1[20];
1161+
int flag;
1162+
char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
1163+
if (head_ref &&
1164+
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
1165+
check_linked_checkouts(new);
1166+
free(head_ref);
1167+
}
1168+
10881169
new->commit = lookup_commit_reference_gently(rev, 1);
10891170
if (!new->commit) {
10901171
/* not a commit */
@@ -1289,7 +1370,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
12891370
!opts.new_branch;
12901371
int n = parse_branchname_arg(argc, argv, dwim_ok,
12911372
&new, &opts.source_tree,
1292-
rev, &opts.new_branch);
1373+
rev, &opts.new_branch,
1374+
opts.force_detach);
12931375
argv += n;
12941376
argc -= n;
12951377
}

t/t2025-checkout-to.sh

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ test_expect_success 'checkout --to an existing worktree' '
1818
'
1919

2020
test_expect_success 'checkout --to a new worktree' '
21-
git checkout --to here master &&
21+
git rev-parse HEAD >expect &&
22+
git checkout --detach --to here master &&
2223
(
2324
cd here &&
2425
test_cmp ../init.t init.t &&
25-
git symbolic-ref HEAD >actual &&
26-
echo refs/heads/master >expect &&
27-
test_cmp expect actual &&
26+
test_must_fail git symbolic-ref HEAD &&
27+
git rev-parse HEAD >actual &&
28+
test_cmp ../expect actual &&
2829
git fsck
2930
)
3031
'
@@ -42,7 +43,7 @@ test_expect_success 'checkout --to a new worktree from a subdir' '
4243
test_expect_success 'checkout --to from a linked checkout' '
4344
(
4445
cd here &&
45-
git checkout --to nested-here master &&
46+
git checkout --detach --to nested-here master &&
4647
cd nested-here &&
4748
git fsck
4849
)
@@ -60,4 +61,18 @@ test_expect_success 'checkout --to a new worktree creating new branch' '
6061
)
6162
'
6263

64+
test_expect_success 'die the same branch is already checked out' '
65+
(
66+
cd here &&
67+
test_must_fail git checkout newmaster
68+
)
69+
'
70+
71+
test_expect_success 'not die on re-checking out current branch' '
72+
(
73+
cd there &&
74+
git checkout newmaster
75+
)
76+
'
77+
6378
test_done

0 commit comments

Comments
 (0)