Skip to content

Commit

Permalink
transport: add client support for object-info
Browse files Browse the repository at this point in the history
Sometimes it is useful to get information about an object without having
to download it completely. The server logic has already been implemented
in “a2ba162cda (object-info: support for retrieving object info,
2021-04-20)”.

Add client functions to communicate with the server.

The client currently supports requesting a list of object ids with
feature 'size' from a v2 server. If a server does not
advertise the feature, then the client falls back
to making the request through 'fetch'.

Helped-by: Jonathan Tan <[email protected]>
Helped-by: Christian Couder <[email protected]>
Signed-off-by: Calvin Wan <[email protected]>
Signed-off-by: Eric Ju  <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
  • Loading branch information
calvin-wan-google authored and gitster committed Sep 27, 2024
1 parent 5e82160 commit 33426e4
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 8 deletions.
4 changes: 3 additions & 1 deletion fetch-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -1347,7 +1347,6 @@ static void write_command_and_capabilities(struct strbuf *req_buf,
packet_buf_delim(req_buf);
}


void send_object_info_request(int fd_out, struct object_info_args *args)
{
struct strbuf req_buf = STRBUF_INIT;
Expand Down Expand Up @@ -1706,6 +1705,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
if (args->depth > 0 || args->deepen_since || args->deepen_not)
args->deepen = 1;

if (args->object_info)
state = FETCH_SEND_REQUEST;

while (state != FETCH_DONE) {
switch (state) {
case FETCH_CHECK_LOCAL:
Expand Down
10 changes: 10 additions & 0 deletions fetch-pack.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct fetch_pack_args {
const struct string_list *deepen_not;
struct list_objects_filter_options filter_options;
const struct string_list *server_options;
struct object_info *object_info_data;

/*
* If not NULL, during packfile negotiation, fetch-pack will send "have"
Expand All @@ -42,6 +43,7 @@ struct fetch_pack_args {
unsigned reject_shallow_remote:1;
unsigned deepen:1;
unsigned refetch:1;
unsigned object_info:1;

/*
* Indicate that the remote of this request is a promisor remote. The
Expand All @@ -68,6 +70,12 @@ struct fetch_pack_args {
unsigned connectivity_checked:1;
};

struct object_info_args {
struct string_list *object_info_options;
const struct string_list *server_options;
struct oid_array *oids;
};

/*
* sought represents remote references that should be updated from.
* On return, the names that were found on the remote will have been
Expand Down Expand Up @@ -106,4 +114,6 @@ int report_unmatched_refs(struct ref **sought, int nr_sought);
*/
int fetch_pack_fsck_objects(void);

void send_object_info_request(int fd_out, struct object_info_args *args);

#endif
8 changes: 6 additions & 2 deletions transport-helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -709,14 +709,18 @@ static int fetch_refs(struct transport *transport,

/*
* If we reach here, then the server, the client, and/or the transport
* helper does not support protocol v2. --negotiate-only requires
* protocol v2.
* helper does not support protocol v2. --negotiate-only and cat-file remote-object-info
* require protocol v2.
*/
if (data->transport_options.acked_commits) {
warning(_("--negotiate-only requires protocol v2"));
return -1;
}

/* fail the command explicitly to avoid further commands input. */
if (transport->smart_options->object_info)
die(_("remote-object-info requires protocol v2"));

if (!data->get_refs_list_called)
get_refs_list_using_list(transport, 0);

Expand Down
116 changes: 111 additions & 5 deletions transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,77 @@ static struct ref *handshake(struct transport *transport, int for_push,
return refs;
}

static int fetch_object_info(struct transport *transport, struct object_info *object_info_data)
{
int size_index = -1;
struct git_transport_data *data = transport->data;
struct object_info_args args = { 0 };
struct packet_reader reader;

args.server_options = transport->server_options;
args.object_info_options = transport->smart_options->object_info_options;
args.oids = transport->smart_options->object_info_oids;

connect_setup(transport, 0);
packet_reader_init(&reader, data->fd[0], NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
PACKET_READ_GENTLE_ON_EOF |
PACKET_READ_DIE_ON_ERR_PACKET);
data->version = discover_version(&reader);

transport->hash_algo = reader.hash_algo;

switch (data->version) {
case protocol_v2:
if (!server_supports_v2("object-info"))
return -1;
if (unsorted_string_list_has_string(args.object_info_options, "size")
&& !server_supports_feature("object-info", "size", 0))
return -1;
send_object_info_request(data->fd[1], &args);
break;
case protocol_v1:
case protocol_v0:
die(_("wrong protocol version. expected v2"));
case protocol_unknown_version:
BUG("unknown protocol version");
}

for (size_t i = 0; i < args.object_info_options->nr; i++) {
if (packet_reader_read(&reader) != PACKET_READ_NORMAL) {
check_stateless_delimiter(transport->stateless_rpc, &reader, "stateless delimiter expected");
return -1;
}
if (unsorted_string_list_has_string(args.object_info_options, reader.line)) {
if (!strcmp(reader.line, "size")) {
size_index = i;
for (size_t j = 0; j < args.oids->nr; j++)
object_info_data[j].sizep = xcalloc(1, sizeof(long));
}
continue;
}
return -1;
}

for (size_t i = 0; packet_reader_read(&reader) == PACKET_READ_NORMAL && i < args.oids->nr; i++){
struct string_list object_info_values = STRING_LIST_INIT_DUP;

string_list_split(&object_info_values, reader.line, ' ', -1);
if (0 <= size_index) {
if (!strcmp(object_info_values.items[1 + size_index].string, ""))
die("object-info: not our ref %s",
object_info_values.items[0].string);

*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);
}

string_list_clear(&object_info_values, 0);
}
check_stateless_delimiter(transport->stateless_rpc, &reader, "stateless delimiter expected");

return 0;
}

static struct ref *get_refs_via_connect(struct transport *transport, int for_push,
struct transport_ls_refs_options *options)
{
Expand Down Expand Up @@ -415,6 +486,7 @@ static int fetch_refs_via_pack(struct transport *transport,
struct ref *refs = NULL;
struct fetch_pack_args args;
struct ref *refs_tmp = NULL;
struct ref *object_info_refs = NULL;

memset(&args, 0, sizeof(args));
args.uploadpack = data->options.uploadpack;
Expand All @@ -441,11 +513,36 @@ static int fetch_refs_via_pack(struct transport *transport,
args.server_options = transport->server_options;
args.negotiation_tips = data->options.negotiation_tips;
args.reject_shallow_remote = transport->smart_options->reject_shallow;
args.object_info = transport->smart_options->object_info;

if (transport->smart_options
&& transport->smart_options->object_info
&& transport->smart_options->object_info_oids->nr > 0) {
struct ref *ref_itr = object_info_refs = alloc_ref("");

if (!fetch_object_info(transport, data->options.object_info_data))
goto cleanup;

args.object_info_data = data->options.object_info_data;
args.quiet = 1;
args.no_progress = 1;
for (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {
ref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];
ref_itr->exact_oid = 1;
if (i == transport->smart_options->object_info_oids->nr - 1)
/* last element, no need to allocate to next */
ref_itr -> next = NULL;
else
ref_itr->next = alloc_ref("");

if (!data->finished_handshake) {
int i;
ref_itr = ref_itr->next;
}

transport->remote_refs = object_info_refs;

} else if (!data->finished_handshake) {
int must_list_refs = 0;
for (i = 0; i < nr_heads; i++) {
for (int i = 0; i < nr_heads; i++) {
if (!to_fetch[i]->exact_oid) {
must_list_refs = 1;
break;
Expand Down Expand Up @@ -483,23 +580,32 @@ static int fetch_refs_via_pack(struct transport *transport,
&transport->pack_lockfiles, data->version);

data->finished_handshake = 0;
if (args.object_info) {
struct ref *ref_cpy_reader = object_info_refs;
for (int i = 0; ref_cpy_reader; i++) {
oid_object_info_extended(the_repository, &ref_cpy_reader->old_oid,
&args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);
ref_cpy_reader = ref_cpy_reader->next;
}
}

data->options.self_contained_and_connected =
args.self_contained_and_connected;
data->options.connectivity_checked = args.connectivity_checked;

if (!refs)
if (!refs && !args.object_info)
ret = -1;
if (report_unmatched_refs(to_fetch, nr_heads))
ret = -1;

cleanup:
free_refs(object_info_refs);
close(data->fd[0]);
if (data->fd[1] >= 0)
close(data->fd[1]);
if (finish_connect(data->conn))
ret = -1;
data->conn = NULL;

free_refs(refs_tmp);
free_refs(refs);
list_objects_filter_release(&args.filter_options);
Expand Down
11 changes: 11 additions & 0 deletions transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "remote.h"
#include "list-objects-filter-options.h"
#include "string-list.h"
#include "object-store.h"

struct git_transport_options {
unsigned thin : 1;
Expand All @@ -30,6 +31,12 @@ struct git_transport_options {
*/
unsigned connectivity_checked:1;

/*
* Transport will attempt to pull only object-info. Fallbacks
* to pulling entire object if object-info is not supported.
*/
unsigned object_info : 1;

int depth;
const char *deepen_since;
const struct string_list *deepen_not;
Expand All @@ -53,6 +60,10 @@ struct git_transport_options {
* common commits to this oidset instead of fetching any packfiles.
*/
struct oidset *acked_commits;

struct oid_array *object_info_oids;
struct object_info *object_info_data;
struct string_list *object_info_options;
};

enum transport_family {
Expand Down

0 comments on commit 33426e4

Please sign in to comment.