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>
This commit is contained in:
Brandon Williams
2018-03-15 10:31:21 -07:00
committed by Junio C Hamano
parent 72d0ea0056
commit e52449b672
6 changed files with 204 additions and 11 deletions

138
connect.c
View File

@ -12,9 +12,11 @@
#include "sha1-array.h"
#include "transport.h"
#include "strbuf.h"
#include "version.h"
#include "protocol.h"
static char *server_capabilities;
static char *server_capabilities_v1;
static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT;
static const char *parse_feature_value(const char *, const char *, int *);
static int check_ref(const char *name, unsigned int flags)
@ -62,6 +64,33 @@ static void die_initial_contact(int unexpected)
"and the repository exists."));
}
/* Checks if the server supports the capability 'c' */
int server_supports_v2(const char *c, int die_on_error)
{
int i;
for (i = 0; i < server_capabilities_v2.argc; i++) {
const char *out;
if (skip_prefix(server_capabilities_v2.argv[i], c, &out) &&
(!*out || *out == '='))
return 1;
}
if (die_on_error)
die("server doesn't support '%s'", c);
return 0;
}
static void process_capabilities_v2(struct packet_reader *reader)
{
while (packet_reader_read(reader) == PACKET_READ_NORMAL)
argv_array_push(&server_capabilities_v2, reader->line);
if (reader->status != PACKET_READ_FLUSH)
die("expected flush after capabilities");
}
enum protocol_version discover_version(struct packet_reader *reader)
{
enum protocol_version version = protocol_unknown_version;
@ -84,7 +113,7 @@ enum protocol_version discover_version(struct packet_reader *reader)
switch (version) {
case protocol_v2:
die("support for protocol v2 not implemented yet");
process_capabilities_v2(reader);
break;
case protocol_v1:
/* Read the peeked version line */
@ -128,7 +157,7 @@ reject:
static void annotate_refs_with_symref_info(struct ref *ref)
{
struct string_list symref = STRING_LIST_INIT_DUP;
const char *feature_list = server_capabilities;
const char *feature_list = server_capabilities_v1;
while (feature_list) {
int len;
@ -157,7 +186,7 @@ static void process_capabilities(const char *line, int *len)
int nul_location = strlen(line);
if (nul_location == *len)
return;
server_capabilities = xstrdup(line + nul_location + 1);
server_capabilities_v1 = xstrdup(line + nul_location + 1);
*len = nul_location;
}
@ -292,6 +321,105 @@ struct ref **get_remote_heads(struct packet_reader *reader,
return list;
}
/* Returns 1 when a valid ref has been added to `list`, 0 otherwise */
static int process_ref_v2(const char *line, struct ref ***list)
{
int ret = 1;
int i = 0;
struct object_id old_oid;
struct ref *ref;
struct string_list line_sections = STRING_LIST_INIT_DUP;
const char *end;
/*
* Ref lines have a number of fields which are space deliminated. The
* first field is the OID of the ref. The second field is the ref
* name. Subsequent fields (symref-target and peeled) are optional and
* don't have a particular order.
*/
if (string_list_split(&line_sections, line, ' ', -1) < 2) {
ret = 0;
goto out;
}
if (parse_oid_hex(line_sections.items[i++].string, &old_oid, &end) ||
*end) {
ret = 0;
goto out;
}
ref = alloc_ref(line_sections.items[i++].string);
oidcpy(&ref->old_oid, &old_oid);
**list = ref;
*list = &ref->next;
for (; i < line_sections.nr; i++) {
const char *arg = line_sections.items[i].string;
if (skip_prefix(arg, "symref-target:", &arg))
ref->symref = xstrdup(arg);
if (skip_prefix(arg, "peeled:", &arg)) {
struct object_id peeled_oid;
char *peeled_name;
struct ref *peeled;
if (parse_oid_hex(arg, &peeled_oid, &end) || *end) {
ret = 0;
goto out;
}
peeled_name = xstrfmt("%s^{}", ref->name);
peeled = alloc_ref(peeled_name);
oidcpy(&peeled->old_oid, &peeled_oid);
**list = peeled;
*list = &peeled->next;
free(peeled_name);
}
}
out:
string_list_clear(&line_sections, 0);
return ret;
}
struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
struct ref **list, int for_push,
const struct argv_array *ref_prefixes)
{
int i;
*list = NULL;
if (server_supports_v2("ls-refs", 1))
packet_write_fmt(fd_out, "command=ls-refs\n");
if (server_supports_v2("agent", 0))
packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized());
packet_delim(fd_out);
/* When pushing we don't want to request the peeled tags */
if (!for_push)
packet_write_fmt(fd_out, "peel\n");
packet_write_fmt(fd_out, "symrefs\n");
for (i = 0; ref_prefixes && i < ref_prefixes->argc; i++) {
packet_write_fmt(fd_out, "ref-prefix %s\n",
ref_prefixes->argv[i]);
}
packet_flush(fd_out);
/* Process response from server */
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
if (!process_ref_v2(reader->line, &list))
die("invalid ls-refs response: %s", reader->line);
}
if (reader->status != PACKET_READ_FLUSH)
die("expected flush after ref listing");
return list;
}
static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
{
int len;
@ -336,7 +464,7 @@ int parse_feature_request(const char *feature_list, const char *feature)
const char *server_feature_value(const char *feature, int *len)
{
return parse_feature_value(server_capabilities, feature, len);
return parse_feature_value(server_capabilities_v1, feature, len);
}
int server_supports(const char *feature)