Skip to content

Commit 7859f53

Browse files
committed
Merge branch 'nd/clone-single-branch'
* nd/clone-single-branch: clone: add --single-branch to fetch only one branch
2 parents 86faaf9 + 3e6e0ed commit 7859f53

File tree

3 files changed

+129
-6
lines changed

3 files changed

+129
-6
lines changed

Documentation/git-clone.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ SYNOPSIS
1313
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
1414
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
1515
[--separate-git-dir <git dir>]
16-
[--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
16+
[--depth <depth>] [--[no-]single-branch]
17+
[--recursive|--recurse-submodules] [--] <repository>
1718
[<directory>]
1819

1920
DESCRIPTION
@@ -179,6 +180,14 @@ objects from the source repository into a pack in the cloned repository.
179180
with a long history, and would want to send in fixes
180181
as patches.
181182

183+
--single-branch::
184+
Clone only the history leading to the tip of a single branch,
185+
either specified by the `--branch` option or the primary
186+
branch remote's `HEAD` points at. When creating a shallow
187+
clone with the `--depth` option, this is the default, unless
188+
`--no-single-branch` is given to fetch the histories near the
189+
tips of all branches.
190+
182191
--recursive::
183192
--recurse-submodules::
184193
After the clone is created, initialize all submodules within,

builtin/clone.c

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ static const char * const builtin_clone_usage[] = {
3737
NULL
3838
};
3939

40-
static int option_no_checkout, option_bare, option_mirror;
40+
static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
4141
static int option_local, option_no_hardlinks, option_shared, option_recursive;
4242
static char *option_template, *option_depth;
4343
static char *option_origin = NULL;
@@ -48,6 +48,7 @@ static int option_verbosity;
4848
static int option_progress;
4949
static struct string_list option_config;
5050
static struct string_list option_reference;
51+
static const char *src_ref_prefix = "refs/heads/";
5152

5253
static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
5354
{
@@ -92,6 +93,8 @@ static struct option builtin_clone_options[] = {
9293
"path to git-upload-pack on the remote"),
9394
OPT_STRING(0, "depth", &option_depth, "depth",
9495
"create a shallow clone of that depth"),
96+
OPT_BOOL(0, "single-branch", &option_single_branch,
97+
"clone only one branch, HEAD or --branch"),
9598
OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
9699
"separate git dir from working tree"),
97100
OPT_STRING_LIST('c', "config", &option_config, "key=value",
@@ -427,8 +430,28 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
427430
struct ref *local_refs = head;
428431
struct ref **tail = head ? &head->next : &local_refs;
429432

430-
get_fetch_map(refs, refspec, &tail, 0);
431-
if (!option_mirror)
433+
if (option_single_branch) {
434+
struct ref *remote_head = NULL;
435+
436+
if (!option_branch)
437+
remote_head = guess_remote_head(head, refs, 0);
438+
else {
439+
struct strbuf sb = STRBUF_INIT;
440+
strbuf_addstr(&sb, src_ref_prefix);
441+
strbuf_addstr(&sb, option_branch);
442+
remote_head = find_ref_by_name(refs, sb.buf);
443+
strbuf_release(&sb);
444+
}
445+
446+
if (!remote_head && option_branch)
447+
warning(_("Could not find remote branch %s to clone."),
448+
option_branch);
449+
else
450+
get_fetch_map(remote_head, refspec, &tail, 0);
451+
} else
452+
get_fetch_map(refs, refspec, &tail, 0);
453+
454+
if (!option_mirror && !option_single_branch)
432455
get_fetch_map(refs, tag_refspec, &tail, 0);
433456

434457
return local_refs;
@@ -448,6 +471,21 @@ static void write_remote_refs(const struct ref *local_refs)
448471
clear_extra_refs();
449472
}
450473

474+
static void write_followtags(const struct ref *refs, const char *msg)
475+
{
476+
const struct ref *ref;
477+
for (ref = refs; ref; ref = ref->next) {
478+
if (prefixcmp(ref->name, "refs/tags/"))
479+
continue;
480+
if (!suffixcmp(ref->name, "^{}"))
481+
continue;
482+
if (!has_sha1_file(ref->old_sha1))
483+
continue;
484+
update_ref(msg, ref->name, ref->old_sha1,
485+
NULL, 0, DIE_ON_ERR);
486+
}
487+
}
488+
451489
static int write_one_config(const char *key, const char *value, void *data)
452490
{
453491
return git_config_set_multivar(key, value ? value : "true", "^$", 0);
@@ -478,7 +516,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
478516
struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
479517
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
480518
struct transport *transport = NULL;
481-
char *src_ref_prefix = "refs/heads/";
482519
int err = 0;
483520

484521
struct refspec *refspec;
@@ -498,6 +535,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
498535
usage_msg_opt(_("You must specify a repository to clone."),
499536
builtin_clone_usage, builtin_clone_options);
500537

538+
if (option_single_branch == -1)
539+
option_single_branch = option_depth ? 1 : 0;
540+
501541
if (option_mirror)
502542
option_bare = 1;
503543

@@ -645,6 +685,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
645685
if (option_depth)
646686
transport_set_option(transport, TRANS_OPT_DEPTH,
647687
option_depth);
688+
if (option_single_branch)
689+
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
648690

649691
transport_set_verbosity(transport, option_verbosity, option_progress);
650692

@@ -663,6 +705,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
663705
clear_extra_refs();
664706

665707
write_remote_refs(mapped_refs);
708+
if (option_single_branch)
709+
write_followtags(refs, reflog_msg.buf);
666710

667711
remote_head = find_ref_by_name(refs, "HEAD");
668712
remote_head_points_at =

t/t5500-fetch-pack.sh

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,19 @@ pull_to_client 2nd "refs/heads/B" $((64*3))
114114

115115
pull_to_client 3rd "refs/heads/A" $((1*3))
116116

117+
test_expect_success 'single branch clone' '
118+
git clone --single-branch "file://$(pwd)/." singlebranch
119+
'
120+
121+
test_expect_success 'single branch object count' '
122+
GIT_DIR=singlebranch/.git git count-objects -v |
123+
grep "^in-pack:" > count.singlebranch &&
124+
echo "in-pack: 198" >expected &&
125+
test_cmp expected count.singlebranch
126+
'
127+
117128
test_expect_success 'clone shallow' '
118-
git clone --depth 2 "file://$(pwd)/." shallow
129+
git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
119130
'
120131

121132
test_expect_success 'clone shallow object count' '
@@ -248,4 +259,63 @@ test_expect_success 'clone shallow object count' '
248259
grep "^count: 52" count.shallow
249260
'
250261

262+
test_expect_success 'clone shallow without --no-single-branch' '
263+
git clone --depth 1 "file://$(pwd)/." shallow2
264+
'
265+
266+
test_expect_success 'clone shallow object count' '
267+
(
268+
cd shallow2 &&
269+
git count-objects -v
270+
) > count.shallow2 &&
271+
grep "^in-pack: 6" count.shallow2
272+
'
273+
274+
test_expect_success 'clone shallow with --branch' '
275+
git clone --depth 1 --branch A "file://$(pwd)/." shallow3
276+
'
277+
278+
test_expect_success 'clone shallow object count' '
279+
echo "in-pack: 12" > count3.expected &&
280+
GIT_DIR=shallow3/.git git count-objects -v |
281+
grep "^in-pack" > count3.actual &&
282+
test_cmp count3.expected count3.actual
283+
'
284+
285+
test_expect_success 'clone shallow with nonexistent --branch' '
286+
git clone --depth 1 --branch Z "file://$(pwd)/." shallow4 &&
287+
GIT_DIR=shallow4/.git git rev-parse HEAD >actual &&
288+
git rev-parse HEAD >expected &&
289+
test_cmp expected actual
290+
'
291+
292+
test_expect_success 'clone shallow with detached HEAD' '
293+
git checkout HEAD^ &&
294+
git clone --depth 1 "file://$(pwd)/." shallow5 &&
295+
git checkout - &&
296+
GIT_DIR=shallow5/.git git rev-parse HEAD >actual &&
297+
git rev-parse HEAD^ >expected &&
298+
test_cmp expected actual
299+
'
300+
301+
test_expect_success 'shallow clone pulling tags' '
302+
git tag -a -m A TAGA1 A &&
303+
git tag -a -m B TAGB1 B &&
304+
git tag TAGA2 A &&
305+
git tag TAGB2 B &&
306+
git clone --depth 1 "file://$(pwd)/." shallow6 &&
307+
308+
cat >taglist.expected <<\EOF &&
309+
TAGB1
310+
TAGB2
311+
EOF
312+
GIT_DIR=shallow6/.git git tag -l >taglist.actual &&
313+
test_cmp taglist.expected taglist.actual &&
314+
315+
echo "in-pack: 7" > count6.expected &&
316+
GIT_DIR=shallow6/.git git count-objects -v |
317+
grep "^in-pack" > count6.actual &&
318+
test_cmp count6.expected count6.actual
319+
'
320+
251321
test_done

0 commit comments

Comments
 (0)