Merge branch 'rt/maint-clone-single'

Running "git fetch" in a repository made with "git clone --single"
slurps all the branches, defeating the point of "--single".

* rt/maint-clone-single:
  clone --single: limit the fetch refspec to fetched branch
This commit is contained in:
Junio C Hamano 2012-10-01 12:58:10 -07:00
commit 92f6e98c69
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
all the remote-tracking branches, and a `git pull` without
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
the remote branch heads under `refs/remotes/origin` and
@ -152,9 +153,10 @@ objects from the source repository into a pack in the cloned repository.
-b <name>::
Instead of pointing the newly created HEAD to the branch pointed
to by the cloned repository's HEAD, point to `<name>` branch
instead. `--branch` can also take tags and treat them like
detached HEAD. In a non-bare repository, this is the branch
that will be checked out.
instead. In a non-bare repository, this is the branch 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>::
-u <upload-pack>::
@ -193,6 +195,11 @@ objects from the source repository into a pack in the cloned repository.
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.
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::
--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 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);
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);
git_config_set(key.buf, repo);
strbuf_reset(&key);
@ -853,6 +887,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
"refs/heads/master");
}
write_refspec_config(src_ref_prefix, our_head_points_at,
remote_head_points_at, &branch_top);
if (is_local)
clone_local(path, git_dir);
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