connect: request remote refs using v2

Teach the client to be able to request a remote's refs using protocol v2. This is done by having a client issue a 'ls-refs' request to a v2 server. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brandon Williams committed Mar 15, 2018 at 10:31 UTC e52449b672210f4b49e116ca34dcd46657287f61
6 files changed +204 -11
builtin/upload-pack.c
+5 -5
@@ -5,6 +5,7 @@
5 #include "parse-options.h"
6 #include "protocol.h"
7 #include "upload-pack.h"
8 +#include "serve.h"
9
10 static const char * const upload_pack_usage[] = {
11 N_("git upload-pack [<options>] <dir>"),
@@ -16,6 +17,7 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
17 const char *dir;
18 int strict = 0;
19 struct upload_pack_options opts = { 0 };
20 + struct serve_options serve_opts = SERVE_OPTIONS_INIT;
21 struct option options[] = {
22 OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc,
23 N_("quit after a single request/response exchange")),
@@ -48,11 +50,9 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
50
51 switch (determine_protocol_version_server()) {
52 case protocol_v2:
51 - /*
52 - * fetch support for protocol v2 has not been implemented yet,
53 - * so ignore the request to use v2 and fallback to using v0.
54 - */
55 - upload_pack(&opts);
53 + serve_opts.advertise_capabilities = opts.advertise_refs;
54 + serve_opts.stateless_rpc = opts.stateless_rpc;
55 + serve(&serve_opts);
56 break;
57 case protocol_v1:
58 /*
connect.c
+133 -5
@@ -12,9 +12,11 @@
12 #include "sha1-array.h"
13 #include "transport.h"
14 #include "strbuf.h"
15 +#include "version.h"
16 #include "protocol.h"
17
17 -static char *server_capabilities;
18 +static char *server_capabilities_v1;
19 +static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT;
20 static const char *parse_feature_value(const char *, const char *, int *);
21
22 static int check_ref(const char *name, unsigned int flags)
@@ -62,6 +64,33 @@ static void die_initial_contact(int unexpected)
64 "and the repository exists."));
65 }
66
67 +/* Checks if the server supports the capability 'c' */
68 +int server_supports_v2(const char *c, int die_on_error)
69 +{
70 + int i;
71 +
72 + for (i = 0; i < server_capabilities_v2.argc; i++) {
73 + const char *out;
74 + if (skip_prefix(server_capabilities_v2.argv[i], c, &out) &&
75 + (!*out || *out == '='))
76 + return 1;
77 + }
78 +
79 + if (die_on_error)
80 + die("server doesn't support '%s'", c);
81 +
82 + return 0;
83 +}
84 +
85 +static void process_capabilities_v2(struct packet_reader *reader)
86 +{
87 + while (packet_reader_read(reader) == PACKET_READ_NORMAL)
88 + argv_array_push(&server_capabilities_v2, reader->line);
89 +
90 + if (reader->status != PACKET_READ_FLUSH)
91 + die("expected flush after capabilities");
92 +}
93 +
94 enum protocol_version discover_version(struct packet_reader *reader)
95 {
96 enum protocol_version version = protocol_unknown_version;
@@ -84,7 +113,7 @@ enum protocol_version discover_version(struct packet_reader *reader)
113
114 switch (version) {
115 case protocol_v2:
87 - die("support for protocol v2 not implemented yet");
116 + process_capabilities_v2(reader);
117 break;
118 case protocol_v1:
119 /* Read the peeked version line */
@@ -128,7 +157,7 @@ reject:
157 static void annotate_refs_with_symref_info(struct ref *ref)
158 {
159 struct string_list symref = STRING_LIST_INIT_DUP;
131 - const char *feature_list = server_capabilities;
160 + const char *feature_list = server_capabilities_v1;
161
162 while (feature_list) {
163 int len;
@@ -157,7 +186,7 @@ static void process_capabilities(const char *line, int *len)
186 int nul_location = strlen(line);
187 if (nul_location == *len)
188 return;
160 - server_capabilities = xstrdup(line + nul_location + 1);
189 + server_capabilities_v1 = xstrdup(line + nul_location + 1);
190 *len = nul_location;
191 }
192
@@ -292,6 +321,105 @@ struct ref **get_remote_heads(struct packet_reader *reader,
321 return list;
322 }
323
324 +/* Returns 1 when a valid ref has been added to `list`, 0 otherwise */
325 +static int process_ref_v2(const char *line, struct ref ***list)
326 +{
327 + int ret = 1;
328 + int i = 0;
329 + struct object_id old_oid;
330 + struct ref *ref;
331 + struct string_list line_sections = STRING_LIST_INIT_DUP;
332 + const char *end;
333 +
334 + /*
335 + * Ref lines have a number of fields which are space deliminated. The
336 + * first field is the OID of the ref. The second field is the ref
337 + * name. Subsequent fields (symref-target and peeled) are optional and
338 + * don't have a particular order.
339 + */
340 + if (string_list_split(&line_sections, line, ' ', -1) < 2) {
341 + ret = 0;
342 + goto out;
343 + }
344 +
345 + if (parse_oid_hex(line_sections.items[i++].string, &old_oid, &end) ||
346 + *end) {
347 + ret = 0;
348 + goto out;
349 + }
350 +
351 + ref = alloc_ref(line_sections.items[i++].string);
352 +
353 + oidcpy(&ref->old_oid, &old_oid);
354 + **list = ref;
355 + *list = &ref->next;
356 +
357 + for (; i < line_sections.nr; i++) {
358 + const char *arg = line_sections.items[i].string;
359 + if (skip_prefix(arg, "symref-target:", &arg))
360 + ref->symref = xstrdup(arg);
361 +
362 + if (skip_prefix(arg, "peeled:", &arg)) {
363 + struct object_id peeled_oid;
364 + char *peeled_name;
365 + struct ref *peeled;
366 + if (parse_oid_hex(arg, &peeled_oid, &end) || *end) {
367 + ret = 0;
368 + goto out;
369 + }
370 +
371 + peeled_name = xstrfmt("%s^{}", ref->name);
372 + peeled = alloc_ref(peeled_name);
373 +
374 + oidcpy(&peeled->old_oid, &peeled_oid);
375 + **list = peeled;
376 + *list = &peeled->next;
377 +
378 + free(peeled_name);
379 + }
380 + }
381 +
382 +out:
383 + string_list_clear(&line_sections, 0);
384 + return ret;
385 +}
386 +
387 +struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
388 + struct ref **list, int for_push,
389 + const struct argv_array *ref_prefixes)
390 +{
391 + int i;
392 + *list = NULL;
393 +
394 + if (server_supports_v2("ls-refs", 1))
395 + packet_write_fmt(fd_out, "command=ls-refs\n");
396 +
397 + if (server_supports_v2("agent", 0))
398 + packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized());
399 +
400 + packet_delim(fd_out);
401 + /* When pushing we don't want to request the peeled tags */
402 + if (!for_push)
403 + packet_write_fmt(fd_out, "peel\n");
404 + packet_write_fmt(fd_out, "symrefs\n");
405 + for (i = 0; ref_prefixes && i < ref_prefixes->argc; i++) {
406 + packet_write_fmt(fd_out, "ref-prefix %s\n",
407 + ref_prefixes->argv[i]);
408 + }
409 + packet_flush(fd_out);
410 +
411 + /* Process response from server */
412 + while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
413 + if (!process_ref_v2(reader->line, &list))
414 + die("invalid ls-refs response: %s", reader->line);
415 + }
416 +
417 + if (reader->status != PACKET_READ_FLUSH)
418 + die("expected flush after ref listing");
419 +
420 + return list;
421 +}
422 +
423 static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
424 {
425 int len;
@@ -336,7 +464,7 @@ int parse_feature_request(const char *feature_list, const char *feature)
464
465 const char *server_feature_value(const char *feature, int *len)
466 {
339 - return parse_feature_value(server_capabilities, feature, len);
467 + return parse_feature_value(server_capabilities_v1, feature, len);
468 }
469
470 int server_supports(const char *feature)
connect.h
+2
@@ -16,4 +16,6 @@ extern int url_is_local_not_ssh(const char *url);
16 struct packet_reader;
17 extern enum protocol_version discover_version(struct packet_reader *reader);
18
19 +extern int server_supports_v2(const char *c, int die_on_error);
20 +
21 #endif
remote.h
+6
@@ -151,11 +151,17 @@ void free_refs(struct ref *ref);
151
152 struct oid_array;
153 struct packet_reader;
154 +struct argv_array;
155 extern struct ref **get_remote_heads(struct packet_reader *reader,
156 struct ref **list, unsigned int flags,
157 struct oid_array *extra_have,
158 struct oid_array *shallow_points);
159
160 +/* Used for protocol v2 in order to retrieve refs from a remote */
161 +extern struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
162 + struct ref **list, int for_push,
163 + const struct argv_array *ref_prefixes);
164 +
165 int resolve_remote_symref(struct ref *ref, struct ref *list);
166 int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
167
t/t5702-protocol-v2.sh new
+57
@@ -0,0 +1,57 @@
1 +#!/bin/sh
2 +
3 +test_description='test git wire-protocol version 2'
4 +
5 +TEST_NO_CREATE_REPO=1
6 +
7 +. ./test-lib.sh
8 +
9 +# Test protocol v2 with 'git://' transport
10 +#
11 +. "$TEST_DIRECTORY"/lib-git-daemon.sh
12 +start_git_daemon --export-all --enable=receive-pack
13 +daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent
14 +
15 +test_expect_success 'create repo to be served by git-daemon' '
16 + git init "$daemon_parent" &&
17 + test_commit -C "$daemon_parent" one
18 +'
19 +
20 +test_expect_success 'list refs with git:// using protocol v2' '
21 + test_when_finished "rm -f log" &&
22 +
23 + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
24 + ls-remote --symref "$GIT_DAEMON_URL/parent" >actual &&
25 +
26 + # Client requested to use protocol v2
27 + grep "git> .*\\\0\\\0version=2\\\0$" log &&
28 + # Server responded using protocol v2
29 + grep "git< version 2" log &&
30 +
31 + git ls-remote --symref "$GIT_DAEMON_URL/parent" >expect &&
32 + test_cmp actual expect
33 +'
34 +
35 +stop_git_daemon
36 +
37 +# Test protocol v2 with 'file://' transport
38 +#
39 +test_expect_success 'create repo to be served by file:// transport' '
40 + git init file_parent &&
41 + test_commit -C file_parent one
42 +'
43 +
44 +test_expect_success 'list refs with file:// using protocol v2' '
45 + test_when_finished "rm -f log" &&
46 +
47 + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
48 + ls-remote --symref "file://$(pwd)/file_parent" >actual &&
49 +
50 + # Server responded using protocol v2
51 + grep "git< version 2" log &&
52 +
53 + git ls-remote --symref "file://$(pwd)/file_parent" >expect &&
54 + test_cmp actual expect
55 +'
56 +
57 +test_done
transport.c
+1 -1
@@ -204,7 +204,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
204 data->version = discover_version(&reader);
205 switch (data->version) {
206 case protocol_v2:
207 - die("support for protocol v2 not implemented yet");
207 + get_remote_refs(data->fd[1], &reader, &refs, for_push, NULL);
208 break;
209 case protocol_v1:
210 case protocol_v0: