Skip to content

Commit

Permalink
clone: add --single-branch to fetch only one branch
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Nguyễn Thái Ngọc Duy authored and Junio C Hamano committed Jan 8, 2012
1 parent 4570aeb commit 3e6e0ed
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 6 deletions.
11 changes: 10 additions & 1 deletion Documentation/git-clone.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ SYNOPSIS
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
[--separate-git-dir <git dir>]
[--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
[--depth <depth>] [--[no-]single-branch]
[--recursive|--recurse-submodules] [--] <repository>
[<directory>]

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

--single-branch::
Clone only the history leading to the tip of a single branch,
either specified by the `--branch` option or the primary
branch remote's `HEAD` points at. When creating a shallow
clone with the `--depth` option, this is the default, unless
`--no-single-branch` is given to fetch the histories near the
tips of all branches.

--recursive::
--recurse-submodules::
After the clone is created, initialize all submodules within,
Expand Down
52 changes: 48 additions & 4 deletions builtin/clone.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ static const char * const builtin_clone_usage[] = {
NULL
};

static int option_no_checkout, option_bare, option_mirror;
static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
static int option_local, option_no_hardlinks, option_shared, option_recursive;
static char *option_template, *option_depth;
static char *option_origin = NULL;
Expand All @@ -48,6 +48,7 @@ static int option_verbosity;
static int option_progress;
static struct string_list option_config;
static struct string_list option_reference;
static const char *src_ref_prefix = "refs/heads/";

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

get_fetch_map(refs, refspec, &tail, 0);
if (!option_mirror)
if (option_single_branch) {
struct ref *remote_head = NULL;

if (!option_branch)
remote_head = guess_remote_head(head, refs, 0);
else {
struct strbuf sb = STRBUF_INIT;
strbuf_addstr(&sb, src_ref_prefix);
strbuf_addstr(&sb, option_branch);
remote_head = find_ref_by_name(refs, sb.buf);
strbuf_release(&sb);
}

if (!remote_head && option_branch)
warning(_("Could not find remote branch %s to clone."),
option_branch);
else
get_fetch_map(remote_head, refspec, &tail, 0);
} else
get_fetch_map(refs, refspec, &tail, 0);

if (!option_mirror && !option_single_branch)
get_fetch_map(refs, tag_refspec, &tail, 0);

return local_refs;
Expand All @@ -448,6 +471,21 @@ static void write_remote_refs(const struct ref *local_refs)
clear_extra_refs();
}

static void write_followtags(const struct ref *refs, const char *msg)
{
const struct ref *ref;
for (ref = refs; ref; ref = ref->next) {
if (prefixcmp(ref->name, "refs/tags/"))
continue;
if (!suffixcmp(ref->name, "^{}"))
continue;
if (!has_sha1_file(ref->old_sha1))
continue;
update_ref(msg, ref->name, ref->old_sha1,
NULL, 0, DIE_ON_ERR);
}
}

static int write_one_config(const char *key, const char *value, void *data)
{
return git_config_set_multivar(key, value ? value : "true", "^$", 0);
Expand Down Expand Up @@ -478,7 +516,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
struct transport *transport = NULL;
char *src_ref_prefix = "refs/heads/";
int err = 0;

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

if (option_single_branch == -1)
option_single_branch = option_depth ? 1 : 0;

if (option_mirror)
option_bare = 1;

Expand Down Expand Up @@ -645,6 +685,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_depth)
transport_set_option(transport, TRANS_OPT_DEPTH,
option_depth);
if (option_single_branch)
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");

transport_set_verbosity(transport, option_verbosity, option_progress);

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

write_remote_refs(mapped_refs);
if (option_single_branch)
write_followtags(refs, reflog_msg.buf);

remote_head = find_ref_by_name(refs, "HEAD");
remote_head_points_at =
Expand Down
72 changes: 71 additions & 1 deletion t/t5500-fetch-pack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,19 @@ pull_to_client 2nd "refs/heads/B" $((64*3))

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

test_expect_success 'single branch clone' '
git clone --single-branch "file://$(pwd)/." singlebranch
'

test_expect_success 'single branch object count' '
GIT_DIR=singlebranch/.git git count-objects -v |
grep "^in-pack:" > count.singlebranch &&
echo "in-pack: 198" >expected &&
test_cmp expected count.singlebranch
'

test_expect_success 'clone shallow' '
git clone --depth 2 "file://$(pwd)/." shallow
git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
'

test_expect_success 'clone shallow object count' '
Expand Down Expand Up @@ -248,4 +259,63 @@ test_expect_success 'clone shallow object count' '
grep "^count: 52" count.shallow
'

test_expect_success 'clone shallow without --no-single-branch' '
git clone --depth 1 "file://$(pwd)/." shallow2
'

test_expect_success 'clone shallow object count' '
(
cd shallow2 &&
git count-objects -v
) > count.shallow2 &&
grep "^in-pack: 6" count.shallow2
'

test_expect_success 'clone shallow with --branch' '
git clone --depth 1 --branch A "file://$(pwd)/." shallow3
'

test_expect_success 'clone shallow object count' '
echo "in-pack: 12" > count3.expected &&
GIT_DIR=shallow3/.git git count-objects -v |
grep "^in-pack" > count3.actual &&
test_cmp count3.expected count3.actual
'

test_expect_success 'clone shallow with nonexistent --branch' '
git clone --depth 1 --branch Z "file://$(pwd)/." shallow4 &&
GIT_DIR=shallow4/.git git rev-parse HEAD >actual &&
git rev-parse HEAD >expected &&
test_cmp expected actual
'

test_expect_success 'clone shallow with detached HEAD' '
git checkout HEAD^ &&
git clone --depth 1 "file://$(pwd)/." shallow5 &&
git checkout - &&
GIT_DIR=shallow5/.git git rev-parse HEAD >actual &&
git rev-parse HEAD^ >expected &&
test_cmp expected actual
'

test_expect_success 'shallow clone pulling tags' '
git tag -a -m A TAGA1 A &&
git tag -a -m B TAGB1 B &&
git tag TAGA2 A &&
git tag TAGB2 B &&
git clone --depth 1 "file://$(pwd)/." shallow6 &&
cat >taglist.expected <<\EOF &&
TAGB1
TAGB2
EOF
GIT_DIR=shallow6/.git git tag -l >taglist.actual &&
test_cmp taglist.expected taglist.actual &&
echo "in-pack: 7" > count6.expected &&
GIT_DIR=shallow6/.git git count-objects -v |
grep "^in-pack" > count6.actual &&
test_cmp count6.expected count6.actual
'

test_done

0 comments on commit 3e6e0ed

Please sign in to comment.