upload-pack: introduce fetch server command
Introduce the 'fetch' server command. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
5b872fff18
commit
3145ea957d
@ -199,3 +199,128 @@ The output of ls-refs is as follows:
|
|||||||
ref-attribute = (symref | peeled)
|
ref-attribute = (symref | peeled)
|
||||||
symref = "symref-target:" symref-target
|
symref = "symref-target:" symref-target
|
||||||
peeled = "peeled:" obj-id
|
peeled = "peeled:" obj-id
|
||||||
|
|
||||||
|
fetch
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
`fetch` is the command used to fetch a packfile in v2. It can be looked
|
||||||
|
at as a modified version of the v1 fetch where the ref-advertisement is
|
||||||
|
stripped out (since the `ls-refs` command fills that role) and the
|
||||||
|
message format is tweaked to eliminate redundancies and permit easy
|
||||||
|
addition of future extensions.
|
||||||
|
|
||||||
|
Additional features not supported in the base command will be advertised
|
||||||
|
as the value of the command in the capability advertisement in the form
|
||||||
|
of a space separated list of features: "<command>=<feature 1> <feature 2>"
|
||||||
|
|
||||||
|
A `fetch` request can take the following arguments:
|
||||||
|
|
||||||
|
want <oid>
|
||||||
|
Indicates to the server an object which the client wants to
|
||||||
|
retrieve. Wants can be anything and are not limited to
|
||||||
|
advertised objects.
|
||||||
|
|
||||||
|
have <oid>
|
||||||
|
Indicates to the server an object which the client has locally.
|
||||||
|
This allows the server to make a packfile which only contains
|
||||||
|
the objects that the client needs. Multiple 'have' lines can be
|
||||||
|
supplied.
|
||||||
|
|
||||||
|
done
|
||||||
|
Indicates to the server that negotiation should terminate (or
|
||||||
|
not even begin if performing a clone) and that the server should
|
||||||
|
use the information supplied in the request to construct the
|
||||||
|
packfile.
|
||||||
|
|
||||||
|
thin-pack
|
||||||
|
Request that a thin pack be sent, which is a pack with deltas
|
||||||
|
which reference base objects not contained within the pack (but
|
||||||
|
are known to exist at the receiving end). This can reduce the
|
||||||
|
network traffic significantly, but it requires the receiving end
|
||||||
|
to know how to "thicken" these packs by adding the missing bases
|
||||||
|
to the pack.
|
||||||
|
|
||||||
|
no-progress
|
||||||
|
Request that progress information that would normally be sent on
|
||||||
|
side-band channel 2, during the packfile transfer, should not be
|
||||||
|
sent. However, the side-band channel 3 is still used for error
|
||||||
|
responses.
|
||||||
|
|
||||||
|
include-tag
|
||||||
|
Request that annotated tags should be sent if the objects they
|
||||||
|
point to are being sent.
|
||||||
|
|
||||||
|
ofs-delta
|
||||||
|
Indicate that the client understands PACKv2 with delta referring
|
||||||
|
to its base by position in pack rather than by an oid. That is,
|
||||||
|
they can read OBJ_OFS_DELTA (ake type 6) in a packfile.
|
||||||
|
|
||||||
|
The response of `fetch` is broken into a number of sections separated by
|
||||||
|
delimiter packets (0001), with each section beginning with its section
|
||||||
|
header.
|
||||||
|
|
||||||
|
output = *section
|
||||||
|
section = (acknowledgments | packfile)
|
||||||
|
(flush-pkt | delim-pkt)
|
||||||
|
|
||||||
|
acknowledgments = PKT-LINE("acknowledgments" LF)
|
||||||
|
(nak | *ack)
|
||||||
|
(ready)
|
||||||
|
ready = PKT-LINE("ready" LF)
|
||||||
|
nak = PKT-LINE("NAK" LF)
|
||||||
|
ack = PKT-LINE("ACK" SP obj-id LF)
|
||||||
|
|
||||||
|
packfile = PKT-LINE("packfile" LF)
|
||||||
|
*PKT-LINE(%x01-03 *%x00-ff)
|
||||||
|
|
||||||
|
acknowledgments section
|
||||||
|
* If the client determines that it is finished with negotiations
|
||||||
|
by sending a "done" line, the acknowledgments sections MUST be
|
||||||
|
omitted from the server's response.
|
||||||
|
|
||||||
|
* Always begins with the section header "acknowledgments"
|
||||||
|
|
||||||
|
* The server will respond with "NAK" if none of the object ids sent
|
||||||
|
as have lines were common.
|
||||||
|
|
||||||
|
* The server will respond with "ACK obj-id" for all of the
|
||||||
|
object ids sent as have lines which are common.
|
||||||
|
|
||||||
|
* A response cannot have both "ACK" lines as well as a "NAK"
|
||||||
|
line.
|
||||||
|
|
||||||
|
* The server will respond with a "ready" line indicating that
|
||||||
|
the server has found an acceptable common base and is ready to
|
||||||
|
make and send a packfile (which will be found in the packfile
|
||||||
|
section of the same response)
|
||||||
|
|
||||||
|
* If the server has found a suitable cut point and has decided
|
||||||
|
to send a "ready" line, then the server can decide to (as an
|
||||||
|
optimization) omit any "ACK" lines it would have sent during
|
||||||
|
its response. This is because the server will have already
|
||||||
|
determined the objects it plans to send to the client and no
|
||||||
|
further negotiation is needed.
|
||||||
|
|
||||||
|
packfile section
|
||||||
|
* This section is only included if the client has sent 'want'
|
||||||
|
lines in its request and either requested that no more
|
||||||
|
negotiation be done by sending 'done' or if the server has
|
||||||
|
decided it has found a sufficient cut point to produce a
|
||||||
|
packfile.
|
||||||
|
|
||||||
|
* Always begins with the section header "packfile"
|
||||||
|
|
||||||
|
* The transmission of the packfile begins immediately after the
|
||||||
|
section header
|
||||||
|
|
||||||
|
* The data transfer of the packfile is always multiplexed, using
|
||||||
|
the same semantics of the 'side-band-64k' capability from
|
||||||
|
protocol version 1. This means that each packet, during the
|
||||||
|
packfile data stream, is made up of a leading 4-byte pkt-line
|
||||||
|
length (typical of the pkt-line format), followed by a 1-byte
|
||||||
|
stream code, followed by the actual data.
|
||||||
|
|
||||||
|
The stream code can be one of:
|
||||||
|
1 - pack data
|
||||||
|
2 - progress messages
|
||||||
|
3 - fatal error message just before stream aborts
|
||||||
|
2
serve.c
2
serve.c
@ -6,6 +6,7 @@
|
|||||||
#include "argv-array.h"
|
#include "argv-array.h"
|
||||||
#include "ls-refs.h"
|
#include "ls-refs.h"
|
||||||
#include "serve.h"
|
#include "serve.h"
|
||||||
|
#include "upload-pack.h"
|
||||||
|
|
||||||
static int always_advertise(struct repository *r,
|
static int always_advertise(struct repository *r,
|
||||||
struct strbuf *value)
|
struct strbuf *value)
|
||||||
@ -54,6 +55,7 @@ struct protocol_capability {
|
|||||||
static struct protocol_capability capabilities[] = {
|
static struct protocol_capability capabilities[] = {
|
||||||
{ "agent", agent_advertise, NULL },
|
{ "agent", agent_advertise, NULL },
|
||||||
{ "ls-refs", always_advertise, ls_refs },
|
{ "ls-refs", always_advertise, ls_refs },
|
||||||
|
{ "fetch", always_advertise, upload_pack_v2 },
|
||||||
};
|
};
|
||||||
|
|
||||||
static void advertise_capabilities(void)
|
static void advertise_capabilities(void)
|
||||||
|
@ -9,6 +9,7 @@ test_expect_success 'test capability advertisement' '
|
|||||||
version 2
|
version 2
|
||||||
agent=git/$(git version | cut -d" " -f3)
|
agent=git/$(git version | cut -d" " -f3)
|
||||||
ls-refs
|
ls-refs
|
||||||
|
fetch
|
||||||
0000
|
0000
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
266
upload-pack.c
266
upload-pack.c
@ -18,6 +18,7 @@
|
|||||||
#include "prio-queue.h"
|
#include "prio-queue.h"
|
||||||
#include "protocol.h"
|
#include "protocol.h"
|
||||||
#include "upload-pack.h"
|
#include "upload-pack.h"
|
||||||
|
#include "serve.h"
|
||||||
|
|
||||||
/* Remember to update object flag allocation in object.h */
|
/* Remember to update object flag allocation in object.h */
|
||||||
#define THEY_HAVE (1u << 11)
|
#define THEY_HAVE (1u << 11)
|
||||||
@ -1065,3 +1066,268 @@ void upload_pack(struct upload_pack_options *options)
|
|||||||
create_pack_file();
|
create_pack_file();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct upload_pack_data {
|
||||||
|
struct object_array wants;
|
||||||
|
struct oid_array haves;
|
||||||
|
|
||||||
|
unsigned stateless_rpc : 1;
|
||||||
|
|
||||||
|
unsigned use_thin_pack : 1;
|
||||||
|
unsigned use_ofs_delta : 1;
|
||||||
|
unsigned no_progress : 1;
|
||||||
|
unsigned use_include_tag : 1;
|
||||||
|
unsigned done : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void upload_pack_data_init(struct upload_pack_data *data)
|
||||||
|
{
|
||||||
|
struct object_array wants = OBJECT_ARRAY_INIT;
|
||||||
|
struct oid_array haves = OID_ARRAY_INIT;
|
||||||
|
|
||||||
|
memset(data, 0, sizeof(*data));
|
||||||
|
data->wants = wants;
|
||||||
|
data->haves = haves;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void upload_pack_data_clear(struct upload_pack_data *data)
|
||||||
|
{
|
||||||
|
object_array_clear(&data->wants);
|
||||||
|
oid_array_clear(&data->haves);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_want(const char *line)
|
||||||
|
{
|
||||||
|
const char *arg;
|
||||||
|
if (skip_prefix(line, "want ", &arg)) {
|
||||||
|
struct object_id oid;
|
||||||
|
struct object *o;
|
||||||
|
|
||||||
|
if (get_oid_hex(arg, &oid))
|
||||||
|
die("git upload-pack: protocol error, "
|
||||||
|
"expected to get oid, not '%s'", line);
|
||||||
|
|
||||||
|
o = parse_object(&oid);
|
||||||
|
if (!o) {
|
||||||
|
packet_write_fmt(1,
|
||||||
|
"ERR upload-pack: not our ref %s",
|
||||||
|
oid_to_hex(&oid));
|
||||||
|
die("git upload-pack: not our ref %s",
|
||||||
|
oid_to_hex(&oid));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(o->flags & WANTED)) {
|
||||||
|
o->flags |= WANTED;
|
||||||
|
add_object_array(o, NULL, &want_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_have(const char *line, struct oid_array *haves)
|
||||||
|
{
|
||||||
|
const char *arg;
|
||||||
|
if (skip_prefix(line, "have ", &arg)) {
|
||||||
|
struct object_id oid;
|
||||||
|
|
||||||
|
if (get_oid_hex(arg, &oid))
|
||||||
|
die("git upload-pack: expected SHA1 object, got '%s'", arg);
|
||||||
|
oid_array_append(haves, &oid);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_args(struct packet_reader *request,
|
||||||
|
struct upload_pack_data *data)
|
||||||
|
{
|
||||||
|
while (packet_reader_read(request) != PACKET_READ_FLUSH) {
|
||||||
|
const char *arg = request->line;
|
||||||
|
|
||||||
|
/* process want */
|
||||||
|
if (parse_want(arg))
|
||||||
|
continue;
|
||||||
|
/* process have line */
|
||||||
|
if (parse_have(arg, &data->haves))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* process args like thin-pack */
|
||||||
|
if (!strcmp(arg, "thin-pack")) {
|
||||||
|
use_thin_pack = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strcmp(arg, "ofs-delta")) {
|
||||||
|
use_ofs_delta = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strcmp(arg, "no-progress")) {
|
||||||
|
no_progress = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strcmp(arg, "include-tag")) {
|
||||||
|
use_include_tag = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strcmp(arg, "done")) {
|
||||||
|
data->done = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ignore unknown lines maybe? */
|
||||||
|
die("unexpect line: '%s'", arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_haves(struct oid_array *haves, struct oid_array *common)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Process haves */
|
||||||
|
for (i = 0; i < haves->nr; i++) {
|
||||||
|
const struct object_id *oid = &haves->oid[i];
|
||||||
|
struct object *o;
|
||||||
|
int we_knew_they_have = 0;
|
||||||
|
|
||||||
|
if (!has_object_file(oid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
oid_array_append(common, oid);
|
||||||
|
|
||||||
|
o = parse_object(oid);
|
||||||
|
if (!o)
|
||||||
|
die("oops (%s)", oid_to_hex(oid));
|
||||||
|
if (o->type == OBJ_COMMIT) {
|
||||||
|
struct commit_list *parents;
|
||||||
|
struct commit *commit = (struct commit *)o;
|
||||||
|
if (o->flags & THEY_HAVE)
|
||||||
|
we_knew_they_have = 1;
|
||||||
|
else
|
||||||
|
o->flags |= THEY_HAVE;
|
||||||
|
if (!oldest_have || (commit->date < oldest_have))
|
||||||
|
oldest_have = commit->date;
|
||||||
|
for (parents = commit->parents;
|
||||||
|
parents;
|
||||||
|
parents = parents->next)
|
||||||
|
parents->item->object.flags |= THEY_HAVE;
|
||||||
|
}
|
||||||
|
if (!we_knew_they_have)
|
||||||
|
add_object_array(o, NULL, &have_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_acks(struct oid_array *acks, struct strbuf *response)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
packet_buf_write(response, "acknowledgments\n");
|
||||||
|
|
||||||
|
/* Send Acks */
|
||||||
|
if (!acks->nr)
|
||||||
|
packet_buf_write(response, "NAK\n");
|
||||||
|
|
||||||
|
for (i = 0; i < acks->nr; i++) {
|
||||||
|
packet_buf_write(response, "ACK %s\n",
|
||||||
|
oid_to_hex(&acks->oid[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok_to_give_up()) {
|
||||||
|
/* Send Ready */
|
||||||
|
packet_buf_write(response, "ready\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_haves_and_send_acks(struct upload_pack_data *data)
|
||||||
|
{
|
||||||
|
struct oid_array common = OID_ARRAY_INIT;
|
||||||
|
struct strbuf response = STRBUF_INIT;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
process_haves(&data->haves, &common);
|
||||||
|
if (data->done) {
|
||||||
|
ret = 1;
|
||||||
|
} else if (send_acks(&common, &response)) {
|
||||||
|
packet_buf_delim(&response);
|
||||||
|
ret = 1;
|
||||||
|
} else {
|
||||||
|
/* Add Flush */
|
||||||
|
packet_buf_flush(&response);
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send response */
|
||||||
|
write_or_die(1, response.buf, response.len);
|
||||||
|
strbuf_release(&response);
|
||||||
|
|
||||||
|
oid_array_clear(&data->haves);
|
||||||
|
oid_array_clear(&common);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum fetch_state {
|
||||||
|
FETCH_PROCESS_ARGS = 0,
|
||||||
|
FETCH_SEND_ACKS,
|
||||||
|
FETCH_SEND_PACK,
|
||||||
|
FETCH_DONE,
|
||||||
|
};
|
||||||
|
|
||||||
|
int upload_pack_v2(struct repository *r, struct argv_array *keys,
|
||||||
|
struct packet_reader *request)
|
||||||
|
{
|
||||||
|
enum fetch_state state = FETCH_PROCESS_ARGS;
|
||||||
|
struct upload_pack_data data;
|
||||||
|
|
||||||
|
upload_pack_data_init(&data);
|
||||||
|
use_sideband = LARGE_PACKET_MAX;
|
||||||
|
|
||||||
|
while (state != FETCH_DONE) {
|
||||||
|
switch (state) {
|
||||||
|
case FETCH_PROCESS_ARGS:
|
||||||
|
process_args(request, &data);
|
||||||
|
|
||||||
|
if (!want_obj.nr) {
|
||||||
|
/*
|
||||||
|
* Request didn't contain any 'want' lines,
|
||||||
|
* guess they didn't want anything.
|
||||||
|
*/
|
||||||
|
state = FETCH_DONE;
|
||||||
|
} else if (data.haves.nr) {
|
||||||
|
/*
|
||||||
|
* Request had 'have' lines, so lets ACK them.
|
||||||
|
*/
|
||||||
|
state = FETCH_SEND_ACKS;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Request had 'want's but no 'have's so we can
|
||||||
|
* immedietly go to construct and send a pack.
|
||||||
|
*/
|
||||||
|
state = FETCH_SEND_PACK;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FETCH_SEND_ACKS:
|
||||||
|
if (process_haves_and_send_acks(&data))
|
||||||
|
state = FETCH_SEND_PACK;
|
||||||
|
else
|
||||||
|
state = FETCH_DONE;
|
||||||
|
break;
|
||||||
|
case FETCH_SEND_PACK:
|
||||||
|
packet_write_fmt(1, "packfile\n");
|
||||||
|
create_pack_file();
|
||||||
|
state = FETCH_DONE;
|
||||||
|
break;
|
||||||
|
case FETCH_DONE:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upload_pack_data_clear(&data);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@ -10,4 +10,10 @@ struct upload_pack_options {
|
|||||||
|
|
||||||
void upload_pack(struct upload_pack_options *options);
|
void upload_pack(struct upload_pack_options *options);
|
||||||
|
|
||||||
|
struct repository;
|
||||||
|
struct argv_array;
|
||||||
|
struct packet_reader;
|
||||||
|
extern int upload_pack_v2(struct repository *r, struct argv_array *keys,
|
||||||
|
struct packet_reader *request);
|
||||||
|
|
||||||
#endif /* UPLOAD_PACK_H */
|
#endif /* UPLOAD_PACK_H */
|
||||||
|
Loading…
Reference in New Issue
Block a user