Skip to content

Commit 7a4ee28

Browse files
peffgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 68ea474 commit 7a4ee28

File tree

3 files changed

+122
-28
lines changed

3 files changed

+122
-28
lines changed

Documentation/git-clone.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ objects from the source repository into a pack in the cloned repository.
127127
Instead of using the remote name 'origin' to keep track
128128
of the upstream repository, use <name>.
129129

130+
--branch <name>::
131+
-b <name>::
132+
Instead of pointing the newly created HEAD to the branch pointed
133+
to by the cloned repositoroy's HEAD, point to <name> branch
134+
instead. In a non-bare repository, this is the branch that will
135+
be checked out.
136+
130137
--upload-pack <upload-pack>::
131138
-u <upload-pack>::
132139
When given, and the repository to clone from is accessed

builtin-clone.c

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ static int option_quiet, option_no_checkout, option_bare, option_mirror;
4141
static int option_local, option_no_hardlinks, option_shared;
4242
static char *option_template, *option_reference, *option_depth;
4343
static char *option_origin = NULL;
44+
static char *option_branch = NULL;
4445
static char *option_upload_pack = "git-upload-pack";
4546
static int option_verbose;
4647

@@ -65,6 +66,8 @@ static struct option builtin_clone_options[] = {
6566
"reference repository"),
6667
OPT_STRING('o', "origin", &option_origin, "branch",
6768
"use <branch> instead of 'origin' to track upstream"),
69+
OPT_STRING('b', "branch", &option_branch, "branch",
70+
"checkout <branch> instead of the remote's HEAD"),
6871
OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
6972
"path to git-upload-pack on the remote"),
7073
OPT_STRING(0, "depth", &option_depth, "depth",
@@ -347,7 +350,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
347350
const char *repo_name, *repo, *work_tree, *git_dir;
348351
char *path, *dir;
349352
int dest_exists;
350-
const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
353+
const struct ref *refs, *remote_head, *mapped_refs;
354+
const struct ref *remote_head_points_at;
355+
const struct ref *our_head_points_at;
351356
struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
352357
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
353358
struct transport *transport = NULL;
@@ -519,53 +524,67 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
519524
mapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf);
520525

521526
remote_head = find_ref_by_name(refs, "HEAD");
522-
head_points_at = guess_remote_head(remote_head, mapped_refs, 0);
527+
remote_head_points_at =
528+
guess_remote_head(remote_head, mapped_refs, 0);
529+
530+
if (option_branch) {
531+
struct strbuf head = STRBUF_INIT;
532+
strbuf_addstr(&head, src_ref_prefix);
533+
strbuf_addstr(&head, option_branch);
534+
our_head_points_at =
535+
find_ref_by_name(mapped_refs, head.buf);
536+
strbuf_release(&head);
537+
538+
if (!our_head_points_at) {
539+
warning("Remote branch %s not found in "
540+
"upstream %s, using HEAD instead",
541+
option_branch, option_origin);
542+
our_head_points_at = remote_head_points_at;
543+
}
544+
}
545+
else
546+
our_head_points_at = remote_head_points_at;
523547
}
524548
else {
525549
warning("You appear to have cloned an empty repository.");
526-
head_points_at = NULL;
550+
our_head_points_at = NULL;
551+
remote_head_points_at = NULL;
527552
remote_head = NULL;
528553
option_no_checkout = 1;
529554
if (!option_bare)
530555
install_branch_config(0, "master", option_origin,
531556
"refs/heads/master");
532557
}
533558

