1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-06-08 06:36:10 +02:00

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>
This commit is contained in:
Nguyễn Thái Ngọc Duy 2014-11-30 15:24:49 +07:00 committed by Junio C Hamano
parent 23af91d102
commit 5883034c61
2 changed files with 104 additions and 7 deletions

View File

@ -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)
@ -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];
@ -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 */
@ -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;
}

View File

@ -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
)
'
@ -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
)
@ -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