Skip to content

Commit

Permalink
checkout: reject if the branch is already checked out elsewhere
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Nguyễn Thái Ngọc Duy authored and Junio C Hamano committed Dec 1, 2014
1 parent 23af91d commit 5883034
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 7 deletions.
86 changes: 84 additions & 2 deletions builtin/checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,11 @@ struct branch_info {
const char *name; /* The short name used */
const char *path; /* The full name of a real branch */
struct commit *commit; /* The named commit */
/*
* if not null the branch is detached because it's already
* checked out in this checkout
*/
char *checkout;
};

static void setup_branch_path(struct branch_info *branch)
Expand Down Expand Up @@ -958,12 +963,78 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
return NULL;
}

static void check_linked_checkout(struct branch_info *new, const char *id)
{
struct strbuf sb = STRBUF_INIT;
struct strbuf path = STRBUF_INIT;
struct strbuf gitdir = STRBUF_INIT;
const char *start, *end;

if (id)
strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
else
strbuf_addf(&path, "%s/HEAD", get_git_common_dir());

if (strbuf_read_file(&sb, path.buf, 0) < 0 ||
!skip_prefix(sb.buf, "ref:", &start))
goto done;
while (isspace(*start))
start++;
end = start;
while (*end && !isspace(*end))
end++;
if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0')
goto done;
if (id) {
strbuf_reset(&path);
strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
goto done;
strbuf_rtrim(&gitdir);
} else
strbuf_addstr(&gitdir, get_git_common_dir());
die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf);
done:
strbuf_release(&path);
strbuf_release(&sb);
strbuf_release(&gitdir);
}

static void check_linked_checkouts(struct branch_info *new)
{
struct strbuf path = STRBUF_INIT;
DIR *dir;
struct dirent *d;

strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
if ((dir = opendir(path.buf)) == NULL) {
strbuf_release(&path);
return;
}

/*
* $GIT_COMMON_DIR/HEAD is practically outside
* $GIT_DIR so resolve_ref_unsafe() won't work (it
* uses git_path). Parse the ref ourselves.
*/
check_linked_checkout(new, NULL);

while ((d = readdir(dir)) != NULL) {
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
check_linked_checkout(new, d->d_name);
}
strbuf_release(&path);
closedir(dir);
}

static int parse_branchname_arg(int argc, const char **argv,
int dwim_new_local_branch_ok,
struct branch_info *new,
struct tree **source_tree,
unsigned char rev[20],
const char **new_branch)
const char **new_branch,
int force_detach)
{
int argcount = 0;
unsigned char branch_rev[20];
Expand Down Expand Up @@ -1085,6 +1156,16 @@ static int parse_branchname_arg(int argc, const char **argv,
else
new->path = NULL; /* not an existing branch */

if (new->path && !force_detach && !*new_branch) {
unsigned char sha1[20];
int flag;
char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
if (head_ref &&
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
check_linked_checkouts(new);
free(head_ref);
}

new->commit = lookup_commit_reference_gently(rev, 1);
if (!new->commit) {
/* not a commit */
Expand Down Expand Up @@ -1289,7 +1370,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
!opts.new_branch;
int n = parse_branchname_arg(argc, argv, dwim_ok,
&new, &opts.source_tree,
rev, &opts.new_branch);
rev, &opts.new_branch,
opts.force_detach);
argv += n;
argc -= n;
}
Expand Down
25 changes: 20 additions & 5 deletions t/t2025-checkout-to.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ test_expect_success 'checkout --to an existing worktree' '
'

test_expect_success 'checkout --to a new worktree' '
git checkout --to here master &&
git rev-parse HEAD >expect &&
git checkout --detach --to here master &&
(
cd here &&
test_cmp ../init.t init.t &&
git symbolic-ref HEAD >actual &&
echo refs/heads/master >expect &&
test_cmp expect actual &&
test_must_fail git symbolic-ref HEAD &&
git rev-parse HEAD >actual &&
test_cmp ../expect actual &&
git fsck
)
'
Expand All @@ -42,7 +43,7 @@ test_expect_success 'checkout --to a new worktree from a subdir' '
test_expect_success 'checkout --to from a linked checkout' '
(
cd here &&
git checkout --to nested-here master &&
git checkout --detach --to nested-here master &&
cd nested-here &&
git fsck
)
Expand All @@ -60,4 +61,18 @@ test_expect_success 'checkout --to a new worktree creating new branch' '
)
'

test_expect_success 'die the same branch is already checked out' '
(
cd here &&
test_must_fail git checkout newmaster
)
'

test_expect_success 'not die on re-checking out current branch' '
(
cd there &&
git checkout newmaster
)
'

test_done

0 comments on commit 5883034

Please sign in to comment.