clone: add --branch option to select a different HEAD
We currently point the HEAD of a newly cloned repo to the same ref as the parent repo's HEAD. While a user can then "git checkout -b foo origin/foo" whichever branch they choose, it is more convenient and more efficient to tell clone which branch you want in the first place. Based on a patch by Kirill A. Korinskiy. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
68ea474164
commit
7a4ee28f41
@ -127,6 +127,13 @@ objects from the source repository into a pack in the cloned repository.
|
|||||||
Instead of using the remote name 'origin' to keep track
|
Instead of using the remote name 'origin' to keep track
|
||||||
of the upstream repository, use <name>.
|
of the upstream repository, use <name>.
|
||||||
|
|
||||||
|
--branch <name>::
|
||||||
|
-b <name>::
|
||||||
|
Instead of pointing the newly created HEAD to the branch pointed
|
||||||
|
to by the cloned repositoroy's HEAD, point to <name> branch
|
||||||
|
instead. In a non-bare repository, this is the branch that will
|
||||||
|
be checked out.
|
||||||
|
|
||||||
--upload-pack <upload-pack>::
|
--upload-pack <upload-pack>::
|
||||||
-u <upload-pack>::
|
-u <upload-pack>::
|
||||||
When given, and the repository to clone from is accessed
|
When given, and the repository to clone from is accessed
|
||||||
|
@ -41,6 +41,7 @@ static int option_quiet, option_no_checkout, option_bare, option_mirror;
|
|||||||
static int option_local, option_no_hardlinks, option_shared;
|
static int option_local, option_no_hardlinks, option_shared;
|
||||||
static char *option_template, *option_reference, *option_depth;
|
static char *option_template, *option_reference, *option_depth;
|
||||||
static char *option_origin = NULL;
|
static char *option_origin = NULL;
|
||||||
|
static char *option_branch = NULL;
|
||||||
static char *option_upload_pack = "git-upload-pack";
|
static char *option_upload_pack = "git-upload-pack";
|
||||||
static int option_verbose;
|
static int option_verbose;
|
||||||
|
|
||||||
@ -65,6 +66,8 @@ static struct option builtin_clone_options[] = {
|
|||||||
"reference repository"),
|
"reference repository"),
|
||||||
OPT_STRING('o', "origin", &option_origin, "branch",
|
OPT_STRING('o', "origin", &option_origin, "branch",
|
||||||
"use <branch> instead of 'origin' to track upstream"),
|
"use <branch> instead of 'origin' to track upstream"),
|
||||||
|
OPT_STRING('b', "branch", &option_branch, "branch",
|
||||||
|
"checkout <branch> instead of the remote's HEAD"),
|
||||||
OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
|
OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
|
||||||
"path to git-upload-pack on the remote"),
|
"path to git-upload-pack on the remote"),
|
||||||
OPT_STRING(0, "depth", &option_depth, "depth",
|
OPT_STRING(0, "depth", &option_depth, "depth",
|
||||||
@ -347,7 +350,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||||||
const char *repo_name, *repo, *work_tree, *git_dir;
|
const char *repo_name, *repo, *work_tree, *git_dir;
|
||||||
char *path, *dir;
|
char *path, *dir;
|
||||||
int dest_exists;
|
int dest_exists;
|
||||||
const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
|
const struct ref *refs, *remote_head, *mapped_refs;
|
||||||
|
const struct ref *remote_head_points_at;
|
||||||
|
const struct ref *our_head_points_at;
|
||||||
struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
|
struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
|
||||||
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
|
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
|
||||||
struct transport *transport = NULL;
|
struct transport *transport = NULL;
|
||||||
@ -519,11 +524,31 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||||||
mapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf);
|
mapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf);
|
||||||
|
|
||||||
remote_head = find_ref_by_name(refs, "HEAD");
|
remote_head = find_ref_by_name(refs, "HEAD");
|
||||||
head_points_at = guess_remote_head(remote_head, mapped_refs, 0);
|
remote_head_points_at =
|
||||||
|
guess_remote_head(remote_head, mapped_refs, 0);
|
||||||
|
|
||||||
|
if (option_branch) {
|
||||||
|
struct strbuf head = STRBUF_INIT;
|
||||||
|
strbuf_addstr(&head, src_ref_prefix);
|
||||||
|
strbuf_addstr(&head, option_branch);
|
||||||
|
our_head_points_at =
|
||||||
|
find_ref_by_name(mapped_refs, head.buf);
|
||||||
|
strbuf_release(&head);
|
||||||
|
|
||||||
|
if (!our_head_points_at) {
|
||||||
|
warning("Remote branch %s not found in "
|
||||||
|
"upstream %s, using HEAD instead",
|
||||||
|
option_branch, option_origin);
|
||||||
|
our_head_points_at = remote_head_points_at;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
our_head_points_at = remote_head_points_at;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
warning("You appear to have cloned an empty repository.");
|
warning("You appear to have cloned an empty repository.");
|
||||||
head_points_at = NULL;
|
our_head_points_at = NULL;
|
||||||
|
remote_head_points_at = NULL;
|
||||||
remote_head = NULL;
|
remote_head = NULL;
|
||||||
option_no_checkout = 1;
|
option_no_checkout = 1;
|
||||||
if (!option_bare)
|
if (!option_bare)
|
||||||
@ -531,41 +556,35 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||||||
"refs/heads/master");
|
"refs/heads/master");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (head_points_at) {
|
if (remote_head_points_at && !option_bare) {
|
||||||
/* Local default branch link */
|
|
||||||
create_symref("HEAD", head_points_at->name, NULL);
|
|
||||||
|
|
||||||
if (!option_bare) {
|
|
||||||
struct strbuf head_ref = STRBUF_INIT;
|
struct strbuf head_ref = STRBUF_INIT;
|
||||||
const char *head = head_points_at->name;
|
|
||||||
|
|
||||||
if (!prefixcmp(head, "refs/heads/"))
|
|
||||||
head += 11;
|
|
||||||
|
|
||||||
/* Set up the initial local branch */
|
|
||||||
|
|
||||||
/* Local branch initial value */
|
|
||||||
update_ref(reflog_msg.buf, "HEAD",
|
|
||||||
head_points_at->old_sha1,
|
|
||||||
NULL, 0, DIE_ON_ERR);
|
|
||||||
|
|
||||||
strbuf_addstr(&head_ref, branch_top.buf);
|
strbuf_addstr(&head_ref, branch_top.buf);
|
||||||
strbuf_addstr(&head_ref, "HEAD");
|
strbuf_addstr(&head_ref, "HEAD");
|
||||||
|
|
||||||
/* Remote branch link */
|
|
||||||
create_symref(head_ref.buf,
|
create_symref(head_ref.buf,
|
||||||
head_points_at->peer_ref->name,
|
remote_head_points_at->peer_ref->name,
|
||||||
reflog_msg.buf);
|
reflog_msg.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (our_head_points_at) {
|
||||||
|
/* Local default branch link */
|
||||||
|
create_symref("HEAD", our_head_points_at->name, NULL);
|
||||||
|
if (!option_bare) {
|
||||||
|
const char *head = skip_prefix(our_head_points_at->name,
|
||||||
|
"refs/heads/");
|
||||||
|
update_ref(reflog_msg.buf, "HEAD",
|
||||||
|
our_head_points_at->old_sha1,
|
||||||
|
NULL, 0, DIE_ON_ERR);
|
||||||
install_branch_config(0, head, option_origin,
|
install_branch_config(0, head, option_origin,
|
||||||
head_points_at->name);
|
our_head_points_at->name);
|
||||||
}
|
}
|
||||||
} else if (remote_head) {
|
} else if (remote_head) {
|
||||||
/* Source had detached HEAD pointing somewhere. */
|
/* Source had detached HEAD pointing somewhere. */
|
||||||
if (!option_bare)
|
if (!option_bare) {
|
||||||
update_ref(reflog_msg.buf, "HEAD",
|
update_ref(reflog_msg.buf, "HEAD",
|
||||||
remote_head->old_sha1,
|
remote_head->old_sha1,
|
||||||
NULL, REF_NODEREF, DIE_ON_ERR);
|
NULL, REF_NODEREF, DIE_ON_ERR);
|
||||||
|
our_head_points_at = remote_head;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Nothing to checkout out */
|
/* Nothing to checkout out */
|
||||||
if (!option_no_checkout)
|
if (!option_no_checkout)
|
||||||
@ -597,7 +616,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||||||
opts.src_index = &the_index;
|
opts.src_index = &the_index;
|
||||||
opts.dst_index = &the_index;
|
opts.dst_index = &the_index;
|
||||||
|
|
||||||
tree = parse_tree_indirect(remote_head->old_sha1);
|
tree = parse_tree_indirect(our_head_points_at->old_sha1);
|
||||||
parse_tree(tree);
|
parse_tree(tree);
|
||||||
init_tree_desc(&t, tree->buffer, tree->size);
|
init_tree_desc(&t, tree->buffer, tree->size);
|
||||||
unpack_trees(1, &t, &opts);
|
unpack_trees(1, &t, &opts);
|
||||||
|
68
t/t5706-clone-branch.sh
Executable file
68
t/t5706-clone-branch.sh
Executable file
@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='clone --branch option'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
check_HEAD() {
|
||||||
|
echo refs/heads/"$1" >expect &&
|
||||||
|
git symbolic-ref HEAD >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
}
|
||||||
|
|
||||||
|
check_file() {
|
||||||
|
echo "$1" >expect &&
|
||||||
|
test_cmp expect file
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
mkdir parent &&
|
||||||
|
(cd parent && git init &&
|
||||||
|
echo one >file && git add file && git commit -m one &&
|
||||||
|
git checkout -b two &&
|
||||||
|
echo two >file && git add file && git commit -m two &&
|
||||||
|
git checkout master)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'vanilla clone chooses HEAD' '
|
||||||
|
git clone parent clone &&
|
||||||
|
(cd clone &&
|
||||||
|
check_HEAD master &&
|
||||||
|
check_file one
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'clone -b chooses specified branch' '
|
||||||
|
git clone -b two parent clone-two &&
|
||||||
|
(cd clone-two &&
|
||||||
|
check_HEAD two &&
|
||||||
|
check_file two
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'clone -b sets up tracking' '
|
||||||
|
(cd clone-two &&
|
||||||
|
echo origin >expect &&
|
||||||
|
git config branch.two.remote >actual &&
|
||||||
|
echo refs/heads/two >>expect &&
|
||||||
|
git config branch.two.merge >>actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
|
||||||
|
(cd clone-two &&
|
||||||
|
echo refs/remotes/origin/master >expect &&
|
||||||
|
git symbolic-ref refs/remotes/origin/HEAD >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'clone -b with bogus branch chooses HEAD' '
|
||||||
|
git clone -b bogus parent clone-bogus &&
|
||||||
|
(cd clone-bogus &&
|
||||||
|
check_HEAD master &&
|
||||||
|
check_file one
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in New Issue
Block a user