clone: respect additional configured fetch refspecs during initial fetch

The initial fetch during a clone doesn't transfer refs matching additional fetch refspecs given on the command line as configuration variables, e.g. '-c remote.origin.fetch=<refspec>'. This contradicts the documentation stating that configuration variables specified via 'git clone -c <key>=<value> ...' "take effect immediately after the repository is initialized, but before the remote history is fetched" and the given example specifically mentions "adding additional fetch refspecs to the origin remote". Furthermore, one-shot configuration variables specified via 'git -c <key>=<value> clone ...', though not written to the newly created repository's config file, live during the lifetime of the 'clone' command, including the initial fetch. All this implies that any fetch refspecs specified this way should already be taken into account during the initial fetch. The reason for this is that the initial fetch is not a fully fledged 'git fetch' but a bunch of direct calls into the fetch/transport machinery with clone's own refs-to-refspec matching logic, which bypasses parts of 'git fetch' processing configured fetch refspecs. This logic only considers a single default refspec, potentially influenced by options like '--single-branch' and '--mirror'. The configured refspecs are, however, already read and parsed properly when clone calls remote.c:remote_get(), but it never looks at the parsed refspecs in the resulting 'struct remote'. Modify clone to take the remote's configured fetch refspecs into account to retrieve all matching refs during the initial fetch. Note that we have to explicitly add the default fetch refspec to the remote's refspecs, because at that point the remote only includes the fetch refspecs specified on the command line. Add tests to check that refspecs given both via 'git clone -c ...' and 'git -c ... clone' retrieve all refs matching either the default or the additional refspecs, and that it works even when the user specifies an alternative remote name via '--origin=<name>'. Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com> Reviewed-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

SZEDER Gábor committed Nov 14, 2018 at 11:46 UTC 515be8338237c1f0f98ff826ba98f50d1dff5517
2 files changed +62 -10
builtin/clone.c
+15 -10
@@ -548,7 +548,7 @@ static struct ref *find_remote_branch(const struct ref *refs, const char *branch
548 }
549
550 static struct ref *wanted_peer_refs(const struct ref *refs,
551 - struct refspec_item *refspec)
551 + struct refspec *refspec)
552 {
553 struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
554 struct ref *local_refs = head;
@@ -569,13 +569,19 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
569 warning(_("Could not find remote branch %s to clone."),
570 option_branch);
571 else {
572 - get_fetch_map(remote_head, refspec, &tail, 0);
572 + int i;
573 + for (i = 0; i < refspec->nr; i++)
574 + get_fetch_map(remote_head, &refspec->items[i],
575 + &tail, 0);
576
577 /* if --branch=tag, pull the requested tag explicitly */
578 get_fetch_map(remote_head, tag_refspec, &tail, 0);
579 }
577 - } else
578 - get_fetch_map(refs, refspec, &tail, 0);
580 + } else {
581 + int i;
582 + for (i = 0; i < refspec->nr; i++)
583 + get_fetch_map(refs, &refspec->items[i], &tail, 0);
584 + }
585
586 if (!option_mirror && !option_single_branch && !option_no_tags)
587 get_fetch_map(refs, tag_refspec, &tail, 0);
@@ -898,7 +904,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
904 int err = 0, complete_refs_before_fetch = 1;
905 int submodule_progress;
906
901 - struct refspec rs = REFSPEC_INIT_FETCH;
907 struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
908
909 fetch_if_missing = 0;
@@ -1080,11 +1085,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1085 if (option_required_reference.nr || option_optional_reference.nr)
1086 setup_reference();
1087
1088 + remote = remote_get(option_origin);
1089 +
1090 strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix,
1091 branch_top.buf);
1085 - refspec_append(&rs, default_refspec.buf);
1092 + refspec_append(&remote->fetch, default_refspec.buf);
1093
1087 - remote = remote_get(option_origin);
1094 transport = transport_get(remote, remote->url[0]);
1095 transport_set_verbosity(transport, option_verbosity, option_progress);
1096 transport->family = family;
@@ -1139,7 +1145,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1145
1146
1147 argv_array_push(&ref_prefixes, "HEAD");
1142 - refspec_ref_prefixes(&rs, &ref_prefixes);
1148 + refspec_ref_prefixes(&remote->fetch, &ref_prefixes);
1149 if (option_branch)
1150 expand_ref_prefix(&ref_prefixes, option_branch);
1151 if (!option_no_tags)
@@ -1148,7 +1154,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1154 refs = transport_get_remote_refs(transport, &ref_prefixes);
1155
1156 if (refs) {
1151 - mapped_refs = wanted_peer_refs(refs, &rs.items[0]);
1157 + mapped_refs = wanted_peer_refs(refs, &remote->fetch);
1158 /*
1159 * transport_get_remote_refs() may return refs with null sha-1
1160 * in mapped_refs (see struct transport->get_refs_list
@@ -1242,7 +1248,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1248 strbuf_release(&default_refspec);
1249 junk_mode = JUNK_LEAVE_ALL;
1250
1245 - refspec_clear(&rs);
1251 argv_array_clear(&ref_prefixes);
1252 return err;
1253 }
t/t5611-clone-config.sh
+47
@@ -45,6 +45,53 @@ test_expect_success 'clone -c config is available during clone' '
45 test_cmp expect child/file
46 '
47
48 +test_expect_success 'clone -c remote.origin.fetch=<refspec> works' '
49 + rm -rf child &&
50 + git update-ref refs/grab/it refs/heads/master &&
51 + git update-ref refs/leave/out refs/heads/master &&
52 + git clone -c "remote.origin.fetch=+refs/grab/*:refs/grab/*" . child &&
53 + git -C child for-each-ref --format="%(refname)" >actual &&
54 +
55 + cat >expect <<-\EOF &&
56 + refs/grab/it
57 + refs/heads/master
58 + refs/remotes/origin/HEAD
59 + refs/remotes/origin/master
60 + EOF
61 + test_cmp expect actual
62 +'
63 +
64 +test_expect_success 'git -c remote.origin.fetch=<refspec> clone works' '
65 + rm -rf child &&
66 + git -c "remote.origin.fetch=+refs/grab/*:refs/grab/*" clone . child &&
67 + git -C child for-each-ref --format="%(refname)" >actual &&
68 +
69 + cat >expect <<-\EOF &&
70 + refs/grab/it
71 + refs/heads/master
72 + refs/remotes/origin/HEAD
73 + refs/remotes/origin/master
74 + EOF
75 + test_cmp expect actual
76 +'
77 +
78 +test_expect_success 'clone -c remote.<remote>.fetch=<refspec> --origin=<name>' '
79 + rm -rf child &&
80 + git clone --origin=upstream \
81 + -c "remote.upstream.fetch=+refs/grab/*:refs/grab/*" \
82 + -c "remote.origin.fetch=+refs/leave/*:refs/leave/*" \
83 + . child &&
84 + git -C child for-each-ref --format="%(refname)" >actual &&
85 +
86 + cat >expect <<-\EOF &&
87 + refs/grab/it
88 + refs/heads/master
89 + refs/remotes/upstream/HEAD
90 + refs/remotes/upstream/master
91 + EOF
92 + test_cmp expect actual
93 +'
94 +
95 # Tests for the hidden file attribute on windows
96 is_hidden () {
97 # Use the output of `attrib`, ignore the absolute path