Skip to content

Commit 3e6e0ed

Browse files
pcloudsgitster
authored andcommitted
clone: add --single-branch to fetch only one branch
When --single-branch is given, only one branch, either HEAD or one specified by --branch, will be fetched. Also only tags that point to the downloaded history are fetched. This helps most in shallow clones, where it can reduce the download to minimum and that is why it is enabled by default when --depth is given. Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 4570aeb commit 3e6e0ed

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)