From: Pablo Sabater <pabloosabaterr@gmail.com>
To: git@vger.kernel.org
Cc: Pablo Sabater <pabloosabaterr@gmail.com>
Subject: [PATCH RFC 2/3] cat-file: make remote-object-info request in batches
Date: Tue, 27 Oct 2026 03:16:52 +0000 [thread overview]
Message-ID: <20261027-remote-object-info-batch-v1-2-ea7a1f0b1df1@gmail.com> (raw)
In-Reply-To: <20261027-remote-object-info-batch-v1-0-ea7a1f0b1df1@gmail.com>
remote-object-info has a hard limit of a maximum of 10k OIDs per
command, this means that if someone wants to request the object-info
from +10k objects the user is the one who has to batch it.
Make it more comfortable by allowing a user to put all the OIDs in a
single remote-object-info command and then internally, let
parse_cmd_remote_object_info() handle the batching.
A default max batch size has been set to 50k OIDs. This number comes
from benchmarking with hyperfine against a local git daemon serving a
bare clone of git.git, requesting 100k and 400k OIDs taken from
`rev-list --objects --all` with "%(objectname) %(objectsize)". A 47ms
delay (roughly my connect time to github.com) was added to
client-to-server traffic on port 9418 with dummynet, giving an RTT of
about 47ms:
+------------+---------------------+
| | objects requested |
| batch size | 100k | 400k |
+------------+----------+----------+
| 10k | 1.691s | 5.862s |
| 50k | 0.981s | 2.397s |
| 100k | 0.915s | 2.058s |
| 200k | 0.924s | 1.889s |
| 400k | 0.908s | 1.820s |
+------------+----------+----------+
To request all the batches over the same connection, it must stay open
between them. Currently, the connection to the remote is closed at two
places:
1. fetch_object_info_via_pack() at transport.c: finish_connect() is
called after fetching the object-info data.
2. parse_cmd_remote_object_info() at cat-file.c: transport_disconnect()
is called right after get_remote_info().
About 1, make fetch_object_info_via_pack() perform the handshake only
once and stop closing the connection, leaving that to
transport_disconnect() on its callers.
About 2, move transport_disconnect() after the batching loop, so it
only disconnects from the remote after all the batches have been
requested.
fetch_object_info() is void, so the only failure that
fetch_object_info_via_pack() could report came from finish_connect().
The exit status of the remote process is no longer checked. Any failure
while serving a batch is already caught by fetch_object_info(), either
through an ERR packet or an unexpected EOF, before any output for that
batch is written. This matches how ls-remote treats the connection
after ls-refs.
Make fetch_object_info_via_pack(), fetch_object_info_helper() and
transport_fetch_object_info() return void.
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
builtin/cat-file.c | 87 ++++++++++++++++++++++++++--------------------------
transport-helper.c | 13 ++++----
transport-internal.h | 6 ++--
transport.c | 33 +++++++++-----------
transport.h | 6 ++--
5 files changed, 69 insertions(+), 76 deletions(-)
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index f7d75a026b..4f78194bbd 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -39,12 +39,7 @@
*/
#define MAX_REMOTE_URL_LEN (8 * 1024)
-/* Maximum number of objects allowed in a single remote-object-info request. */
-#define MAX_ALLOWED_OBJ_LIMIT 10000
-
-/* Maximum input size permitted for the remote-object-info command. */
-#define MAX_REMOTE_OBJ_INFO_LINE \
- (MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))
+#define DEFAULT_REMOTE_BATCH_SIZE 50000
enum batch_mode {
BATCH_MODE_CONTENTS,
@@ -708,8 +703,7 @@ static void get_remote_info(struct transport *transport,
oid_array_append(object_info_oids, &oid);
}
- if (transport_fetch_object_info(transport, object_info_oids, results))
- die(_("failed to get object info from the remote"));
+ transport_fetch_object_info(transport, object_info_oids, results);
}
struct object_cb_data {
@@ -805,20 +799,15 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
char *line_to_split;
struct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;
struct oid_array object_info_oids = OID_ARRAY_INIT;
+ size_t batch_size = DEFAULT_REMOTE_BATCH_SIZE;
struct transport *transport;
struct remote *remote;
- if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)
- die(_("remote-object-info command too long"));
-
line_to_split = xstrdup(line);
count = split_cmdline(line_to_split, &argv);
if (count < 0)
die(_("remote-object-info: failed to parse command line: %s"),
split_cmdline_strerror(count));
- if (count - 1 > MAX_ALLOWED_OBJ_LIMIT)
- die(_("remote-object-info supports at most %d objects"),
- MAX_ALLOWED_OBJ_LIMIT);
if (count < 2)
die(_("remote-object-info requires objects"));
@@ -836,48 +825,58 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
if (!transport->smart_options)
die(_("smart options required"));
- get_remote_info(transport, argv + 1, count - 1, &results, &object_info_oids);
- string_list_clear(&data->remote_allowed_atoms, 0);
- string_list_append(&data->remote_allowed_atoms, "objectname");
- if (results.sizes)
- string_list_append(&data->remote_allowed_atoms, "objectsize");
- if (results.types)
- string_list_append(&data->remote_allowed_atoms, "objecttype");
+ for (size_t j = 0; j < count - 1; j += batch_size) {
+ const char **objects = argv + 1 + j;
+ size_t remaining = count - 1 - j;
+ size_t objects_nr = batch_size < remaining ? batch_size : remaining;
- transport_disconnect(transport);
+ get_remote_info(transport, objects, objects_nr, &results, &object_info_oids);
- data->skip_object_info = 1;
- for (size_t i = 0; i < results.nr; i++) {
- data->oid = object_info_oids.oid[i];
+ string_list_clear(&data->remote_allowed_atoms, 0);
+ string_list_append(&data->remote_allowed_atoms, "objectname");
+ if (results.sizes)
+ string_list_append(&data->remote_allowed_atoms, "objectsize");
+ if (results.types)
+ string_list_append(&data->remote_allowed_atoms, "objecttype");
- if (results.unrecognized[i]) {
- report_object_status(opt, oid_to_hex(&data->oid),
- &data->oid, "missing");
- continue;
- }
+ data->skip_object_info = 1;
+ for (size_t i = 0; i < results.nr; i++) {
+ data->oid = object_info_oids.oid[i];
- /*
- * When reaching here, it means remote-object-info can retrieve
- * information from server without downloading them.
- */
- if (results.sizes)
- data->size = results.sizes[i];
+ if (results.unrecognized[i]) {
+ report_object_status(opt, oid_to_hex(&data->oid),
+ &data->oid, "missing");
+ continue;
+ }
- if (results.types)
- data->type = results.types[i];
+ /*
+ * When reaching here, it means remote-object-info can retrieve
+ * information from server without downloading them.
+ */
+ if (results.sizes)
+ data->size = results.sizes[i];
- opt->batch_mode = BATCH_MODE_INFO;
- data->is_remote = 1;
- batch_object_write(argv[i + 1], output, opt, data, NULL, 0);
- data->is_remote = 0;
+ if (results.types)
+ data->type = results.types[i];
+
+ opt->batch_mode = BATCH_MODE_INFO;
+ data->is_remote = 1;
+ batch_object_write(objects[i], output, opt, data, NULL, 0);
+ data->is_remote = 0;
+ }
+ data->skip_object_info = 0;
+ FREE_AND_NULL(results.sizes);
+ FREE_AND_NULL(results.types);
+ FREE_AND_NULL(results.unrecognized);
+ results.nr = 0;
+ oid_array_clear(&object_info_oids);
}
- data->skip_object_info = 0;
+ transport_disconnect(transport);
free_fetch_object_info_results(&results);
free(line_to_split);
free(argv);
- oid_array_clear(&object_info_oids);
}
static void dispatch_calls(struct batch_options *opt,
diff --git a/transport-helper.c b/transport-helper.c
index d5a064d386..dafe829b50 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -786,16 +786,15 @@ static int fetch_refs(struct transport *transport,
return -1;
}
-static int fetch_object_info_helper(struct transport *transport,
- const struct oid_array *oids,
- struct fetch_object_info_results *results)
+static void fetch_object_info_helper(struct transport *transport,
+ const struct oid_array *oids,
+ struct fetch_object_info_results *results)
{
get_helper(transport);
if (process_connect(transport, 0))
- return transport->vtable->fetch_object_info(transport, oids,
- results);
-
- die(_("object-info requires protocol v2"));
+ transport->vtable->fetch_object_info(transport, oids, results);
+ else
+ die(_("object-info requires protocol v2"));
}
struct push_update_ref_state {
diff --git a/transport-internal.h b/transport-internal.h
index a10b27cc81..53ce86ed48 100644
--- a/transport-internal.h
+++ b/transport-internal.h
@@ -53,9 +53,9 @@ struct transport_vtable {
*
* Uses object-info capability of v2 protocol.
*/
- int (*fetch_object_info)(struct transport *transport,
- const struct oid_array *oids,
- struct fetch_object_info_results *results);
+ void (*fetch_object_info)(struct transport *transport,
+ const struct oid_array *oids,
+ struct fetch_object_info_results *results);
/**
* Push the objects and refs. Send the necessary objects, and
diff --git a/transport.c b/transport.c
index 25e2c14a7b..352540492c 100644
--- a/transport.c
+++ b/transport.c
@@ -433,11 +433,10 @@ static int get_bundle_uri(struct transport *transport)
transport->bundles, stateless_rpc);
}
-static int fetch_object_info_via_pack(struct transport *transport,
- const struct oid_array *oids,
- struct fetch_object_info_results *results)
+static void fetch_object_info_via_pack(struct transport *transport,
+ const struct oid_array *oids,
+ struct fetch_object_info_results *results)
{
- int ret = 0;
struct git_transport_data *data = transport->data;
struct packet_reader reader;
@@ -447,8 +446,13 @@ static int fetch_object_info_via_pack(struct transport *transport,
PACKET_READ_GENTLE_ON_EOF |
PACKET_READ_DIE_ON_ERR_PACKET);
- data->version = discover_version(&reader);
- transport->hash_algo = reader.hash_algo;
+ if (!data->finished_handshake) {
+ data->version = discover_version(&reader);
+ transport->hash_algo = reader.hash_algo;
+ data->finished_handshake = 1;
+ } else {
+ reader.hash_algo = transport->hash_algo;
+ }
fetch_object_info(data->version,
transport->server_options,
@@ -456,24 +460,15 @@ static int fetch_object_info_via_pack(struct transport *transport,
&reader,
results,
transport->stateless_rpc, data->fd[1]);
-
- close(data->fd[0]);
- if (data->fd[1] >= 0)
- close(data->fd[1]);
- if (finish_connect(data->conn))
- ret = -1;
- data->conn = NULL;
-
- return ret;
}
-int transport_fetch_object_info(struct transport *transport,
- const struct oid_array *oids,
- struct fetch_object_info_results *results)
+void transport_fetch_object_info(struct transport *transport,
+ const struct oid_array *oids,
+ struct fetch_object_info_results *results)
{
if (!transport->vtable->fetch_object_info)
die(_("remote does not support object-info"));
- return transport->vtable->fetch_object_info(transport, oids, results);
+ transport->vtable->fetch_object_info(transport, oids, results);
}
static int fetch_refs_via_pack(struct transport *transport,
diff --git a/transport.h b/transport.h
index 39193d0077..c7f2b8b87f 100644
--- a/transport.h
+++ b/transport.h
@@ -314,9 +314,9 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs);
/*
* Fetch the object info from remote
*/
-int transport_fetch_object_info(struct transport *transport,
- const struct oid_array *oids,
- struct fetch_object_info_results *results);
+void transport_fetch_object_info(struct transport *transport,
+ const struct oid_array *oids,
+ struct fetch_object_info_results *results);
/*
* If this flag is set, unlocking will avoid to call non-async-signal-safe
--
2.56.0
next prev parent reply other threads:[~2026-10-27 3:16 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-27 3:16 [PATCH RFC 0/3] cat-file: make remote-object-info request in batches Pablo Sabater
2026-10-27 3:16 ` [PATCH RFC 1/3] cat-file: extract the transport ownership from get_remote_info() Pablo Sabater
2026-10-27 19:02 ` Junio C Hamano
2026-10-27 21:00 ` Pablo Sabater
2026-10-28 6:37 ` Junio C Hamano
2026-10-28 12:07 ` Pablo Sabater
2026-10-28 13:42 ` Junio C Hamano
2026-10-27 3:16 ` Pablo Sabater [this message]
2026-10-27 3:16 ` [PATCH RFC 3/3] cat-file: add --remote-max-batch option Pablo Sabater
2026-10-27 19:12 ` [PATCH RFC 0/3] cat-file: make remote-object-info request in batches Junio C Hamano
2026-10-27 20:47 ` Pablo Sabater
2026-10-27 20:53 ` Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261027-remote-object-info-batch-v1-2-ea7a1f0b1df1@gmail.com \
--to=pabloosabaterr@gmail.com \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.