clone --single: limit the fetch refspec to fetched branch

After running "git clone --single", the resulting repository has the
usual default "+refs/heads/*:refs/remotes/origin/*" wildcard fetch
refspec installed, which means that a subsequent "git fetch" will
end up grabbing all the other branches.

Update the fetch refspec to cover only the singly cloned ref instead
to correct this.

That means:
If "--single" is used without "--branch" or "--mirror", the
fetch refspec covers the branch on which remote's HEAD points to.
If "--single" is used with "--branch", it'll cover only the branch
specified in the "--branch" option.
If "--single" is combined with "--mirror", then it'll cover all
refs of the cloned repository.
If "--single" is used with "--branch" that specifies a tag, then
it'll cover only the ref for this tag.

Signed-off-by: Ralf Thielow <ralf.thielow@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Ralf Thielow 2012-09-20 20:04:08 +02:00 committed by Junio C Hamano
parent 1403db49b8
commit 31b808a032
3 changed files with 218 additions and 18 deletions

View File

@ -29,7 +29,8 @@ currently active branch.
After the clone, a plain `git fetch` without arguments will update After the clone, a plain `git fetch` without arguments will update
all the remote-tracking branches, and a `git pull` without all the remote-tracking branches, and a `git pull` without
arguments will in addition merge the remote master branch into the arguments will in addition merge the remote master branch into the
current master branch, if any. current master branch, if any (this is untrue when "--single-branch"
is given; see below).
This default configuration is achieved by creating references to This default configuration is achieved by creating references to
the remote branch heads under `refs/remotes/origin` and the remote branch heads under `refs/remotes/origin` and
@ -147,9 +148,10 @@ objects from the source repository into a pack in the cloned repository.
-b <name>:: -b <name>::
Instead of pointing the newly created HEAD to the branch pointed Instead of pointing the newly created HEAD to the branch pointed
to by the cloned repository's HEAD, point to `<name>` branch to by the cloned repository's HEAD, point to `<name>` branch
instead. `--branch` can also take tags and treat them like instead. In a non-bare repository, this is the branch that will
detached HEAD. In a non-bare repository, this is the branch be checked out.
that will be checked out. `--branch` can also take tags and detaches the HEAD at that commit
in the resulting repository.
--upload-pack <upload-pack>:: --upload-pack <upload-pack>::
-u <upload-pack>:: -u <upload-pack>::
@ -188,6 +190,11 @@ objects from the source repository into a pack in the cloned repository.
clone with the `--depth` option, this is the default, unless clone with the `--depth` option, this is the default, unless
`--no-single-branch` is given to fetch the histories near the `--no-single-branch` is given to fetch the histories near the
tips of all branches. tips of all branches.
Further fetches into the resulting repository will only update the
remote tracking branch for the branch this option was used for the
initial cloning. If the HEAD at the remote did not point at any
branch when `--single-branch` clone was made, no remote tracking
branch is created.
--recursive:: --recursive::
--recurse-submodules:: --recurse-submodules::

View File

@ -610,6 +610,54 @@ static void write_config(struct string_list *config)
} }
} }
static void write_refspec_config(const char* src_ref_prefix,
const struct ref* our_head_points_at,
const struct ref* remote_head_points_at, struct strbuf* branch_top)
{
struct strbuf key = STRBUF_INIT;
struct strbuf value = STRBUF_INIT;
if (option_mirror || !option_bare) {
if (option_single_branch && !option_mirror) {
if (option_branch) {
if (strstr(our_head_points_at->name, "refs/tags/"))
strbuf_addf(&value, "+%s:%s", our_head_points_at->name,
our_head_points_at->name);
else
strbuf_addf(&value, "+%s:%s%s", our_head_points_at->name,
branch_top->buf, option_branch);
} else if (remote_head_points_at) {
strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name,
branch_top->buf,
skip_prefix(remote_head_points_at->name, "refs/heads/"));
}
/*
* otherwise, the next "git fetch" will
* simply fetch from HEAD without updating
* any remote tracking branch, which is what
* we want.
*/
} else {
strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top->buf);
}
/* Configure the remote */
if (value.len) {
strbuf_addf(&key, "remote.%s.fetch", option_origin);
git_config_set_multivar(key.buf, value.buf, "^$", 0);
strbuf_reset(&key);
if (option_mirror) {
strbuf_addf(&key, "remote.%s.mirror", option_origin);
git_config_set(key.buf, "true");
strbuf_reset(&key);
}
}
}
strbuf_release(&key);
strbuf_release(&value);
}
int cmd_clone(int argc, const char **argv, const char *prefix) int cmd_clone(int argc, const char **argv, const char *prefix)
{ {
int is_bundle = 0, is_local; int is_bundle = 0, is_local;
@ -755,20 +803,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
} }
strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf); strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
if (option_mirror || !option_bare) {
/* Configure the remote */
strbuf_addf(&key, "remote.%s.fetch", option_origin);
git_config_set_multivar(key.buf, value.buf, "^$", 0);
strbuf_reset(&key);
if (option_mirror) {
strbuf_addf(&key, "remote.%s.mirror", option_origin);
git_config_set(key.buf, "true");
strbuf_reset(&key);
}
}
strbuf_addf(&key, "remote.%s.url", option_origin); strbuf_addf(&key, "remote.%s.url", option_origin);
git_config_set(key.buf, repo); git_config_set(key.buf, repo);
strbuf_reset(&key); strbuf_reset(&key);
@ -853,6 +887,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
"refs/heads/master"); "refs/heads/master");
} }
write_refspec_config(src_ref_prefix, our_head_points_at,
remote_head_points_at, &branch_top);
if (is_local) if (is_local)
clone_local(path, git_dir); clone_local(path, git_dir);
else if (refs && complete_refs_before_fetch) else if (refs && complete_refs_before_fetch)

