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>

Ralf Thielow committed Sep 20, 2012 at 20:04 UTC 31b808a03290a4f87c571cc1c61f26d8a03c7025
3 files changed +218 -18
Documentation/git-clone.txt
+11 -4
@@ -29,7 +29,8 @@ currently active branch.
29 After the clone, a plain `git fetch` without arguments will update
30 all the remote-tracking branches, and a `git pull` without
31 arguments will in addition merge the remote master branch into the
32 -current master branch, if any.
32 +current master branch, if any (this is untrue when "--single-branch"
33 +is given; see below).
34
35 This default configuration is achieved by creating references to
36 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.
148 -b <name>::
149 Instead of pointing the newly created HEAD to the branch pointed
150 to by the cloned repository's HEAD, point to `<name>` branch
150 - instead. `--branch` can also take tags and treat them like
151 - detached HEAD. In a non-bare repository, this is the branch
152 - that will be checked out.
151 + instead. In a non-bare repository, this is the branch that will
152 + be checked out.
153 + `--branch` can also take tags and detaches the HEAD at that commit
154 + in the resulting repository.
155
156 --upload-pack <upload-pack>::
157 -u <upload-pack>::
@@ -188,6 +190,11 @@ objects from the source repository into a pack in the cloned repository.
190 clone with the `--depth` option, this is the default, unless
191 `--no-single-branch` is given to fetch the histories near the
192 tips of all branches.
193 + Further fetches into the resulting repository will only update the
194 + remote tracking branch for the branch this option was used for the
195 + initial cloning. If the HEAD at the remote did not point at any
196 + branch when `--single-branch` clone was made, no remote tracking
197 + branch is created.
198
199 --recursive::
200 --recurse-submodules::
builtin/clone.c
+51 -14
@@ -610,6 +610,54 @@ static void write_config(struct string_list *config)
610 }
611 }
612
613 +static void write_refspec_config(const char* src_ref_prefix,
614 + const struct ref* our_head_points_at,
615 + const struct ref* remote_head_points_at, struct strbuf* branch_top)
616 +{
617 + struct strbuf key = STRBUF_INIT;
618 + struct strbuf value = STRBUF_INIT;
619 +
620 + if (option_mirror || !option_bare) {
621 + if (option_single_branch && !option_mirror) {
622 + if (option_branch) {
623 + if (strstr(our_head_points_at->name, "refs/tags/"))
624 + strbuf_addf(&value, "+%s:%s", our_head_points_at->name,
625 + our_head_points_at->name);
626 + else
627 + strbuf_addf(&value, "+%s:%s%s", our_head_points_at->name,
628 + branch_top->buf, option_branch);
629 + } else if (remote_head_points_at) {
630 + strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name,
631 + branch_top->buf,
632 + skip_prefix(remote_head_points_at->name, "refs/heads/"));
633 + }
634 + /*
635 + * otherwise, the next "git fetch" will
636 + * simply fetch from HEAD without updating
637 + * any remote tracking branch, which is what
638 + * we want.
639 + */
640 + } else {
641 + strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top->buf);
642 + }
643 + /* Configure the remote */
644 + if (value.len) {
645 + strbuf_addf(&key, "remote.%s.fetch", option_origin);
646 + git_config_set_multivar(key.buf, value.buf, "^$", 0);
647 + strbuf_reset(&key);
648 +
649 + if (option_mirror) {
650 + strbuf_addf(&key, "remote.%s.mirror", option_origin);
651 + git_config_set(key.buf, "true");
652 + strbuf_reset(&key);
653 + }
654 + }
655 + }
656 +
657 + strbuf_release(&key);
658 + strbuf_release(&value);
659 +}
660 +
661 int cmd_clone(int argc, const char **argv, const char *prefix)
662 {
663 int is_bundle = 0, is_local;
@@ -755,20 +803,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
803 }
804
805 strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
758 -
759 - if (option_mirror || !option_bare) {
760 - /* Configure the remote */
761 - strbuf_addf(&key, "remote.%s.fetch", option_origin);
762 - git_config_set_multivar(key.buf, value.buf, "^$", 0);
763 - strbuf_reset(&key);
764 -
765 - if (option_mirror) {
766 - strbuf_addf(&key, "remote.%s.mirror", option_origin);
767 - git_config_set(key.buf, "true");
768 - strbuf_reset(&key);
769 - }
770 - }
771 -
806 strbuf_addf(&key, "remote.%s.url", option_origin);
807 git_config_set(key.buf, repo);
808 strbuf_reset(&key);
@@ -853,6 +887,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
887 "refs/heads/master");
888 }
889
890 + write_refspec_config(src_ref_prefix, our_head_points_at,
891 + remote_head_points_at, &branch_top);
892 +
893 if (is_local)
894 clone_local(path, git_dir);
895 else if (refs && complete_refs_before_fetch)
t/t5709-clone-refspec.sh new
+156
@@ -0,0 +1,156 @@
1 +#!/bin/sh
2 +
3 +test_description='test refspec written by clone-command'
4 +. ./test-lib.sh
5 +
6 +test_expect_success 'setup' '
7 + # Make two branches, "master" and "side"
8 + echo one >file &&
9 + git add file &&
10 + git commit -m one &&
11 + echo two >file &&
12 + git commit -a -m two &&
13 + git tag two &&
14 + echo three >file &&
15 + git commit -a -m three &&
16 + git checkout -b side &&
17 + echo four >file &&
18 + git commit -a -m four &&
19 + git checkout master &&
20 +
21 + # default clone
22 + git clone . dir_all &&
23 +
24 + # default --single that follows HEAD=master
25 + git clone --single-branch . dir_master &&
26 +
27 + # default --single that follows HEAD=side
28 + git checkout side &&
29 + git clone --single-branch . dir_side &&
30 +
31 + # explicit --single that follows side
32 + git checkout master &&
33 + git clone --single-branch --branch side . dir_side2 &&
34 +
35 + # default --single with --mirror
36 + git clone --single-branch --mirror . dir_mirror &&
37 +
38 + # default --single with --branch and --mirror
39 + git clone --single-branch --mirror --branch side . dir_mirror_side &&
40 +
41 + # --single that does not know what branch to follow
42 + git checkout two^ &&
43 + git clone --single-branch . dir_detached &&
44 +
45 + # explicit --single with tag
46 + git clone --single-branch --branch two . dir_tag &&
47 +
48 + # advance both "master" and "side" branches
49 + git checkout side &&
50 + echo five >file &&
51 + git commit -a -m five &&
52 + git checkout master &&
53 + echo six >file &&
54 + git commit -a -m six &&
55 +
56 + # update tag
57 + git tag -d two && git tag two
58 +'
59 +
60 +test_expect_success 'by default all branches will be kept updated' '
61 + (
62 + cd dir_all && git fetch &&
63 + git for-each-ref refs/remotes/origin |
64 + sed -e "/HEAD$/d" \
65 + -e "s|/remotes/origin/|/heads/|" >../actual
66 + ) &&
67 + # follow both master and side
68 + git for-each-ref refs/heads >expect &&
69 + test_cmp expect actual
70 +'
71 +
72 +test_expect_success 'by default no tags will be kept updated' '
73 + (
74 + cd dir_all && git fetch &&
75 + git for-each-ref refs/tags >../actual
76 + ) &&
77 + git for-each-ref refs/tags >expect &&
78 + test_must_fail test_cmp expect actual
79 +'
80 +
81 +test_expect_success '--single-branch while HEAD pointing at master' '
82 + (
83 + cd dir_master && git fetch &&
84 + git for-each-ref refs/remotes/origin |
85 + sed -e "/HEAD$/d" \
86 + -e "s|/remotes/origin/|/heads/|" >../actual
87 + ) &&
88 + # only follow master
89 + git for-each-ref refs/heads/master >expect &&
90 + test_cmp expect actual
91 +'
92 +
93 +test_expect_success '--single-branch while HEAD pointing at side' '
94 + (
95 + cd dir_side && git fetch &&
96 + git for-each-ref refs/remotes/origin |
97 + sed -e "/HEAD$/d" \
98 + -e "s|/remotes/origin/|/heads/|" >../actual
99 + ) &&
100 + # only follow side
101 + git for-each-ref refs/heads/side >expect &&
102 + test_cmp expect actual
103 +'
104 +
105 +test_expect_success '--single-branch with explicit --branch side' '
106 + (
107 + cd dir_side2 && git fetch &&
108 + git for-each-ref refs/remotes/origin |
109 + sed -e "/HEAD$/d" \
110 + -e "s|/remotes/origin/|/heads/|" >../actual
111 + ) &&
112 + # only follow side
113 + git for-each-ref refs/heads/side >expect &&
114 + test_cmp expect actual
115 +'
116 +
117 +test_expect_success '--single-branch with explicit --branch with tag fetches updated tag' '
118 + (
119 + cd dir_tag && git fetch &&
120 + git for-each-ref refs/tags >../actual
121 + ) &&
122 + git for-each-ref refs/tags >expect &&
123 + test_cmp expect actual
124 +'
125 +
126 +test_expect_success '--single-branch with --mirror' '
127 + (
128 + cd dir_mirror && git fetch &&
129 + git for-each-ref refs > ../actual
130 + ) &&
131 + git for-each-ref refs >expect &&
132 + test_cmp expect actual
133 +'
134 +
135 +test_expect_success '--single-branch with explicit --branch and --mirror' '
136 + (
137 + cd dir_mirror_side && git fetch &&
138 + git for-each-ref refs > ../actual
139 + ) &&
140 + git for-each-ref refs >expect &&
141 + test_cmp expect actual
142 +'
143 +
144 +test_expect_success '--single-branch with detached' '
145 + (
146 + cd dir_detached && git fetch &&
147 + git for-each-ref refs/remotes/origin |
148 + sed -e "/HEAD$/d" \
149 + -e "s|/remotes/origin/|/heads/|" >../actual
150 + )
151 + # nothing
152 + >expect &&
153 + test_cmp expect actual
154 +'
155 +
156 +test_done