Skip to content

Commit 0527fba

Browse files
committed
Merge branch 'jt/avoid-ls-refs'
Over some transports, fetching objects with an exact commit object name can be done without first seeing the ref advertisements. The code has been optimized to exploit this. * jt/avoid-ls-refs: fetch: do not list refs if fetching only hashes transport: list refs before fetch if necessary transport: do not list refs if possible transport: allow skipping of ref listing
2 parents d4cd2dd + e70a303 commit 0527fba

File tree

7 files changed

+114
-13
lines changed

7 files changed

+114
-13
lines changed

builtin/fetch.c

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,7 @@ static int do_fetch(struct transport *transport,
11861186
int retcode = 0;
11871187
const struct ref *remote_refs;
11881188
struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
1189+
int must_list_refs = 1;
11891190

11901191
if (tags == TAGS_DEFAULT) {
11911192
if (transport->remote->fetch_tags == 2)
@@ -1201,17 +1202,36 @@ static int do_fetch(struct transport *transport,
12011202
goto cleanup;
12021203
}
12031204

1204-
if (rs->nr)
1205+
if (rs->nr) {
1206+
int i;
1207+
12051208
refspec_ref_prefixes(rs, &ref_prefixes);
1206-
else if (transport->remote && transport->remote->fetch.nr)
1209+
1210+
/*
1211+
* We can avoid listing refs if all of them are exact
1212+
* OIDs
1213+
*/
1214+
must_list_refs = 0;
1215+
for (i = 0; i < rs->nr; i++) {
1216+
if (!rs->items[i].exact_sha1) {
1217+
must_list_refs = 1;
1218+
break;
1219+
}
1220+
}
1221+
} else if (transport->remote && transport->remote->fetch.nr)
12071222
refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);
12081223

1209-
if (ref_prefixes.argc &&
1210-
(tags == TAGS_SET || (tags == TAGS_DEFAULT))) {
1211-
argv_array_push(&ref_prefixes, "refs/tags/");
1224+
if (tags == TAGS_SET || tags == TAGS_DEFAULT) {
1225+
must_list_refs = 1;
1226+
if (ref_prefixes.argc)
1227+
argv_array_push(&ref_prefixes, "refs/tags/");
12121228
}
12131229

1214-
remote_refs = transport_get_remote_refs(transport, &ref_prefixes);
1230+
if (must_list_refs)
1231+
remote_refs = transport_get_remote_refs(transport, &ref_prefixes);
1232+
else
1233+
remote_refs = NULL;
1234+
12151235
argv_array_clear(&ref_prefixes);
12161236

12171237
ref_map = get_ref_map(transport->remote, remote_refs, rs,

fetch-pack.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1626,7 +1626,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
16261626
parse_list_objects_filter(&args->filter_options, "blob:none");
16271627
}
16281628

1629-
if (!ref) {
1629+
if (version != protocol_v2 && !ref) {
16301630
packet_flush(fd[1]);
16311631
die(_("no matching remote head"));
16321632
}

t/t5551-http-fetch-smart.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,21 @@ test_expect_success 'using fetch command in remote-curl updates refs' '
381381
test_cmp expect actual
382382
'
383383

384+
test_expect_success 'fetch by SHA-1 without tag following' '
385+
SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" &&
386+
rm -rf "$SERVER" client &&
387+
388+
git init "$SERVER" &&
389+
test_commit -C "$SERVER" foo &&
390+
391+
git clone $HTTPD_URL/smart/server client &&
392+
393+
test_commit -C "$SERVER" bar &&
394+
git -C "$SERVER" rev-parse bar >bar_hash &&
395+
git -C client -c protocol.version=0 fetch \
396+
--no-tags origin $(cat bar_hash)
397+
'
398+
384399
test_expect_success 'GIT_REDACT_COOKIES redacts cookies' '
385400
rm -rf clone &&
386401
echo "Set-Cookie: Foo=1" >cookies &&

t/t5702-protocol-v2.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ test_expect_success 'fetch with git:// using protocol v2' '
7979
grep "fetch< version 2" log
8080
'
8181

82+
test_expect_success 'fetch by hash without tag following with protocol v2 does not list refs' '
83+
test_when_finished "rm -f log" &&
84+
85+
test_commit -C "$daemon_parent" two_a &&
86+
git -C "$daemon_parent" rev-parse two_a >two_a_hash &&
87+
88+
GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \
89+
fetch --no-tags origin $(cat two_a_hash) &&
90+
91+
grep "fetch< version 2" log &&
92+
! grep "fetch> command=ls-refs" log
93+
'
94+
8295
test_expect_success 'pull with git:// using protocol v2' '
8396
test_when_finished "rm -f log" &&
8497
@@ -286,6 +299,10 @@ test_expect_success 'dynamically fetch missing object' '
286299
grep "version 2" trace
287300
'
288301

302+
test_expect_success 'when dynamically fetching missing object, do not list refs' '
303+
! grep "git> command=ls-refs" trace
304+
'
305+
289306
test_expect_success 'partial fetch' '
290307
rm -rf client "$(pwd)/trace" &&
291308
git init client &&

transport-helper.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push,
11051105
}
11061106

11071107
static struct transport_vtable vtable = {
1108+
0,
11081109
set_helper_option,
11091110
get_refs_list,
11101111
fetch,

transport-internal.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ struct transport;
66
struct argv_array;
77

88
struct transport_vtable {
9+
/**
10+
* This transport supports the fetch() function being called
11+
* without get_refs_list() first being called.
12+
*/
13+
unsigned fetch_without_list : 1;
14+
915
/**
1016
* Returns 0 if successful, positive if the option is not
1117
* recognized or is inapplicable, and negative if the option

transport.c

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,18 @@ static int connect_setup(struct transport *transport, int for_push)
252252
return 0;
253253
}
254254

255-
static struct ref *get_refs_via_connect(struct transport *transport, int for_push,
256-
const struct argv_array *ref_prefixes)
255+
/*
256+
* Obtains the protocol version from the transport and writes it to
257+
* transport->data->version, first connecting if not already connected.
258+
*
259+
* If the protocol version is one that allows skipping the listing of remote
260+
* refs, and must_list_refs is 0, the listing of remote refs is skipped and
261+
* this function returns NULL. Otherwise, this function returns the list of
262+
* remote refs.
263+
*/
264+
static struct ref *handshake(struct transport *transport, int for_push,
265+
const struct argv_array *ref_prefixes,
266+
int must_list_refs)
257267
{
258268
struct git_transport_data *data = transport->data;
259269
struct ref *refs = NULL;
@@ -268,8 +278,10 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
268278
data->version = discover_version(&reader);
269279
switch (data->version) {
270280
case protocol_v2:
271-
get_remote_refs(data->fd[1], &reader, &refs, for_push,
272-
ref_prefixes, transport->server_options);
281+
if (must_list_refs)
282+
get_remote_refs(data->fd[1], &reader, &refs, for_push,
283+
ref_prefixes,
284+
transport->server_options);
273285
break;
274286
case protocol_v1:
275287
case protocol_v0:
@@ -283,9 +295,18 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
283295
}
284296
data->got_remote_heads = 1;
285297

298+
if (reader.line_peeked)
299+
BUG("buffer must be empty at the end of handshake()");
300+
286301
return refs;
287302
}
288303

304+
static struct ref *get_refs_via_connect(struct transport *transport, int for_push,
305+
const struct argv_array *ref_prefixes)
306+
{
307+
return handshake(transport, for_push, ref_prefixes, 1);
308+
}
309+
289310
static int fetch_refs_via_pack(struct transport *transport,
290311
int nr_heads, struct ref **to_fetch)
291312
{
@@ -320,8 +341,17 @@ static int fetch_refs_via_pack(struct transport *transport,
320341
args.server_options = transport->server_options;
321342
args.negotiation_tips = data->options.negotiation_tips;
322343

323-
if (!data->got_remote_heads)
324-
refs_tmp = get_refs_via_connect(transport, 0, NULL);
344+
if (!data->got_remote_heads) {
345+
int i;
346+
int must_list_refs = 0;
347+
for (i = 0; i < nr_heads; i++) {
348+
if (!to_fetch[i]->exact_oid) {
349+
must_list_refs = 1;
350+
break;
351+
}
352+
}
353+
refs_tmp = handshake(transport, 0, NULL, must_list_refs);
354+
}
325355

326356
switch (data->version) {
327357
case protocol_v2:
@@ -703,6 +733,7 @@ static int disconnect_git(struct transport *transport)
703733
}
704734

705735
static struct transport_vtable taken_over_vtable = {
736+
1,
706737
NULL,
707738
get_refs_via_connect,
708739
fetch_refs_via_pack,
@@ -852,6 +883,7 @@ void transport_check_allowed(const char *type)
852883
}
853884

854885
static struct transport_vtable bundle_vtable = {
886+
0,
855887
NULL,
856888
get_refs_from_bundle,
857889
fetch_refs_from_bundle,
@@ -861,6 +893,7 @@ static struct transport_vtable bundle_vtable = {
861893
};
862894

863895
static struct transport_vtable builtin_smart_vtable = {
896+
1,
864897
NULL,
865898
get_refs_via_connect,
866899
fetch_refs_via_pack,
@@ -1227,6 +1260,15 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)
12271260
struct ref **heads = NULL;
12281261
struct ref *rm;
12291262

1263+
if (!transport->vtable->fetch_without_list)
1264+
/*
1265+
* Some transports (e.g. the built-in bundle transport and the
1266+
* transport helper interface) do not work when fetching is
1267+
* done immediately after transport creation. List the remote
1268+
* refs anyway (if not already listed) as a workaround.
1269+
*/
1270+
transport_get_remote_refs(transport, NULL);
1271+
12301272
for (rm = refs; rm; rm = rm->next) {
12311273
nr_refs++;
12321274
if (rm->peer_ref &&

0 commit comments

Comments
 (0)