534-
if (head_points_at) {
535-
/* Local default branch link */
536-
create_symref("HEAD", head_points_at->name, NULL);
559+
if (remote_head_points_at && !option_bare) {
560+
struct strbuf head_ref = STRBUF_INIT;
561+
strbuf_addstr(&head_ref, branch_top.buf);
562+
strbuf_addstr(&head_ref, "HEAD");
563+
create_symref(head_ref.buf,
564+
remote_head_points_at->peer_ref->name,
565+
reflog_msg.buf);
566+
}
537567

568+
if (our_head_points_at) {
569+
/* Local default branch link */
570+
create_symref("HEAD", our_head_points_at->name, NULL);
538571
if (!option_bare) {
539-
struct strbuf head_ref = STRBUF_INIT;
540-
const char *head = head_points_at->name;
541-
542-
if (!prefixcmp(head, "refs/heads/"))
543-
head += 11;
544-
545-
/* Set up the initial local branch */
546-
547-
/* Local branch initial value */
572+
const char *head = skip_prefix(our_head_points_at->name,
573+
"refs/heads/");
548574
update_ref(reflog_msg.buf, "HEAD",
549-
head_points_at->old_sha1,
575+
our_head_points_at->old_sha1,
550576
NULL, 0, DIE_ON_ERR);
551-
552-
strbuf_addstr(&head_ref, branch_top.buf);
553-
strbuf_addstr(&head_ref, "HEAD");
554-
555-
/* Remote branch link */
556-
create_symref(head_ref.buf,
557-
head_points_at->peer_ref->name,
558-
reflog_msg.buf);
559-
560577
install_branch_config(0, head, option_origin,
561-
head_points_at->name);
578+
our_head_points_at->name);
562579
}
563580
} else if (remote_head) {
564581
/* Source had detached HEAD pointing somewhere. */
565-
if (!option_bare)
582+
if (!option_bare) {
566583
update_ref(reflog_msg.buf, "HEAD",
567584
remote_head->old_sha1,
568585
NULL, REF_NODEREF, DIE_ON_ERR);
586+
our_head_points_at = remote_head;
587+
}
569588
} else {
570589
/* Nothing to checkout out */
571590
if (!option_no_checkout)
@@ -597,7 +616,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
597616
opts.src_index = &the_index;
598617
opts.dst_index = &the_index;
599618

600-
tree = parse_tree_indirect(remote_head->old_sha1);
619+
tree = parse_tree_indirect(our_head_points_at->old_sha1);
601620
parse_tree(tree);
602621
init_tree_desc(&t, tree->buffer, tree->size);
603622
unpack_trees(1, &t, &opts);

t/t5706-clone-branch.sh

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/bin/sh
2+
3+
test_description='clone --branch option'
4+
. ./test-lib.sh
5+
6+
check_HEAD() {
7+
echo refs/heads/"$1" >expect &&
8+
git symbolic-ref HEAD >actual &&
9+
test_cmp expect actual
10+
}
11+
12+
check_file() {
13+
echo "$1" >expect &&
14+
test_cmp expect file
15+
}
16+
17+
test_expect_success 'setup' '
18+
mkdir parent &&
19+
(cd parent && git init &&
20+
echo one >file && git add file && git commit -m one &&
21+
git checkout -b two &&
22+
echo two >file && git add file && git commit -m two &&
23+
git checkout master)
24+
'
25+
26+
test_expect_success 'vanilla clone chooses HEAD' '
27+
git clone parent clone &&
28+
(cd clone &&
29+
check_HEAD master &&
30+
check_file one
31+
)
32+
'
33+
34+
test_expect_success 'clone -b chooses specified branch' '
35+
git clone -b two parent clone-two &&
36+
(cd clone-two &&
37+
check_HEAD two &&
38+
check_file two
39+
)
40+
'
41+
42+
test_expect_success 'clone -b sets up tracking' '
43+
(cd clone-two &&
44+
echo origin >expect &&
45+
git config branch.two.remote >actual &&
46+
echo refs/heads/two >>expect &&
47+
git config branch.two.merge >>actual &&
48+
test_cmp expect actual
49+
)
50+
'
51+
52+
test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
53+
(cd clone-two &&
54+
echo refs/remotes/origin/master >expect &&
55+
git symbolic-ref refs/remotes/origin/HEAD >actual &&
56+
test_cmp expect actual
57+
)
58+
'
59+
60+
test_expect_success 'clone -b with bogus branch chooses HEAD' '
61+
git clone -b bogus parent clone-bogus &&
62+
(cd clone-bogus &&
63+
check_HEAD master &&
64+
check_file one
65+
)
66+
'
67+
68+
test_done

0 commit comments

Comments
 (0)