clone: add a --no-tags option to clone without tags

Add a --no-tags option to clone without fetching any tags. Without this change there's no easy way to clone a repository without also fetching its tags. When supplying --single-branch the primary remote branch will be cloned, but in addition tags will be followed & retrieved. Now --no-tags can be added --single-branch to clone a repository without tags, and which only tracks a single upstream branch. This option works without --single-branch as well, and will do a normal clone but not fetch any tags. Many git commands pay some fixed overhead as a function of the number of references. E.g. creating ~40k tags in linux.git will cause a command like `git log -1 >/dev/null` to run in over a second instead of in a matter of milliseconds, in addition numerous other things will slow down, e.g. "git log <TAB>" with the bash completion will slowly show ~40k references instead of 1. The user might want to avoid all of that overhead to simply use a repository like that to browse the "master" branch, or something like a CI tool might want to keep that one branch up-to-date without caring about any other references. Without this change the only way of accomplishing this was either by manually tweaking the config in a fresh repository: git init git && cat >git/.git/config <<EOF && [remote "origin"] url = git@github.com:git/git.git tagOpt = --no-tags fetch = +refs/heads/master:refs/remotes/origin/master [branch "master"] remote = origin merge = refs/heads/master EOF cd git && git pull Which requires hardcoding the "master" name, which may not be the main --single-branch would have retrieved, or alternatively by setting tagOpt=--no-tags right after cloning & deleting any existing tags: git clone --single-branch git@github.com:git/git.git && cd git && git config remote.origin.tagOpt --no-tags && git tag -l | xargs git tag -d Which of course was also subtly buggy if --branch was pointed at a tag, leaving the user in a detached head: git clone --single-branch --branch v2.12.0 git@github.com:git/git.git && cd git && git config remote.origin.tagOpt --no-tags && git tag -l | xargs git tag -d Now all this complexity becomes the much simpler: git clone --single-branch --no-tags git@github.com:git/git.git Or in the case of cloning a single tag "branch": git clone --single-branch --branch v2.12.0 --no-tags git@github.com:git/git.git Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed Apr 26, 2017 at 23:12 UTC 0dab2468ee5bbfaa854a22eb17c70647fc8b6b83
4 files changed +99 -5
Documentation/git-clone.txt
+13 -1
@@ -13,7 +13,7 @@ SYNOPSIS
13 [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
14 [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
15 [--dissociate] [--separate-git-dir <git dir>]
16 - [--depth <depth>] [--[no-]single-branch]
16 + [--depth <depth>] [--[no-]single-branch] [--no-tags]
17 [--recurse-submodules] [--[no-]shallow-submodules]
18 [--jobs <n>] [--] <repository> [<directory>]
19
@@ -215,6 +215,18 @@ objects from the source repository into a pack in the cloned repository.
215 branch when `--single-branch` clone was made, no remote-tracking
216 branch is created.
217
218 +--no-tags::
219 + Don't clone any tags, and set
220 + `remote.<remote>.tagOpt=--no-tags` in the config, ensuring
221 + that future `git pull` and `git fetch` operations won't follow
222 + any tags. Subsequent explicit tag fetches will still work,
223 + (see linkgit:git-fetch[1]).
224 ++
225 +Can be used in conjunction with `--single-branch` to clone and
226 +maintain a branch with no references other than a single cloned
227 +branch. This is useful e.g. to maintain minimal clones of the default
228 +branch of some repository for search indexing.
229 +
230 --recurse-submodules[=<pathspec]::
231 After the clone is created, initialize and clone submodules
232 within based on the provided pathspec. If no pathspec is
builtin/clone.c
+11 -2
@@ -40,6 +40,7 @@ static const char * const builtin_clone_usage[] = {
40
41 static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
42 static int option_local = -1, option_no_hardlinks, option_shared;
43 +static int option_no_tags;
44 static int option_shallow_submodules;
45 static int deepen;
46 static char *option_template, *option_depth, *option_since;
@@ -120,6 +121,8 @@ static struct option builtin_clone_options[] = {
121 N_("deepen history of shallow clone, excluding rev")),
122 OPT_BOOL(0, "single-branch", &option_single_branch,
123 N_("clone only one branch, HEAD or --branch")),
124 + OPT_BOOL(0, "no-tags", &option_no_tags,
125 + N_("don't clone any tags, and make later fetches not to follow them")),
126 OPT_BOOL(0, "shallow-submodules", &option_shallow_submodules,
127 N_("any cloned submodules will be shallow")),
128 OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
@@ -563,7 +566,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
566 } else
567 get_fetch_map(refs, refspec, &tail, 0);
568
566 - if (!option_mirror && !option_single_branch)
569 + if (!option_mirror && !option_single_branch && !option_no_tags)
570 get_fetch_map(refs, tag_refspec, &tail, 0);
571
572 return local_refs;
@@ -652,7 +655,7 @@ static void update_remote_refs(const struct ref *refs,
655
656 if (refs) {
657 write_remote_refs(mapped_refs);
655 - if (option_single_branch)
658 + if (option_single_branch && !option_no_tags)
659 write_followtags(refs, msg);
660 }
661
@@ -1035,6 +1038,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1038 git_config_set(key.buf, repo);
1039 strbuf_reset(&key);
1040
1041 + if (option_no_tags) {
1042 + strbuf_addf(&key, "remote.%s.tagOpt", option_origin);
1043 + git_config_set(key.buf, "--no-tags");
1044 + strbuf_reset(&key);
1045 + }
1046 +
1047 if (option_required_reference.nr || option_optional_reference.nr)
1048 setup_reference();
1049
contrib/completion/git-completion.bash
+1
@@ -1309,6 +1309,7 @@ _git_clone ()
1309 --template=
1310 --depth
1311 --single-branch
1312 + --no-tags
1313 --branch
1314 --recurse-submodules
1315 --no-single-branch
t/t5612-clone-refspec.sh
+74 -2
@@ -17,13 +17,20 @@ test_expect_success 'setup' '
17 echo four >file &&
18 git commit -a -m four &&
19 git checkout master &&
20 + git tag five &&
21
22 # default clone
23 git clone . dir_all &&
24
25 + # default clone --no-tags
26 + git clone --no-tags . dir_all_no_tags &&
27 +
28 # default --single that follows HEAD=master
29 git clone --single-branch . dir_master &&
30
31 + # default --single that follows HEAD=master with no tags
32 + git clone --single-branch --no-tags . dir_master_no_tags &&
33 +
34 # default --single that follows HEAD=side
35 git checkout side &&
36 git clone --single-branch . dir_side &&
@@ -45,6 +52,9 @@ test_expect_success 'setup' '
52 # explicit --single with tag
53 git clone --single-branch --branch two . dir_tag &&
54
55 + # explicit --single with tag and --no-tags
56 + git clone --single-branch --no-tags --branch two . dir_tag_no_tags &&
57 +
58 # advance both "master" and "side" branches
59 git checkout side &&
60 echo five >file &&
@@ -77,7 +87,18 @@ test_expect_success 'by default no tags will be kept updated' '
87 git for-each-ref refs/tags >../actual
88 ) &&
89 git for-each-ref refs/tags >expect &&
80 - test_must_fail test_cmp expect actual
90 + test_must_fail test_cmp expect actual &&
91 + test_line_count = 2 actual
92 +'
93 +
94 +test_expect_success 'clone with --no-tags' '
95 + (
96 + cd dir_all_no_tags &&
97 + git fetch &&
98 + git for-each-ref refs/tags >../actual
99 + ) &&
100 + >expect &&
101 + test_cmp expect actual
102 '
103
104 test_expect_success '--single-branch while HEAD pointing at master' '
@@ -90,7 +111,47 @@ test_expect_success '--single-branch while HEAD pointing at master' '
111 ) &&
112 # only follow master
113 git for-each-ref refs/heads/master >expect &&
93 - test_cmp expect actual
114 + # get & check latest tags
115 + test_cmp expect actual &&
116 + (
117 + cd dir_master &&
118 + git fetch --tags &&
119 + git for-each-ref refs/tags >../actual
120 + ) &&
121 + git for-each-ref refs/tags >expect &&
122 + test_cmp expect actual &&
123 + test_line_count = 2 actual
124 +'
125 +
126 +test_expect_success '--single-branch while HEAD pointing at master and --no-tags' '
127 + (
128 + cd dir_master_no_tags &&
129 + git fetch &&
130 + git for-each-ref refs/remotes/origin |
131 + sed -e "/HEAD$/d" \
132 + -e "s|/remotes/origin/|/heads/|" >../actual
133 + ) &&
134 + # only follow master
135 + git for-each-ref refs/heads/master >expect &&
136 + test_cmp expect actual &&
137 + # get tags (noop)
138 + (
139 + cd dir_master_no_tags &&
140 + git fetch &&
141 + git for-each-ref refs/tags >../actual
142 + ) &&
143 + >expect &&
144 + test_cmp expect actual &&
145 + test_line_count = 0 actual &&
146 + # get tags with --tags overrides tagOpt
147 + (
148 + cd dir_master_no_tags &&
149 + git fetch --tags &&
150 + git for-each-ref refs/tags >../actual
151 + ) &&
152 + git for-each-ref refs/tags >expect &&
153 + test_cmp expect actual &&
154 + test_line_count = 2 actual
155 '
156
157 test_expect_success '--single-branch while HEAD pointing at side' '
@@ -129,6 +190,17 @@ test_expect_success '--single-branch with explicit --branch with tag fetches upd
190 test_cmp expect actual
191 '
192
193 +test_expect_success '--single-branch with explicit --branch with tag fetches updated tag despite --no-tags' '
194 + (
195 + cd dir_tag_no_tags &&
196 + git fetch &&
197 + git for-each-ref refs/tags >../actual
198 + ) &&
199 + git for-each-ref refs/tags/two >expect &&
200 + test_cmp expect actual &&
201 + test_line_count = 1 actual
202 +'
203 +
204 test_expect_success '--single-branch with --mirror' '
205 (
206 cd dir_mirror &&