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