Merge branch 'bw/protocol-v2'
The beginning of the next-gen transfer protocol. * bw/protocol-v2: (35 commits) remote-curl: don't request v2 when pushing remote-curl: implement stateless-connect command http: eliminate "# service" line when using protocol v2 http: don't always add Git-Protocol header http: allow providing extra headers for http requests remote-curl: store the protocol version the server responded with remote-curl: create copy of the service name pkt-line: add packet_buf_write_len function transport-helper: introduce stateless-connect transport-helper: refactor process_connect_service transport-helper: remove name parameter connect: don't request v2 when pushing connect: refactor git_connect to only get the protocol version once fetch-pack: support shallow requests fetch-pack: perform a fetch using v2 upload-pack: introduce fetch server command push: pass ref prefixes when pushing fetch: pass ref prefixes when fetching ls-remote: pass ref prefixes when requesting a remote's refs transport: convert transport_get_remote_refs to take a list of ref prefixes ...
This commit is contained in:
339
fetch-pack.c
339
fetch-pack.c
@ -305,9 +305,9 @@ static void insert_one_alternate_object(struct object *obj)
|
||||
#define PIPESAFE_FLUSH 32
|
||||
#define LARGE_FLUSH 16384
|
||||
|
||||
static int next_flush(struct fetch_pack_args *args, int count)
|
||||
static int next_flush(int stateless_rpc, int count)
|
||||
{
|
||||
if (args->stateless_rpc) {
|
||||
if (stateless_rpc) {
|
||||
if (count < LARGE_FLUSH)
|
||||
count <<= 1;
|
||||
else
|
||||
@ -470,7 +470,7 @@ static int find_common(struct fetch_pack_args *args,
|
||||
send_request(args, fd[1], &req_buf);
|
||||
strbuf_setlen(&req_buf, state_len);
|
||||
flushes++;
|
||||
flush_at = next_flush(args, count);
|
||||
flush_at = next_flush(args->stateless_rpc, count);
|
||||
|
||||
/*
|
||||
* We keep one window "ahead" of the other side, and
|
||||
@ -1080,6 +1080,328 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
|
||||
return ref;
|
||||
}
|
||||
|
||||
static void add_shallow_requests(struct strbuf *req_buf,
|
||||
const struct fetch_pack_args *args)
|
||||
{
|
||||
if (is_repository_shallow())
|
||||
write_shallow_commits(req_buf, 1, NULL);
|
||||
if (args->depth > 0)
|
||||
packet_buf_write(req_buf, "deepen %d", args->depth);
|
||||
if (args->deepen_since) {
|
||||
timestamp_t max_age = approxidate(args->deepen_since);
|
||||
packet_buf_write(req_buf, "deepen-since %"PRItime, max_age);
|
||||
}
|
||||
if (args->deepen_not) {
|
||||
int i;
|
||||
for (i = 0; i < args->deepen_not->nr; i++) {
|
||||
struct string_list_item *s = args->deepen_not->items + i;
|
||||
packet_buf_write(req_buf, "deepen-not %s", s->string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_wants(const struct ref *wants, struct strbuf *req_buf)
|
||||
{
|
||||
for ( ; wants ; wants = wants->next) {
|
||||
const struct object_id *remote = &wants->old_oid;
|
||||
const char *remote_hex;
|
||||
struct object *o;
|
||||
|
||||
/*
|
||||
* If that object is complete (i.e. it is an ancestor of a
|
||||
* local ref), we tell them we have it but do not have to
|
||||
* tell them about its ancestors, which they already know
|
||||
* about.
|
||||
*
|
||||
* We use lookup_object here because we are only
|
||||
* interested in the case we *know* the object is
|
||||
* reachable and we have already scanned it.
|
||||
*/
|
||||
if (((o = lookup_object(remote->hash)) != NULL) &&
|
||||
(o->flags & COMPLETE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
remote_hex = oid_to_hex(remote);
|
||||
packet_buf_write(req_buf, "want %s\n", remote_hex);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_common(struct strbuf *req_buf, struct oidset *common)
|
||||
{
|
||||
struct oidset_iter iter;
|
||||
const struct object_id *oid;
|
||||
oidset_iter_init(common, &iter);
|
||||
|
||||
while ((oid = oidset_iter_next(&iter))) {
|
||||
packet_buf_write(req_buf, "have %s\n", oid_to_hex(oid));
|
||||
}
|
||||
}
|
||||
|
||||
static int add_haves(struct strbuf *req_buf, int *haves_to_send, int *in_vain)
|
||||
{
|
||||
int ret = 0;
|
||||
int haves_added = 0;
|
||||
const struct object_id *oid;
|
||||
|
||||
while ((oid = get_rev())) {
|
||||
packet_buf_write(req_buf, "have %s\n", oid_to_hex(oid));
|
||||
if (++haves_added >= *haves_to_send)
|
||||
break;
|
||||
}
|
||||
|
||||
*in_vain += haves_added;
|
||||
if (!haves_added || *in_vain >= MAX_IN_VAIN) {
|
||||
/* Send Done */
|
||||
packet_buf_write(req_buf, "done\n");
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
/* Increase haves to send on next round */
|
||||
*haves_to_send = next_flush(1, *haves_to_send);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int send_fetch_request(int fd_out, const struct fetch_pack_args *args,
|
||||
const struct ref *wants, struct oidset *common,
|
||||
int *haves_to_send, int *in_vain)
|
||||
{
|
||||
int ret = 0;
|
||||
struct strbuf req_buf = STRBUF_INIT;
|
||||
|
||||
if (server_supports_v2("fetch", 1))
|
||||
packet_buf_write(&req_buf, "command=fetch");
|
||||
if (server_supports_v2("agent", 0))
|
||||
packet_buf_write(&req_buf, "agent=%s", git_user_agent_sanitized());
|
||||
|
||||
packet_buf_delim(&req_buf);
|
||||
if (args->use_thin_pack)
|
||||
packet_buf_write(&req_buf, "thin-pack");
|
||||
if (args->no_progress)
|
||||
packet_buf_write(&req_buf, "no-progress");
|
||||
if (args->include_tag)
|
||||
packet_buf_write(&req_buf, "include-tag");
|
||||
if (prefer_ofs_delta)
|
||||
packet_buf_write(&req_buf, "ofs-delta");
|
||||
|
||||
/* Add shallow-info and deepen request */
|
||||
if (server_supports_feature("fetch", "shallow", 0))
|
||||
add_shallow_requests(&req_buf, args);
|
||||
else if (is_repository_shallow() || args->deepen)
|
||||
die(_("Server does not support shallow requests"));
|
||||
|
||||
/* add wants */
|
||||
add_wants(wants, &req_buf);
|
||||
|
||||
/* Add all of the common commits we've found in previous rounds */
|
||||
add_common(&req_buf, common);
|
||||
|
||||
/* Add initial haves */
|
||||
ret = add_haves(&req_buf, haves_to_send, in_vain);
|
||||
|
||||
/* Send request */
|
||||
packet_buf_flush(&req_buf);
|
||||
write_or_die(fd_out, req_buf.buf, req_buf.len);
|
||||
|
||||
strbuf_release(&req_buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Processes a section header in a server's response and checks if it matches
|
||||
* `section`. If the value of `peek` is 1, the header line will be peeked (and
|
||||
* not consumed); if 0, the line will be consumed and the function will die if
|
||||
* the section header doesn't match what was expected.
|
||||
*/
|
||||
static int process_section_header(struct packet_reader *reader,
|
||||
const char *section, int peek)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (packet_reader_peek(reader) != PACKET_READ_NORMAL)
|
||||
die("error reading section header '%s'", section);
|
||||
|
||||
ret = !strcmp(reader->line, section);
|
||||
|
||||
if (!peek) {
|
||||
if (!ret)
|
||||
die("expected '%s', received '%s'",
|
||||
section, reader->line);
|
||||
packet_reader_read(reader);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int process_acks(struct packet_reader *reader, struct oidset *common)
|
||||
{
|
||||
/* received */
|
||||
int received_ready = 0;
|
||||
int received_ack = 0;
|
||||
|
||||
process_section_header(reader, "acknowledgments", 0);
|
||||
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
|
||||
const char *arg;
|
||||
|
||||
if (!strcmp(reader->line, "NAK"))
|
||||
continue;
|
||||
|
||||
if (skip_prefix(reader->line, "ACK ", &arg)) {
|
||||
struct object_id oid;
|
||||
if (!get_oid_hex(arg, &oid)) {
|
||||
struct commit *commit;
|
||||
oidset_insert(common, &oid);
|
||||
commit = lookup_commit(&oid);
|
||||
mark_common(commit, 0, 1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(reader->line, "ready")) {
|
||||
clear_prio_queue(&rev_list);
|
||||
received_ready = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
die("unexpected acknowledgment line: '%s'", reader->line);
|
||||
}
|
||||
|
||||
if (reader->status != PACKET_READ_FLUSH &&
|
||||
reader->status != PACKET_READ_DELIM)
|
||||
die("error processing acks: %d", reader->status);
|
||||
|
||||
/* return 0 if no common, 1 if there are common, or 2 if ready */
|
||||
return received_ready ? 2 : (received_ack ? 1 : 0);
|
||||
}
|
||||
|
||||
static void receive_shallow_info(struct fetch_pack_args *args,
|
||||
struct packet_reader *reader)
|
||||
{
|
||||
process_section_header(reader, "shallow-info", 0);
|
||||
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
|
||||
const char *arg;
|
||||
struct object_id oid;
|
||||
|
||||
if (skip_prefix(reader->line, "shallow ", &arg)) {
|
||||
if (get_oid_hex(arg, &oid))
|
||||
die(_("invalid shallow line: %s"), reader->line);
|
||||
register_shallow(&oid);
|
||||
continue;
|
||||
}
|
||||
if (skip_prefix(reader->line, "unshallow ", &arg)) {
|
||||
if (get_oid_hex(arg, &oid))
|
||||
die(_("invalid unshallow line: %s"), reader->line);
|
||||
if (!lookup_object(oid.hash))
|
||||
die(_("object not found: %s"), reader->line);
|
||||
/* make sure that it is parsed as shallow */
|
||||
if (!parse_object(&oid))
|
||||
die(_("error in object: %s"), reader->line);
|
||||
if (unregister_shallow(&oid))
|
||||
die(_("no shallow found: %s"), reader->line);
|
||||
continue;
|
||||
}
|
||||
die(_("expected shallow/unshallow, got %s"), reader->line);
|
||||
}
|
||||
|
||||
if (reader->status != PACKET_READ_FLUSH &&
|
||||
reader->status != PACKET_READ_DELIM)
|
||||
die("error processing shallow info: %d", reader->status);
|
||||
|
||||
setup_alternate_shallow(&shallow_lock, &alternate_shallow_file, NULL);
|
||||
args->deepen = 1;
|
||||
}
|
||||
|
||||
enum fetch_state {
|
||||
FETCH_CHECK_LOCAL = 0,
|
||||
FETCH_SEND_REQUEST,
|
||||
FETCH_PROCESS_ACKS,
|
||||
FETCH_GET_PACK,
|
||||
FETCH_DONE,
|
||||
};
|
||||
|
||||
static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
|
||||
int fd[2],
|
||||
const struct ref *orig_ref,
|
||||
struct ref **sought, int nr_sought,
|
||||
char **pack_lockfile)
|
||||
{
|
||||
struct ref *ref = copy_ref_list(orig_ref);
|
||||
enum fetch_state state = FETCH_CHECK_LOCAL;
|
||||
struct oidset common = OIDSET_INIT;
|
||||
struct packet_reader reader;
|
||||
int in_vain = 0;
|
||||
int haves_to_send = INITIAL_FLUSH;
|
||||
packet_reader_init(&reader, fd[0], NULL, 0,
|
||||
PACKET_READ_CHOMP_NEWLINE);
|
||||
|
||||
while (state != FETCH_DONE) {
|
||||
switch (state) {
|
||||
case FETCH_CHECK_LOCAL:
|
||||
sort_ref_list(&ref, ref_compare_name);
|
||||
QSORT(sought, nr_sought, cmp_ref_by_name);
|
||||
|
||||
/* v2 supports these by default */
|
||||
allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
|
||||
use_sideband = 2;
|
||||
if (args->depth > 0 || args->deepen_since || args->deepen_not)
|
||||
args->deepen = 1;
|
||||
|
||||
if (marked)
|
||||
for_each_ref(clear_marks, NULL);
|
||||
marked = 1;
|
||||
|
||||
for_each_ref(rev_list_insert_ref_oid, NULL);
|
||||
for_each_cached_alternate(insert_one_alternate_object);
|
||||
|
||||
/* Filter 'ref' by 'sought' and those that aren't local */
|
||||
if (everything_local(args, &ref, sought, nr_sought))
|
||||
state = FETCH_DONE;
|
||||
else
|
||||
state = FETCH_SEND_REQUEST;
|
||||
break;
|
||||
case FETCH_SEND_REQUEST:
|
||||
if (send_fetch_request(fd[1], args, ref, &common,
|
||||
&haves_to_send, &in_vain))
|
||||
state = FETCH_GET_PACK;
|
||||
else
|
||||
state = FETCH_PROCESS_ACKS;
|
||||
break;
|
||||
case FETCH_PROCESS_ACKS:
|
||||
/* Process ACKs/NAKs */
|
||||
switch (process_acks(&reader, &common)) {
|
||||
case 2:
|
||||
state = FETCH_GET_PACK;
|
||||
break;
|
||||
case 1:
|
||||
in_vain = 0;
|
||||
/* fallthrough */
|
||||
default:
|
||||
state = FETCH_SEND_REQUEST;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case FETCH_GET_PACK:
|
||||
/* Check for shallow-info section */
|
||||
if (process_section_header(&reader, "shallow-info", 1))
|
||||
receive_shallow_info(args, &reader);
|
||||
|
||||
/* get the pack */
|
||||
process_section_header(&reader, "packfile", 0);
|
||||
if (get_pack(args, fd, pack_lockfile))
|
||||
die(_("git fetch-pack: fetch failed."));
|
||||
|
||||
state = FETCH_DONE;
|
||||
break;
|
||||
case FETCH_DONE:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
oidset_clear(&common);
|
||||
return ref;
|
||||
}
|
||||
|
||||
static void fetch_pack_config(void)
|
||||
{
|
||||
git_config_get_int("fetch.unpacklimit", &fetch_unpack_limit);
|
||||
@ -1225,7 +1547,8 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
|
||||
const char *dest,
|
||||
struct ref **sought, int nr_sought,
|
||||
struct oid_array *shallow,
|
||||
char **pack_lockfile)
|
||||
char **pack_lockfile,
|
||||
enum protocol_version version)
|
||||
{
|
||||
struct ref *ref_cpy;
|
||||
struct shallow_info si;
|
||||
@ -1239,8 +1562,12 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
|
||||
die(_("no matching remote head"));
|
||||
}
|
||||
prepare_shallow_info(&si, shallow);
|
||||
ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
|
||||
&si, pack_lockfile);
|
||||
if (version == protocol_v2)
|
||||
ref_cpy = do_fetch_pack_v2(args, fd, ref, sought, nr_sought,
|
||||
pack_lockfile);
|
||||
else
|
||||
ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
|
||||
&si, pack_lockfile);
|
||||
reprepare_packed_git(the_repository);
|
||||
update_shallow(args, sought, nr_sought, &si);
|
||||
clear_shallow_info(&si);
|
||||
|
Reference in New Issue
Block a user