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 <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Nguyễn Thái Ngọc Duy committed
Jan 7, 2012 at 21:45 UTC
3e6e0edde20f536b28cf1dac04c7376000bfc701
3 files changed
+129
-6
Documentation/git-clone.txt
+10
-1
@@ -13,7 +13,8 @@ SYNOPSIS
13
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
14
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
15
[--separate-git-dir <git dir>]
16
- [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
16
+ [--depth <depth>] [--[no-]single-branch]
17
+ [--recursive|--recurse-submodules] [--] <repository>
18
[<directory>]
19
20
DESCRIPTION
@@ -179,6 +180,14 @@ objects from the source repository into a pack in the cloned repository.
180
with a long history, and would want to send in fixes
181
as patches.
182
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
+
191
--recursive::
192
--recurse-submodules::
193
After the clone is created, initialize all submodules within,
builtin/clone.c
+48
-4
@@ -37,7 +37,7 @@ static const char * const builtin_clone_usage[] = {
37
NULL
38
};
39
40
-static int option_no_checkout, option_bare, option_mirror;
40
+static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
41
static int option_local, option_no_hardlinks, option_shared, option_recursive;
42
static char *option_template, *option_depth;
43
static char *option_origin = NULL;
@@ -48,6 +48,7 @@ static int option_verbosity;
48
static int option_progress;
49
static struct string_list option_config;
50
static struct string_list option_reference;
51
+static const char *src_ref_prefix = "refs/heads/";
52
53
static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
54
{
@@ -92,6 +93,8 @@ static struct option builtin_clone_options[] = {
93
"path to git-upload-pack on the remote"),
94
OPT_STRING(0, "depth", &option_depth, "depth",
95
"create a shallow clone of that depth"),
96
+ OPT_BOOL(0, "single-branch", &option_single_branch,
97
+ "clone only one branch, HEAD or --branch"),
98
OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
99
"separate git dir from working tree"),
100
OPT_STRING_LIST('c', "config", &option_config, "key=value",
@@ -427,8 +430,28 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
430
struct ref *local_refs = head;
431
struct ref **tail = head ? &head->next : &local_refs;
432
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)
455
get_fetch_map(refs, tag_refspec, &tail, 0);
456
457
return local_refs;
@@ -448,6 +471,21 @@ static void write_remote_refs(const struct ref *local_refs)
471
clear_extra_refs();
472
}
473
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
+
489
static int write_one_config(const char *key, const char *value, void *data)
490
{
491
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)
516
struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
517
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
518
struct transport *transport = NULL;
481
- char *src_ref_prefix = "refs/heads/";
519
int err = 0;
520
521
struct refspec *refspec;
@@ -498,6 +535,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
535
usage_msg_opt(_("You must specify a repository to clone."),
536
builtin_clone_usage, builtin_clone_options);
537
538
+ if (option_single_branch == -1)
539
+ option_single_branch = option_depth ? 1 : 0;
540
+
541
if (option_mirror)
542
option_bare = 1;
543
@@ -645,6 +685,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
685
if (option_depth)
686
transport_set_option(transport, TRANS_OPT_DEPTH,
687
option_depth);
688
+ if (option_single_branch)
689
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
690
691
transport_set_verbosity(transport, option_verbosity, option_progress);
692
@@ -663,6 +705,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
705
clear_extra_refs();
706
707
write_remote_refs(mapped_refs);
708
+ if (option_single_branch)
709
+ write_followtags(refs, reflog_msg.buf);
710
711
remote_head = find_ref_by_name(refs, "HEAD");
712
remote_head_points_at =
t/t5500-fetch-pack.sh
+71
-1
@@ -114,8 +114,19 @@ pull_to_client 2nd "refs/heads/B" $((64*3))
114
115
pull_to_client 3rd "refs/heads/A" $((1*3))
116
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
+
128
test_expect_success 'clone shallow' '
118
- git clone --depth 2 "file://$(pwd)/." shallow
129
+ git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
130
'
131
132
test_expect_success 'clone shallow object count' '
@@ -248,4 +259,63 @@ test_expect_success 'clone shallow object count' '
259
grep "^count: 52" count.shallow
260
'
261
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
+
321
test_done