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 <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Nguyễn Thái Ngọc Duy committed
Nov 30, 2014 at 15:24 UTC
5883034c61c60e04db16520a759ec67a34ffaea3
2 files changed
+104
-7
builtin/checkout.c
+84
-2
@@ -430,6 +430,11 @@ struct branch_info {
430
const char *name; /* The short name used */
431
const char *path; /* The full name of a real branch */
432
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;
438
};
439
440
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)
963
return NULL;
964
}
965
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
+
1031
static int parse_branchname_arg(int argc, const char **argv,
1032
int dwim_new_local_branch_ok,
1033
struct branch_info *new,
1034
struct tree **source_tree,
1035
unsigned char rev[20],
966
- const char **new_branch)
1036
+ const char **new_branch,
1037
+ int force_detach)
1038
{
1039
int argcount = 0;
1040
unsigned char branch_rev[20];
@@ -1085,6 +1156,16 @@ static int parse_branchname_arg(int argc, const char **argv,
1156
else
1157
new->path = NULL; /* not an existing branch */
1158
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
+
1169
new->commit = lookup_commit_reference_gently(rev, 1);
1170
if (!new->commit) {
1171
/* not a commit */
@@ -1289,7 +1370,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
1370
!opts.new_branch;
1371
int n = parse_branchname_arg(argc, argv, dwim_ok,
1372
&new, &opts.source_tree,
1292
- rev, &opts.new_branch);
1373
+ rev, &opts.new_branch,
1374
+ opts.force_detach);
1375
argv += n;
1376
argc -= n;
1377
}
t/t2025-checkout-to.sh
+20
-5
@@ -18,13 +18,14 @@ test_expect_success 'checkout --to an existing worktree' '
18
'
19
20
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 &&
23
(
24
cd here &&
25
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 &&
29
git fsck
30
)
31
'
@@ -42,7 +43,7 @@ test_expect_success 'checkout --to a new worktree from a subdir' '
43
test_expect_success 'checkout --to from a linked checkout' '
44
(
45
cd here &&
45
- git checkout --to nested-here master &&
46
+ git checkout --detach --to nested-here master &&
47
cd nested-here &&
48
git fsck
49
)
@@ -60,4 +61,18 @@ test_expect_success 'checkout --to a new worktree creating new branch' '
61
)
62
'
63
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
+
78
test_done