156
t/t5709-clone-refspec.sh Executable file
View File

@ -0,0 +1,156 @@
#!/bin/sh
test_description='test refspec written by clone-command'
. ./test-lib.sh
test_expect_success 'setup' '
# Make two branches, "master" and "side"
echo one >file &&
git add file &&
git commit -m one &&
echo two >file &&
git commit -a -m two &&
git tag two &&
echo three >file &&
git commit -a -m three &&
git checkout -b side &&
echo four >file &&
git commit -a -m four &&
git checkout master &&
# default clone
git clone . dir_all &&
# default --single that follows HEAD=master
git clone --single-branch . dir_master &&
# default --single that follows HEAD=side
git checkout side &&
git clone --single-branch . dir_side &&
# explicit --single that follows side
git checkout master &&
git clone --single-branch --branch side . dir_side2 &&
# default --single with --mirror
git clone --single-branch --mirror . dir_mirror &&
# default --single with --branch and --mirror
git clone --single-branch --mirror --branch side . dir_mirror_side &&
# --single that does not know what branch to follow
git checkout two^ &&
git clone --single-branch . dir_detached &&
# explicit --single with tag
git clone --single-branch --branch two . dir_tag &&
# advance both "master" and "side" branches
git checkout side &&
echo five >file &&
git commit -a -m five &&
git checkout master &&
echo six >file &&
git commit -a -m six &&
# update tag
git tag -d two && git tag two
'
test_expect_success 'by default all branches will be kept updated' '
(
cd dir_all && git fetch &&
git for-each-ref refs/remotes/origin |
sed -e "/HEAD$/d" \
-e "s|/remotes/origin/|/heads/|" >../actual
) &&
# follow both master and side
git for-each-ref refs/heads >expect &&
test_cmp expect actual
'
test_expect_success 'by default no tags will be kept updated' '
(
cd dir_all && git fetch &&
git for-each-ref refs/tags >../actual
) &&
git for-each-ref refs/tags >expect &&
test_must_fail test_cmp expect actual
'
test_expect_success '--single-branch while HEAD pointing at master' '
(
cd dir_master && git fetch &&
git for-each-ref refs/remotes/origin |
sed -e "/HEAD$/d" \
-e "s|/remotes/origin/|/heads/|" >../actual
) &&
# only follow master
git for-each-ref refs/heads/master >expect &&
test_cmp expect actual
'
test_expect_success '--single-branch while HEAD pointing at side' '
(
cd dir_side && git fetch &&
git for-each-ref refs/remotes/origin |
sed -e "/HEAD$/d" \
-e "s|/remotes/origin/|/heads/|" >../actual
) &&
# only follow side
git for-each-ref refs/heads/side >expect &&
test_cmp expect actual
'
test_expect_success '--single-branch with explicit --branch side' '
(
cd dir_side2 && git fetch &&
git for-each-ref refs/remotes/origin |
sed -e "/HEAD$/d" \
-e "s|/remotes/origin/|/heads/|" >../actual
) &&
# only follow side
git for-each-ref refs/heads/side >expect &&
test_cmp expect actual
'
test_expect_success '--single-branch with explicit --branch with tag fetches updated tag' '
(
cd dir_tag && git fetch &&
git for-each-ref refs/tags >../actual
) &&
git for-each-ref refs/tags >expect &&
test_cmp expect actual
'
test_expect_success '--single-branch with --mirror' '
(
cd dir_mirror && git fetch &&
git for-each-ref refs > ../actual
) &&
git for-each-ref refs >expect &&
test_cmp expect actual
'
test_expect_success '--single-branch with explicit --branch and --mirror' '
(
cd dir_mirror_side && git fetch &&
git for-each-ref refs > ../actual
) &&
git for-each-ref refs >expect &&
test_cmp expect actual
'
test_expect_success '--single-branch with detached' '
(
cd dir_detached && git fetch &&
git for-each-ref refs/remotes/origin |
sed -e "/HEAD$/d" \
-e "s|/remotes/origin/|/heads/|" >../actual
)
# nothing
>expect &&
test_cmp expect actual
'
test_done