mirror of
https://github.com/git/git
synced 2024-10-28 19:25:47 +00: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:
parent
23af91d102
commit
5883034c61
2 changed files with 104 additions and 7 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue