{"thread":{"id":"61695","subject":"[PATCH 0/6] cat-file: add remote-object-info to batch-command","startedAt":"2024-06-28T19:05:31Z","lastAt":"2026-07-24T15:52:30Z","messageCount":397,"participants":["Eric Ju","Karthik Nayak","Peijian Ju","Justin Tobler","Toon claes","Junio C Hamano","Toon Claes","Christian Couder","Taylor Blau","karthik nayak","Patrick Steinhardt","Calvin Wan","Jeff King","Pablo Sabater","Chandra Pratap","SZEDER Gábor"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"497818","messageId":"20240628190503.67389-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":null,"subject":"[PATCH 0/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-06-28T19:04:57Z","receivedAt":"2024-06-28T19:05:31Z","isPatch":true,"body":"This is a continuation of Calvin Wan's (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info command at [1].\n\nSometimes it is useful to get information about an object without having to download\nit completely. The server logic for retrieving size has already been implemented and merged in\n\"a2ba162cda (object-info: support for retrieving object info, 2021-04-20)\"[2].\nThis patch series implement the client option for it.\n\nThis patch series add the `remote-object-info` command to `cat-file --batch-command`. This command\nallows the client to make an object-info command request to a server\nthat supports protocol v2. If the server is v2, but does not have\nobject-info capability, the entire object is fetched and the\nrelevant object info is returned.\n\nA few questions open for discussions please:\n\n1. In the current implementation, if a user puts `remote-object-info` in protocol v1,\n   `cat-file --batch-command` will die. Which way do we prefer? \"error and exit (i.e. die)\"\n   or \"warn and wait for new command\".\n\n2. Right now, only the size is supported. If the batch command format\n   contains objectsize:disk or deltabase, it will die. The question\n   is about objecttype. In the current implementation, it will die too.\n   But dying on objecttype breaks the default format. We have changed the\n   default format to %(objectname) %(objectsize) when remote-object-info is used.\n   Any suggestions on this approach?\n\n\n[1] https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[2] https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n\n\nCalvin Wan (5):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n  cat-file: add remote-object-info to batch-command\n\nEric Ju (1):\n  cat-file: add declaration of variable i inside its for loop\n\n Documentation/git-cat-file.txt         |  22 +-\n builtin/cat-file.c                     | 240 ++++++++++----\n fetch-pack.c                           |  48 ++-\n fetch-pack.h                           |  10 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/t1017-cat-file-remote-object-info.sh | 412 +++++++++++++++++++++++++\n transport-helper.c                     |   8 +-\n transport.c                            | 102 +++++-\n transport.h                            |  11 +\n 11 files changed, 785 insertions(+), 86 deletions(-)\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\n-- \n2.45.2\n\n"},{"id":"497819","messageId":"20240628190503.67389-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH 1/6] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-06-28T19:04:58Z","receivedAt":"2024-06-28T19:05:42Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nA subsequent patch need to write capabilities for another command.\nRefactor write_fetch_command_and_capabilities() to be used by both\nfetch and future command.\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex eba9e420ea..fc9fb66cd8 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1313,13 +1313,13 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n+static void write_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t\t const struct string_list *server_options, const char* command)\n {\n \tconst char *hash_name;\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n@@ -1355,7 +1355,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, args->server_options, \"fetch\");\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2163,7 +2163,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, server_options, \"fetch\");\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.45.2\n\n"},{"id":"497820","messageId":"20240628190503.67389-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH 2/6] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-06-28T19:04:59Z","receivedAt":"2024-06-28T19:05:42Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex fc9fb66cd8..da0de9c537 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1676,18 +1676,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.45.2\n\n"},{"id":"497821","messageId":"20240628190503.67389-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH 3/6] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-06-28T19:05:00Z","receivedAt":"2024-06-28T19:05:43Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex aa651b73e9..fd42fecc15 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -68,7 +68,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -76,6 +76,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.45.2\n\n"},{"id":"497822","messageId":"20240628190503.67389-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH 4/6] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-06-28T19:05:01Z","receivedAt":"2024-06-28T19:05:44Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes it is useful to get information about an object without having\nto download it completely. The server logic has already been implemented\nas “a2ba162cda (object-info: support for retrieving object info,\n2021-04-20)”.\n\nAdd client functions to communicate with the server.\n\nThe client currently supports requesting a list of object ids with\nfeatures 'size' and 'type' from a v2 server. If a server does not\nadvertise either of the requested features, then the client falls back\nto making the request through 'fetch'.\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\n---\n fetch-pack.c       |  24 +++++++++++\n fetch-pack.h       |  10 +++++\n transport-helper.c |   8 +++-\n transport.c        | 102 ++++++++++++++++++++++++++++++++++++++++++---\n transport.h        |  11 +++++\n 5 files changed, 148 insertions(+), 7 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex da0de9c537..d533cac1d8 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1345,6 +1345,27 @@ static void write_command_and_capabilities(struct strbuf *req_buf,\n \tpacket_buf_delim(req_buf);\n }\n \n+void send_object_info_request(int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, args->server_options, \"object-info\");\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids) {\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\t}\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1682,6 +1703,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 6775d26517..16e4dc0824 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info **object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\n@@ -68,6 +70,12 @@ struct fetch_pack_args {\n \tunsigned connectivity_checked:1;\n };\n \n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n /*\n  * sought represents remote references that should be updated from.\n  * On return, the names that were found on the remote will have been\n@@ -101,4 +109,6 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n  */\n int report_unmatched_refs(struct ref **sought, int nr_sought);\n \n+void send_object_info_request(int fd_out, struct object_info_args *args);\n+\n #endif\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 9820947ab2..670d1e7068 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -697,13 +697,17 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n+\t * require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n \t\treturn -1;\n \t}\n+\tif (transport->smart_options->object_info) {\n+\t\t// fail the command explicitly to avoid further commands input\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\t}\n \n \tif (!data->get_refs_list_called)\n \t\tget_refs_list_using_list(transport, 0);\ndiff --git a/transport.c b/transport.c\nindex 83ddea8fbc..2847aa3f3c 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -363,6 +363,73 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \treturn refs;\n }\n \n+static int fetch_object_info(struct transport *transport, struct object_info **object_info_data)\n+{\n+\tint size_index = -1;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct object_info_args args;\n+\tstruct packet_reader reader;\n+\n+\tmemset(&args, 0, sizeof(args));\n+\targs.server_options = transport->server_options;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\tdata->version = discover_version(&reader);\n+\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tswitch (data->version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\treturn -1;\n+\t\tif (unsorted_string_list_has_string(args.object_info_options, \"size\")\n+\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0)) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tsend_object_info_request(data->fd[1], &args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"wrong protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args.object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n+\t\t\tif (!strcmp(reader.line, \"size\"))\n+\t\t\t\tsize_index = i;\n+\t\t\tcontinue;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(&reader) == PACKET_READ_NORMAL && i < args.oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader.line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\t\t\t*(*object_info_data)[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n+\t\t}\n+\t}\n+\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\n+\n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n \t\t\t\t\tstruct transport_ls_refs_options *options)\n {\n@@ -410,6 +477,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \tstruct ref *refs = NULL;\n \tstruct fetch_pack_args args;\n \tstruct ref *refs_tmp = NULL;\n+\tstruct ref *object_info_refs = xcalloc(1, sizeof (struct ref));\n \n \tmemset(&args, 0, sizeof(args));\n \targs.uploadpack = data->options.uploadpack;\n@@ -436,11 +504,27 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n-\n-\tif (!data->finished_handshake) {\n-\t\tint i;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options && transport->smart_options->object_info) {\n+\t\tstruct ref *ref = object_info_refs;\n+\n+\t\tif (!fetch_object_info(transport, data->options.object_info_data))\n+\t\t\tgoto cleanup;\n+\t\targs.object_info_data = data->options.object_info_data;\n+\t\targs.quiet = 1;\n+\t\targs.no_progress = 1;\n+\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n+\t\t\tstruct ref *temp_ref = xcalloc(1, sizeof (struct ref));\n+\t\t\ttemp_ref->old_oid = *(transport->smart_options->object_info_oids->oid + i);\n+\t\t\ttemp_ref->exact_oid = 1;\n+\t\t\tref->next = temp_ref;\n+\t\t\tref = ref->next;\n+\t\t}\n+\t\ttransport->remote_refs = object_info_refs->next;\n+\t} else if (!data->finished_handshake) {\n \t\tint must_list_refs = 0;\n-\t\tfor (i = 0; i < nr_heads; i++) {\n+\t\tfor (int i = 0; i < nr_heads; i++) {\n \t\t\tif (!to_fetch[i]->exact_oid) {\n \t\t\t\tmust_list_refs = 1;\n \t\t\t\tbreak;\n@@ -478,11 +562,18 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t  &transport->pack_lockfiles, data->version);\n \n \tdata->finished_handshake = 0;\n+\tif (args.object_info) {\n+\t\tstruct ref *ref_cpy_reader = object_info_refs->next;\n+\t\tfor (int i = 0; ref_cpy_reader; i++) {\n+\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &(*args.object_info_data)[i], OBJECT_INFO_LOOKUP_REPLACE);\n+\t\t\tref_cpy_reader = ref_cpy_reader->next;\n+\t\t}\n+\t}\n \tdata->options.self_contained_and_connected =\n \t\targs.self_contained_and_connected;\n \tdata->options.connectivity_checked = args.connectivity_checked;\n \n-\tif (!refs)\n+\tif (!refs && !args.object_info)\n \t\tret = -1;\n \tif (report_unmatched_refs(to_fetch, nr_heads))\n \t\tret = -1;\n@@ -498,6 +589,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \tfree_refs(refs_tmp);\n \tfree_refs(refs);\n \tlist_objects_filter_release(&args.filter_options);\n+\tfree_refs(object_info_refs);\n \treturn ret;\n }\n \ndiff --git a/transport.h b/transport.h\nindex 6393cd9823..5a3cda1860 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to pull only object-info. Fallbacks\n+\t * to pulling entire object if object-info is not supported.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info **object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.45.2\n\n"},{"id":"497823","messageId":"20240628190503.67389-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH 5/6] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-06-28T19:05:02Z","receivedAt":"2024-06-28T19:05:45Z","isPatch":true,"body":"Some code declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\n\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/cat-file.c | 11 +++--------\n 1 file changed, 3 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 43a1d7ac49..72a78cdc8c 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -668,12 +668,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (int i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -681,9 +679,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -709,7 +705,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -719,7 +714,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (int i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \n-- \n2.45.2\n\n"},{"id":"497824","messageId":"20240628190503.67389-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH 6/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-06-28T19:05:03Z","receivedAt":"2024-06-28T19:05:46Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSince the `info` command in cat-file --batch-command prints object info\nfor a given object, it is natural to add another command in cat-file\n--batch-command to print object info for a given object from a remote.\nAdd `remote-object-info` to cat-file --batch-command.\n\nWhile `info` takes object ids one at a time, this creates overhead when\nmaking requests to a server so `remote-object-info` instead can take\nmultiple object ids at once.\n\ncat-file --batch-command is generally implemented in the following\nmanner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Set batch mode state, get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Set batch mode state, get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through object info\n        - Call respective function attached to `info`\n        - Set batch mode state, use passed in object info, print object\n          info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through object info\n            - Call respective function attached to `info`\n            - Set batch mode state, use passed in object info, print\n              object info\n    Else:\n        - Call respective function attached to command\n        - Set batch mode state, get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen generates multiple `info` commands with the object info passed in.\n\nIn order for remote-object-info to avoid remote communication overhead\nin the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-cat-file.txt         |  22 +-\n builtin/cat-file.c                     | 231 ++++++++++----\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/t1017-cat-file-remote-object-info.sh | 412 +++++++++++++++++++++++++\n 5 files changed, 620 insertions(+), 59 deletions(-)\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex bd95a6c10a..ab0647bb39 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -149,6 +149,12 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified <remote> without\n+\tdownloading object from remote.\n+\tError when no object references is provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +296,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +305,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +322,9 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except remote-object-info command who uses\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+When \"%(objecttype)\" is supported, default format should be unified.\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +406,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since objecttype, objectsize:disk and deltabase are currently not supported by the\n+remote-object-info, git will error and exit when they are in the format string.\n+\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 72a78cdc8c..34958a1747 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -24,6 +24,9 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -42,9 +45,14 @@ struct batch_options {\n \tchar input_delim;\n \tchar output_delim;\n \tconst char *format;\n+\tint use_remote_info;\n };\n \n+#define DEFAULT_FORMAT \"%(objectname) %(objecttype) %(objectsize)\"\n+\n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -508,7 +516,6 @@ static void batch_object_write(const char *obj_name,\n \t}\n \n \tbatch_write(opt, scratch->buf, scratch->len);\n-\n \tif (opt->batch_mode == BATCH_MODE_CONTENTS) {\n \t\tprint_object_or_die(opt, data);\n \t\tbatch_write(opt, &opt->output_delim, 1);\n@@ -526,51 +533,118 @@ static void batch_one_object(const char *obj_name,\n \t\t(opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0);\n \tenum get_oid_result result;\n \n-\tresult = get_oid_with_context(the_repository, obj_name,\n-\t\t\t\t      flags, &data->oid, &ctx);\n-\tif (result != FOUND) {\n-\t\tswitch (result) {\n-\t\tcase MISSING_OBJECT:\n-\t\t\tprintf(\"%s missing%c\", obj_name, opt->output_delim);\n-\t\t\tbreak;\n-\t\tcase SHORT_NAME_AMBIGUOUS:\n-\t\t\tprintf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n-\t\t\tbreak;\n-\t\tcase DANGLING_SYMLINK:\n-\t\t\tprintf(\"dangling %\"PRIuMAX\"%c%s%c\",\n-\t\t\t       (uintmax_t)strlen(obj_name),\n-\t\t\t       opt->output_delim, obj_name, opt->output_delim);\n-\t\t\tbreak;\n-\t\tcase SYMLINK_LOOP:\n-\t\t\tprintf(\"loop %\"PRIuMAX\"%c%s%c\",\n-\t\t\t       (uintmax_t)strlen(obj_name),\n-\t\t\t       opt->output_delim, obj_name, opt->output_delim);\n-\t\t\tbreak;\n-\t\tcase NOT_DIR:\n-\t\t\tprintf(\"notdir %\"PRIuMAX\"%c%s%c\",\n-\t\t\t       (uintmax_t)strlen(obj_name),\n-\t\t\t       opt->output_delim, obj_name, opt->output_delim);\n-\t\t\tbreak;\n-\t\tdefault:\n-\t\t\tBUG(\"unknown get_sha1_with_context result %d\\n\",\n-\t\t\t       result);\n-\t\t\tbreak;\n+\tif (!opt->use_remote_info) {\n+\t\tresult = get_oid_with_context(the_repository, obj_name,\n+\t\t\t\t\t\tflags, &data->oid, &ctx);\n+\t\tif (result != FOUND) {\n+\t\t\tswitch (result) {\n+\t\t\tcase MISSING_OBJECT:\n+\t\t\t\tprintf(\"%s missing%c\", obj_name, opt->output_delim);\n+\t\t\t\tbreak;\n+\t\t\tcase SHORT_NAME_AMBIGUOUS:\n+\t\t\t\tprintf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n+\t\t\t\tbreak;\n+\t\t\tcase DANGLING_SYMLINK:\n+\t\t\t\tprintf(\"dangling %\"PRIuMAX\"%c%s%c\",\n+\t\t\t\t\t(uintmax_t)strlen(obj_name),\n+\t\t\t\t\topt->output_delim, obj_name, opt->output_delim);\n+\t\t\t\tbreak;\n+\t\t\tcase SYMLINK_LOOP:\n+\t\t\t\tprintf(\"loop %\"PRIuMAX\"%c%s%c\",\n+\t\t\t\t\t(uintmax_t)strlen(obj_name),\n+\t\t\t\t\topt->output_delim, obj_name, opt->output_delim);\n+\t\t\t\tbreak;\n+\t\t\tcase NOT_DIR:\n+\t\t\t\tprintf(\"notdir %\"PRIuMAX\"%c%s%c\",\n+\t\t\t\t\t(uintmax_t)strlen(obj_name),\n+\t\t\t\t\topt->output_delim, obj_name, opt->output_delim);\n+\t\t\t\tbreak;\n+\t\t\tdefault:\n+\t\t\t\tBUG(\"unknown get_sha1_with_context result %d\\n\",\n+\t\t\t\t\tresult);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tfflush(stdout);\n+\t\t\treturn;\n \t\t}\n-\t\tfflush(stdout);\n-\t\treturn;\n-\t}\n \n-\tif (ctx.mode == 0) {\n-\t\tprintf(\"symlink %\"PRIuMAX\"%c%s%c\",\n-\t\t       (uintmax_t)ctx.symlink_path.len,\n-\t\t       opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n-\t\tfflush(stdout);\n-\t\treturn;\n+\t\tif (ctx.mode == 0) {\n+\t\t\tprintf(\"symlink %\"PRIuMAX\"%c%s%c\",\n+\t\t\t\t(uintmax_t)ctx.symlink_path.len,\n+\t\t\t\topt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n+\t\t\tfflush(stdout);\n+\t\t\treturn;\n+\t\t}\n \t}\n \n \tbatch_object_write(obj_name, scratch, opt, data, NULL, 0);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT\n+\t*/\n+\tif (!opt->format) {\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\t}\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tint include_size = 0;\n+\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\t\t/*\n+\t\t * 'size' is the only option currently supported.\n+\t\t * Other options that are passed in the format will exit with error.\n+\t\t */\n+\t\tif (strstr(opt->format, \"%(objectsize)\")) {\n+\t\t\tstring_list_append(&object_info_options, \"size\");\n+\t\t\tinclude_size = 1;\n+\t\t}\n+\t\tif (strstr(opt->format, \"%(objecttype)\")) {\n+\t\t\tdie(_(\"objecttype is currently not supported with remote-object-info\"));\n+\t\t}\n+\t\tif (strstr(opt->format, \"%(objectsize:disk)\"))\n+\t\t\tdie(_(\"objectsize:disk is currently not supported with remote-object-info\"));\n+\t\tif (strstr(opt->format, \"%(deltabase)\"))\n+\t\t\tdie(_(\"deltabase is currently not supported with remote-object-info\"));\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\t\t\tif (include_size)\n+\t\t\t\t\tremote_object_info[i].sizep = xcalloc(1, sizeof(long));\n+\t\t\t}\n+\t\t\tgtransport->smart_options->object_info_data = &remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -642,6 +716,7 @@ typedef void (*parse_cmd_fn_t)(struct batch_options *, const char *,\n struct queued_cmd {\n \tparse_cmd_fn_t fn;\n \tchar *line;\n+\tconst char *name;\n };\n \n static void parse_cmd_contents(struct batch_options *opt,\n@@ -662,6 +737,55 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static const struct parse_cmd {\n+\tconst char *name;\n+\tparse_cmd_fn_t fn;\n+\tunsigned takes_args;\n+} commands[] = {\n+\t{ \"contents\", parse_cmd_contents, 1 },\n+\t{ \"info\", parse_cmd_info, 1 },\n+\t{ \"remote-object-info\", parse_cmd_info, 1 },\n+\t{ \"flush\", NULL, 0 },\n+};\n+\n+static void parse_remote_info(struct batch_options *opt,\n+\t\t\t   char *line,\n+\t\t\t   struct strbuf *output,\n+\t\t\t   struct expand_data *data,\n+\t\t\t   const struct parse_cmd *p_cmd,\n+\t\t\t   struct queued_cmd *q_cmd)\n+{\n+\tint count;\n+\tconst char **argv;\n+\n+\tcount = split_cmdline(line, &argv);\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\topt->use_remote_info = 1;\n+\tdata->skip_object_info = 1;\n+\tdata->mark_query = 0;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tif (remote_object_info[i].sizep)\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\tif (remote_object_info[i].typep)\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (p_cmd)\n+\t\t\tp_cmd->fn(opt, argv[i+1], output, data);\n+\t\telse\n+\t\t\tq_cmd->fn(opt, argv[i+1], output, data);\n+\t}\n+\topt->use_remote_info = 0;\n+\tdata->skip_object_info = 0;\n+\tdata->mark_query = 1;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -671,8 +795,12 @@ static void dispatch_calls(struct batch_options *opt,\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (int i = 0; i < nr; i++)\n-\t\tcmd[i].fn(opt, cmd[i].line, output, data);\n+\tfor (int i = 0; i < nr; i++) {\n+\t\tif (!strcmp(cmd[i].name, \"remote-object-info\"))\n+\t\t\tparse_remote_info(opt, cmd[i].line, output, data, NULL, &cmd[i]);\n+\t\telse\n+\t\t\tcmd[i].fn(opt, cmd[i].line, output, data);\n+\t}\n \n \tfflush(stdout);\n }\n@@ -685,17 +813,6 @@ static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n \t*nr = 0;\n }\n \n-\n-static const struct parse_cmd {\n-\tconst char *name;\n-\tparse_cmd_fn_t fn;\n-\tunsigned takes_args;\n-} commands[] = {\n-\t{ \"contents\", parse_cmd_contents, 1},\n-\t{ \"info\", parse_cmd_info, 1},\n-\t{ \"flush\", NULL, 0},\n-};\n-\n static void batch_objects_command(struct batch_options *opt,\n \t\t\t\t    struct strbuf *output,\n \t\t\t\t    struct expand_data *data)\n@@ -740,11 +857,17 @@ static void batch_objects_command(struct batch_options *opt,\n \t\t\tdispatch_calls(opt, output, data, queued_cmd, nr);\n \t\t\tfree_cmds(queued_cmd, &nr);\n \t\t} else if (!opt->buffer_output) {\n-\t\t\tcmd->fn(opt, p, output, data);\n+\t\t\tif (!strcmp(cmd->name, \"remote-object-info\")) {\n+\t\t\t\tchar *line = xstrdup_or_null(p);\n+\t\t\t\tparse_remote_info(opt, line, output, data, cmd, NULL);\n+\t\t\t} else {\n+\t\t\t\tcmd->fn(opt, p, output, data);\n+\t\t\t}\n \t\t} else {\n \t\t\tALLOC_GROW(queued_cmd, nr + 1, alloc);\n \t\t\tcall.fn = cmd->fn;\n \t\t\tcall.line = xstrdup_or_null(p);\n+\t\t\tcall.name = cmd->name;\n \t\t\tqueued_cmd[nr++] = call;\n \t\t}\n \t}\n@@ -761,8 +884,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tstrbuf_release(&input);\n }\n \n-#define DEFAULT_FORMAT \"%(objectname) %(objecttype) %(objectsize)\"\n-\n static int batch_objects(struct batch_options *opt)\n {\n \tstruct strbuf input = STRBUF_INIT;\ndiff --git a/object-file.c b/object-file.c\nindex d3cf4b8b2e..6aaa167942 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2988,3 +2988,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex c5f2bb2fc2..333e19cd1e 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -533,4 +533,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(each_packed_object_fn, void *,\n \t\t\t   enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..7a7bdfeb91\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,412 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+echo_without_newline () {\n+    printf '%s' \"$*\"\n+}\n+\n+strlen () {\n+    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\".\n+# Tests with the default filter are used to test the fallback to 'fetch' command\n+\n+\n+# Test --batch-command remote-object-info with 'git://' transport\n+\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+'\n+\n+set_transport_variables () {\n+\thello_sha1=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_sha1=$(git -C \"$1\" write-tree)\n+\tcommit_sha1=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_sha1)\n+\ttag_sha1=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent\" &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_sha1\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent\" &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent\" &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1 $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent\" &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1 $tag_sha1\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'http://' transport\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello\n+'\n+\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1 $tag_sha1\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1 $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n+\t\tEOF\n+\t\ttest_grep \"objectsize:disk is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n+\t\tEOF\n+\t\ttest_grep \"deltabase is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t(\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+# shellcheck disable=SC2016\n+test_expect_success 'remote-object-info fails on missing OID fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd missing_oid_repo &&\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"fatal: object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' transport\n+\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello\n+'\n+\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tcd server &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://$(pwd)\" $hello_sha1\n+\t\tremote-object-info \"file://$(pwd)\" $tree_sha1\n+\t\tremote-object-info \"file://$(pwd)\" $commit_sha1\n+\t\tremote-object-info \"file://$(pwd)\" $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tcd server &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://$(pwd)\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tcd server &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://$(pwd)\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"file://$(pwd)\" $commit_sha1 $tag_sha1\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tcd server &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://$(pwd)\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"file://$(pwd)\" $commit_sha1 $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_done\n-- \n2.45.2\n\n"},{"id":"498089","messageId":"CAOLa=ZSY1y4wz6M9mOLvTCPoeCmceD-HKqT5tomF+BzbL5yp4Q@mail.gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-2-eric.peijian@gmail.com","subject":"Re: [PATCH 1/6] fetch-pack: refactor packet writing","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-07-04T16:59:59Z","receivedAt":"2024-07-04T17:00:03Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n> From: Calvin Wan <calvinwan@google.com>\n>\n> A subsequent patch need to write capabilities for another command.\n\ns/need/needs\n\n> Refactor write_fetch_command_and_capabilities() to be used by both\n> fetch and future command.\n>\n\nNit: mostly from my lack of understanding, but until I read the code, I\ncouldn't understand what 'command' meant in this para. Maybe some\npreface would be nice here.\n\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n>  fetch-pack.c | 12 ++++++------\n>  1 file changed, 6 insertions(+), 6 deletions(-)\n>\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index eba9e420ea..fc9fb66cd8 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1313,13 +1313,13 @@ static int add_haves(struct fetch_negotiator *negotiator,\n>  \treturn haves_added;\n>  }\n>\n> -static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> -\t\t\t\t\t\t const struct string_list *server_options)\n> +static void write_command_and_capabilities(struct strbuf *req_buf,\n> +\t\t\t\t\t\t const struct string_list *server_options, const char* command)\n>  {\n>  \tconst char *hash_name;\n>\n> -\tensure_server_supports_v2(\"fetch\");\n> -\tpacket_buf_write(req_buf, \"command=fetch\");\n> +\tensure_server_supports_v2(command);\n> +\tpacket_buf_write(req_buf, \"command=%s\", command);\n>  \tif (server_supports_v2(\"agent\"))\n>  \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n>  \tif (advertise_sid && server_supports_v2(\"session-id\"))\n> @@ -1355,7 +1355,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n>  \tint done_sent = 0;\n>  \tstruct strbuf req_buf = STRBUF_INIT;\n>\n> -\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n> +\twrite_command_and_capabilities(&req_buf, args->server_options, \"fetch\");\n>\n>  \tif (args->use_thin_pack)\n>  \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n> @@ -2163,7 +2163,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n>  \t\t\t\t\t   the_repository, \"%d\",\n>  \t\t\t\t\t   negotiation_round);\n>  \t\tstrbuf_reset(&req_buf);\n> -\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n> +\t\twrite_command_and_capabilities(&req_buf, server_options, \"fetch\");\n>\n>  \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n>\n> --\n> 2.45.2\n\nRight, this commit in itself looks good. But I was curious why we need\nthis, so I did a sneak peak into the following commits.\n\nTo summarize, we want to call:\n   `write_command_and_capabilities(..., \"object-info\");`\nin the upcoming patches to get the object-info details from the server.\nBut isn't this function too specific to the \"fetch\" command to be\ngeneralized to be for \"object-info\" too?\n\nWouldn't it make sense to add a custom function for 'object-info' in\n'connect.c'? Like how we currently have `get_remote_bundle_uri()` for\n'bundle-uri' and `get_remote_refs` for 'ls-refs'?\n"},{"id":"498269","messageId":"CAN2LT1Ctwdij9-DujKeuzPX71mzEoMVbdrTkJ8bpRnX9NAZy9Q@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZSY1y4wz6M9mOLvTCPoeCmceD-HKqT5tomF+BzbL5yp4Q@mail.gmail.com","subject":"Re: [PATCH 1/6] fetch-pack: refactor packet writing","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-08T15:17:46Z","receivedAt":"2024-07-08T15:17:57Z","isPatch":true,"body":"On Thu, Jul 4, 2024 at 1:00 PM Karthik Nayak <karthik.188@gmail.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> > From: Calvin Wan <calvinwan@google.com>\n> >\n> > A subsequent patch need to write capabilities for another command.\n>\n> s/need/needs\n\nThank you. Fixed in v2.\n\n> > Refactor write_fetch_command_and_capabilities() to be used by both\n> > fetch and future command.\n> >\n>\n> Nit: mostly from my lack of understanding, but until I read the code, I\n> couldn't understand what 'command' meant in this para. Maybe some\n> preface would be nice here.\n>\n\nThank you. I will add this in v2 commit message.\n\n    Here \"command\" means the \"operations\" supported by Git’s wire protocol\n    https://git-scm.com/docs/protocol-v2. An example would be a\n    git's subcommand, such as git-fetch(1); or an operation supported by\n    the server side such as \"object-info\" implemented at \"a2ba162cda\n    (object-info: support for retrieving object info, 2021-04-20)\".\n\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> > ---\n> >  fetch-pack.c | 12 ++++++------\n> >  1 file changed, 6 insertions(+), 6 deletions(-)\n> >\n> > diff --git a/fetch-pack.c b/fetch-pack.c\n> > index eba9e420ea..fc9fb66cd8 100644\n> > --- a/fetch-pack.c\n> > +++ b/fetch-pack.c\n> > @@ -1313,13 +1313,13 @@ static int add_haves(struct fetch_negotiator *negotiator,\n> >       return haves_added;\n> >  }\n> >\n> > -static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> > -                                              const struct string_list *server_options)\n> > +static void write_command_and_capabilities(struct strbuf *req_buf,\n> > +                                              const struct string_list *server_options, const char* command)\n> >  {\n> >       const char *hash_name;\n> >\n> > -     ensure_server_supports_v2(\"fetch\");\n> > -     packet_buf_write(req_buf, \"command=fetch\");\n> > +     ensure_server_supports_v2(command);\n> > +     packet_buf_write(req_buf, \"command=%s\", command);\n> >       if (server_supports_v2(\"agent\"))\n> >               packet_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n> >       if (advertise_sid && server_supports_v2(\"session-id\"))\n> > @@ -1355,7 +1355,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n> >       int done_sent = 0;\n> >       struct strbuf req_buf = STRBUF_INIT;\n> >\n> > -     write_fetch_command_and_capabilities(&req_buf, args->server_options);\n> > +     write_command_and_capabilities(&req_buf, args->server_options, \"fetch\");\n> >\n> >       if (args->use_thin_pack)\n> >               packet_buf_write(&req_buf, \"thin-pack\");\n> > @@ -2163,7 +2163,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n> >                                          the_repository, \"%d\",\n> >                                          negotiation_round);\n> >               strbuf_reset(&req_buf);\n> > -             write_fetch_command_and_capabilities(&req_buf, server_options);\n> > +             write_command_and_capabilities(&req_buf, server_options, \"fetch\");\n> >\n> >               packet_buf_write(&req_buf, \"wait-for-done\");\n> >\n> > --\n> > 2.45.2\n>\n> Right, this commit in itself looks good. But I was curious why we need\n> this, so I did a sneak peak into the following commits.\n>\n> To summarize, we want to call:\n>    `write_command_and_capabilities(..., \"object-info\");`\n> in the upcoming patches to get the object-info details from the server.\n> But isn't this function too specific to the \"fetch\" command to be\n> generalized to be for \"object-info\" too?\n>\n> Wouldn't it make sense to add a custom function for 'object-info' in\n> 'connect.c'? Like how we currently have `get_remote_bundle_uri()` for\n> 'bundle-uri' and `get_remote_refs` for 'ls-refs'?\n\nThank you. I am reading through the old comments left by Taylor\nat https://lore.kernel.org/git/YkOPyc9tUfe2Tozx@nand.local/\n\n  \" Makes obvious sense, and this was something that jumped out to me when I\n  looked at the first and second versions of this patch. I'm glad that\n  this is getting factored out.\"\n\n\nIt seems refactoring this into a more general function is on purpose.\nIt is encouraged to use this general function to request capability\nrather than adding a custom function.\nTaylor’s comment was 2 years ago, but I think refactoring this into a\nmore general function to\nenforce DRY still makes sense.\n"},{"id":"498337","messageId":"7lc6m7627sojdzabmz6pvaulbrcods6tqf7bnf3vxqsxzievjl@ixbispu2b3ch","threadId":"61695","inReplyTo":"20240628190503.67389-7-eric.peijian@gmail.com","subject":"Re: [PATCH 6/6] cat-file: add remote-object-info to batch-command","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2024-07-09T01:50:53Z","receivedAt":"2024-07-09T01:51:24Z","isPatch":true,"body":"On 24/06/28 03:05PM, Eric Ju wrote:\n> From: Calvin Wan <calvinwan@google.com>\n> \n> Since the `info` command in cat-file --batch-command prints object info\n> for a given object, it is natural to add another command in cat-file\n> --batch-command to print object info for a given object from a remote.\n> Add `remote-object-info` to cat-file --batch-command.\n> \n> While `info` takes object ids one at a time, this creates overhead when\n> making requests to a server so `remote-object-info` instead can take\n> multiple object ids at once.\n> \n> cat-file --batch-command is generally implemented in the following\n> manner:\n> \n>  - Receive and parse input from user\n>  - Call respective function attached to command\n>  - Set batch mode state, get object info, print object info\n> \n> In --buffer mode, this changes to:\n> \n>  - Receive and parse input from user\n>  - Store respective function attached to command in a queue\n>  - After flush, loop through commands in queue\n>     - Call respective function attached to command\n>     - Set batch mode state, get object info, print object info\n\nSo the problem is that there is overhead associated with getting object\ninfo from the remote. Therefore, remote-object-info also supports\nbatching objects together. This seems reasonable.\n\n> \n> Notice how the getting and printing of object info is accomplished one\n> at a time. As described above, this creates a problem for making\n> requests to a server. Therefore, `remote-object-info` is implemented in\n> the following manner:\n> \n>  - Receive and parse input from user\n>  If command is `remote-object-info`:\n>     - Get object info from remote\n>     - Loop through object info\n>         - Call respective function attached to `info`\n>         - Set batch mode state, use passed in object info, print object\n>           info\n>  Else:\n>     - Call respective function attached to command\n>     - Parse input, get object info, print object info\n> \n> And finally for --buffer mode `remote-object-info`:\n>  - Receive and parse input from user\n>  - Store respective function attached to command in a queue\n>  - After flush, loop through commands in queue:\n>     If command is `remote-object-info`:\n>         - Get object info from remote\n>         - Loop through object info\n>             - Call respective function attached to `info`\n>             - Set batch mode state, use passed in object info, print\n>               object info\n>     Else:\n>         - Call respective function attached to command\n>         - Set batch mode state, get object info, print object info\n> \n> To summarize, `remote-object-info` gets object info from the remote and\n> then generates multiple `info` commands with the object info passed in.\n> \n> In order for remote-object-info to avoid remote communication overhead\n> in the non-buffer mode, the objects are passed in as such:\n\nEven in non-buffer mode, having separate remote-object-info commands\nwould result in additional overhead correct? From my understanding each\ncommand is executed sequently, so multiples of remote-object-info would\nalways result in additional overhead.\n\n> \n> remote-object-info <remote> <oid> <oid> ... <oid>\n> \n> rather than\n> \n> remote-object-info <remote> <oid>\n> remote-object-info <remote> <oid>\n> ...\n> remote-object-info <remote> <oid>\n> \n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n\nI think the sign-offs are supposed to go at the bottom.\n\n[snip]\n> @@ -526,51 +533,118 @@ static void batch_one_object(const char *obj_name,\n>  \t\t(opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0);\n>  \tenum get_oid_result result;\n>  \n> -\tresult = get_oid_with_context(the_repository, obj_name,\n> -\t\t\t\t      flags, &data->oid, &ctx);\n> -\tif (result != FOUND) {\n> -\t\tswitch (result) {\n> -\t\tcase MISSING_OBJECT:\n> -\t\t\tprintf(\"%s missing%c\", obj_name, opt->output_delim);\n> -\t\t\tbreak;\n> -\t\tcase SHORT_NAME_AMBIGUOUS:\n> -\t\t\tprintf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n> -\t\t\tbreak;\n> -\t\tcase DANGLING_SYMLINK:\n> -\t\t\tprintf(\"dangling %\"PRIuMAX\"%c%s%c\",\n> -\t\t\t       (uintmax_t)strlen(obj_name),\n> -\t\t\t       opt->output_delim, obj_name, opt->output_delim);\n> -\t\t\tbreak;\n> -\t\tcase SYMLINK_LOOP:\n> -\t\t\tprintf(\"loop %\"PRIuMAX\"%c%s%c\",\n> -\t\t\t       (uintmax_t)strlen(obj_name),\n> -\t\t\t       opt->output_delim, obj_name, opt->output_delim);\n> -\t\t\tbreak;\n> -\t\tcase NOT_DIR:\n> -\t\t\tprintf(\"notdir %\"PRIuMAX\"%c%s%c\",\n> -\t\t\t       (uintmax_t)strlen(obj_name),\n> -\t\t\t       opt->output_delim, obj_name, opt->output_delim);\n> -\t\t\tbreak;\n> -\t\tdefault:\n> -\t\t\tBUG(\"unknown get_sha1_with_context result %d\\n\",\n> -\t\t\t       result);\n> -\t\t\tbreak;\n> +\tif (!opt->use_remote_info) {\n\nWhen using the remote-object-info command, the object in question is\nsupposed to be on the remote and may not exist locally. Therefore we\nskip over `get_oid_with_context()`.\n\n> +\t\tresult = get_oid_with_context(the_repository, obj_name,\n> +\t\t\t\t\t\tflags, &data->oid, &ctx);\n> +\t\tif (result != FOUND) {\n> +\t\t\tswitch (result) {\n> +\t\t\tcase MISSING_OBJECT:\n> +\t\t\t\tprintf(\"%s missing%c\", obj_name, opt->output_delim);\n> +\t\t\t\tbreak;\n> +\t\t\tcase SHORT_NAME_AMBIGUOUS:\n> +\t\t\t\tprintf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n> +\t\t\t\tbreak;\n> +\t\t\tcase DANGLING_SYMLINK:\n> +\t\t\t\tprintf(\"dangling %\"PRIuMAX\"%c%s%c\",\n> +\t\t\t\t\t(uintmax_t)strlen(obj_name),\n> +\t\t\t\t\topt->output_delim, obj_name, opt->output_delim);\n> +\t\t\t\tbreak;\n> +\t\t\tcase SYMLINK_LOOP:\n> +\t\t\t\tprintf(\"loop %\"PRIuMAX\"%c%s%c\",\n> +\t\t\t\t\t(uintmax_t)strlen(obj_name),\n> +\t\t\t\t\topt->output_delim, obj_name, opt->output_delim);\n> +\t\t\t\tbreak;\n> +\t\t\tcase NOT_DIR:\n> +\t\t\t\tprintf(\"notdir %\"PRIuMAX\"%c%s%c\",\n> +\t\t\t\t\t(uintmax_t)strlen(obj_name),\n> +\t\t\t\t\topt->output_delim, obj_name, opt->output_delim);\n> +\t\t\t\tbreak;\n> +\t\t\tdefault:\n> +\t\t\t\tBUG(\"unknown get_sha1_with_context result %d\\n\",\n> +\t\t\t\t\tresult);\n> +\t\t\t\tbreak;\n> +\t\t\t}\n> +\t\t\tfflush(stdout);\n> +\t\t\treturn;\n>  \t\t}\n> -\t\tfflush(stdout);\n> -\t\treturn;\n> -\t}\n>  \n> -\tif (ctx.mode == 0) {\n> -\t\tprintf(\"symlink %\"PRIuMAX\"%c%s%c\",\n> -\t\t       (uintmax_t)ctx.symlink_path.len,\n> -\t\t       opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n> -\t\tfflush(stdout);\n> -\t\treturn;\n> +\t\tif (ctx.mode == 0) {\n> +\t\t\tprintf(\"symlink %\"PRIuMAX\"%c%s%c\",\n> +\t\t\t\t(uintmax_t)ctx.symlink_path.len,\n> +\t\t\t\topt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n> +\t\t\tfflush(stdout);\n> +\t\t\treturn;\n> +\t\t}\n>  \t}\n>  \n>  \tbatch_object_write(obj_name, scratch, opt, data, NULL, 0);\n>  }\n>  \n> +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> +{\n> +\tint retval = 0;\n> +\tstruct remote *remote = NULL;\n> +\tstruct object_id oid;\n> +\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> +\tstatic struct transport *gtransport;\n> +\n> +\t/*\n> +\t * Change the format to \"%(objectname) %(objectsize)\" when\n> +\t * remote-object-info command is used. Once we start supporting objecttype\n> +\t * the default format should change to DEFAULT_FORMAT\n> +\t*/\n> +\tif (!opt->format) {\n> +\t\topt->format = \"%(objectname) %(objectsize)\";\n> +\t}\n\nWe should omit the parenthesis for single line if statements.\n\n> +\n> +\tremote = remote_get(argv[0]);\n> +\tif (!remote)\n> +\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n> +\toid_array_clear(&object_info_oids);\n> +\tfor (size_t i = 1; i < argc; i++) {\n> +\t\tif (get_oid_hex(argv[i], &oid))\n> +\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n> +\t\toid_array_append(&object_info_oids, &oid);\n> +\t}\n> +\n> +\tgtransport = transport_get(remote, NULL);\n> +\tif (gtransport->smart_options) {\n> +\t\tint include_size = 0;\n> +\n> +\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> +\t\tgtransport->smart_options->object_info = 1;\n> +\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n> +\t\t/*\n> +\t\t * 'size' is the only option currently supported.\n> +\t\t * Other options that are passed in the format will exit with error.\n> +\t\t */\n> +\t\tif (strstr(opt->format, \"%(objectsize)\")) {\n> +\t\t\tstring_list_append(&object_info_options, \"size\");\n> +\t\t\tinclude_size = 1;\n> +\t\t}\n> +\t\tif (strstr(opt->format, \"%(objecttype)\")) {\n> +\t\t\tdie(_(\"objecttype is currently not supported with remote-object-info\"));\n> +\t\t}\n\nAnother single line if statement above that should omit the parenthesis.\n\n> +\t\tif (strstr(opt->format, \"%(objectsize:disk)\"))\n> +\t\t\tdie(_(\"objectsize:disk is currently not supported with remote-object-info\"));\n> +\t\tif (strstr(opt->format, \"%(deltabase)\"))\n> +\t\t\tdie(_(\"deltabase is currently not supported with remote-object-info\"));\n> +\t\tif (object_info_options.nr > 0) {\n> +\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n> +\t\t\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n> +\t\t\t\tif (include_size)\n> +\t\t\t\t\tremote_object_info[i].sizep = xcalloc(1, sizeof(long));\n> +\t\t\t}\n> +\t\t\tgtransport->smart_options->object_info_data = &remote_object_info;\n> +\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n> +\t\t}\n> +\t} else {\n> +\t\tretval = -1;\n> +\t}\n> +\n> +\treturn retval;\n> +}\n> +\n>  struct object_cb_data {\n>  \tstruct batch_options *opt;\n>  \tstruct expand_data *expand;\n> @@ -642,6 +716,7 @@ typedef void (*parse_cmd_fn_t)(struct batch_options *, const char *,\n>  struct queued_cmd {\n>  \tparse_cmd_fn_t fn;\n>  \tchar *line;\n> +\tconst char *name;\n\nSince special handling is needed for the remote-object-info command, we\nrecord the queued command names to check against later.\n\n>  };\n>  \n>  static void parse_cmd_contents(struct batch_options *opt,\n> @@ -662,6 +737,55 @@ static void parse_cmd_info(struct batch_options *opt,\n>  \tbatch_one_object(line, output, opt, data);\n>  }\n>  \n> +static const struct parse_cmd {\n> +\tconst char *name;\n> +\tparse_cmd_fn_t fn;\n> +\tunsigned takes_args;\n> +} commands[] = {\n> +\t{ \"contents\", parse_cmd_contents, 1 },\n> +\t{ \"info\", parse_cmd_info, 1 },\n> +\t{ \"remote-object-info\", parse_cmd_info, 1 },\n> +\t{ \"flush\", NULL, 0 },\n> +};\n> +\n> +static void parse_remote_info(struct batch_options *opt,\n> +\t\t\t   char *line,\n> +\t\t\t   struct strbuf *output,\n> +\t\t\t   struct expand_data *data,\n> +\t\t\t   const struct parse_cmd *p_cmd,\n> +\t\t\t   struct queued_cmd *q_cmd)\n\nIt seems a little confusing to me that `parse_remote_info()` accepts \nboth a `parse_cmd` and `queued_cmd`, but only expects to use one or the\nother. It looks like this is done because `dispatch_calls()` already \naccepts `queued_cmd`, but now needs to call `parse_remote_info()`. \n\nSince it is only the underlying command function that is needed by\n`parse_remote_info()`\n\n> +{\n> +\tint count;\n> +\tconst char **argv;\n> +\n> +\tcount = split_cmdline(line, &argv);\n> +\tif (get_remote_info(opt, count, argv))\n> +\t\tgoto cleanup;\n> +\topt->use_remote_info = 1;\n> +\tdata->skip_object_info = 1;\n> +\tdata->mark_query = 0;\n> +\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n> +\t\tif (remote_object_info[i].sizep)\n> +\t\t\tdata->size = *remote_object_info[i].sizep;\n> +\t\tif (remote_object_info[i].typep)\n> +\t\t\tdata->type = *remote_object_info[i].typep;\n> +\n> +\t\tdata->oid = object_info_oids.oid[i];\n> +\t\tif (p_cmd)\n> +\t\t\tp_cmd->fn(opt, argv[i+1], output, data);\n> +\t\telse\n> +\t\t\tq_cmd->fn(opt, argv[i+1], output, data);\n> +\t}\n> +\topt->use_remote_info = 0;\n> +\tdata->skip_object_info = 0;\n> +\tdata->mark_query = 1;\n> +\n> +cleanup:\n> +\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n> +\t\tfree_object_info_contents(&remote_object_info[i]);\n> +\tfree(remote_object_info);\n> +}\n> +\n>  static void dispatch_calls(struct batch_options *opt,\n>  \t\tstruct strbuf *output,\n>  \t\tstruct expand_data *data,\n> @@ -671,8 +795,12 @@ static void dispatch_calls(struct batch_options *opt,\n>  \tif (!opt->buffer_output)\n>  \t\tdie(_(\"flush is only for --buffer mode\"));\n>  \n> -\tfor (int i = 0; i < nr; i++)\n> -\t\tcmd[i].fn(opt, cmd[i].line, output, data);\n> +\tfor (int i = 0; i < nr; i++) {\n> +\t\tif (!strcmp(cmd[i].name, \"remote-object-info\"))\n> +\t\t\tparse_remote_info(opt, cmd[i].line, output, data, NULL, &cmd[i]);\n\nIf we adapt `parse_remote_info()` to accept the command function we\ncould pass cmd->fn here instead.\n\n> +\t\telse\n> +\t\t\tcmd[i].fn(opt, cmd[i].line, output, data);\n> +\t}\n>  \n>  \tfflush(stdout);\n>  }\n> @@ -685,17 +813,6 @@ static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n>  \t*nr = 0;\n>  }\n>  \n> -\n> -static const struct parse_cmd {\n> -\tconst char *name;\n> -\tparse_cmd_fn_t fn;\n> -\tunsigned takes_args;\n> -} commands[] = {\n> -\t{ \"contents\", parse_cmd_contents, 1},\n> -\t{ \"info\", parse_cmd_info, 1},\n> -\t{ \"flush\", NULL, 0},\n> -};\n> -\n>  static void batch_objects_command(struct batch_options *opt,\n>  \t\t\t\t    struct strbuf *output,\n>  \t\t\t\t    struct expand_data *data)\n> @@ -740,11 +857,17 @@ static void batch_objects_command(struct batch_options *opt,\n>  \t\t\tdispatch_calls(opt, output, data, queued_cmd, nr);\n>  \t\t\tfree_cmds(queued_cmd, &nr);\n>  \t\t} else if (!opt->buffer_output) {\n> -\t\t\tcmd->fn(opt, p, output, data);\n> +\t\t\tif (!strcmp(cmd->name, \"remote-object-info\")) {\n> +\t\t\t\tchar *line = xstrdup_or_null(p);\n> +\t\t\t\tparse_remote_info(opt, line, output, data, cmd, NULL);\n\nSame here, if we adapt `parse_remote_info()` to accept the command \nfunction we could pass cmd->fn here instead.\n\n> +\t\t\t} else {\n> +\t\t\t\tcmd->fn(opt, p, output, data);\n> +\t\t\t}\n>  \t\t} else {\n>  \t\t\tALLOC_GROW(queued_cmd, nr + 1, alloc);\n>  \t\t\tcall.fn = cmd->fn;\n>  \t\t\tcall.line = xstrdup_or_null(p);\n> +\t\t\tcall.name = cmd->name;\n>  \t\t\tqueued_cmd[nr++] = call;\n>  \t\t}\n>  \t}\n> @@ -761,8 +884,6 @@ static void batch_objects_command(struct batch_options *opt,\n>  \tstrbuf_release(&input);\n>  }\n>  \n> -#define DEFAULT_FORMAT \"%(objectname) %(objecttype) %(objectsize)\"\n> -\n>  static int batch_objects(struct batch_options *opt)\n>  {\n>  \tstruct strbuf input = STRBUF_INIT;\n[snip]\n"},{"id":"498346","messageId":"87ikxfqbm9.fsf@iotcl.com","threadId":"61695","inReplyTo":"20240628190503.67389-5-eric.peijian@gmail.com","subject":"Re: [PATCH 4/6] transport: add client support for object-info","fromName":"Toon claes","fromEmail":"toon@iotcl.com","sentAt":"2024-07-09T07:15:58Z","receivedAt":"2024-07-09T07:16:11Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n> diff --git a/transport.c b/transport.c\n> index 83ddea8fbc..2847aa3f3c 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -436,11 +504,27 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \targs.server_options = transport->server_options;\n>  \targs.negotiation_tips = data->options.negotiation_tips;\n>  \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n> -\n> -\tif (!data->finished_handshake) {\n> -\t\tint i;\n> +\targs.object_info = transport->smart_options->object_info;\n> +\n> +\tif (transport->smart_options && transport->smart_options->object_info) {\n> +\t\tstruct ref *ref = object_info_refs;\n> +\n> +\t\tif (!fetch_object_info(transport, data->options.object_info_data))\n> +\t\t\tgoto cleanup;\n> +\t\targs.object_info_data = data->options.object_info_data;\n> +\t\targs.quiet = 1;\n> +\t\targs.no_progress = 1;\n> +\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> +\t\t\tstruct ref *temp_ref = xcalloc(1, sizeof (struct ref));\n> +\t\t\ttemp_ref->old_oid = *(transport->smart_options->object_info_oids->oid + i);\n\nAny reason why you're not using the subscript operator (square brackets)\nlike this:\n\n+\t\t\ttemp_ref->old_oid = transport->smart_options->object_info_oids->oid[i];\n\n> +\t\t\ttemp_ref->exact_oid = 1;\n> +\t\t\tref->next = temp_ref;\n> +\t\t\tref = ref->next;\n> +\t\t}\n> +\t\ttransport->remote_refs = object_info_refs->next;\n\nI find it a bit weird you're allocating object_info_refs, only to use it\nto point to the next. Can I suggest a little refactor:\n\n----8<-----8<----\ndiff --git a/transport.c b/transport.c\nindex 662faa004e..56cb3a1693 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -479,7 +479,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n        struct ref *refs = NULL;\n        struct fetch_pack_args args;\n        struct ref *refs_tmp = NULL;\n-       struct ref *object_info_refs = xcalloc(1, sizeof (struct ref));\n+       struct ref *object_info_refs = NULL;\n\n        memset(&args, 0, sizeof(args));\n        args.uploadpack = data->options.uploadpack;\n@@ -509,7 +509,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n        args.object_info = transport->smart_options->object_info;\n\n        if (transport->smart_options && transport->smart_options->object_info) {\n-               struct ref *ref = object_info_refs;\n+               struct ref *ref = object_info_refs = xcalloc(1, sizeof (struct ref));\n\n                if (!fetch_object_info(transport, data->options.object_info_data))\n                        goto cleanup;\n@@ -517,13 +517,12 @@ static int fetch_refs_via_pack(struct transport *transport,\n                args.quiet = 1;\n                args.no_progress = 1;\n                for (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n-                       struct ref *temp_ref = xcalloc(1, sizeof (struct ref));\n-                       temp_ref->old_oid = *(transport->smart_options->object_info_oids->oid + i);\n-                       temp_ref->exact_oid = 1;\n-                       ref->next = temp_ref;\n+                       ref->old_oid = transport->smart_options->object_info_oids->oid[i];\n+                       ref->exact_oid = 1;\n+                       ref->next = xcalloc(1, sizeof (struct ref));\n                        ref = ref->next;\n                }\n-               transport->remote_refs = object_info_refs->next;\n+               transport->remote_refs = object_info_refs;\n        } else if (!data->finished_handshake) {\n                int must_list_refs = 0;\n                for (int i = 0; i < nr_heads; i++) {\n@@ -565,7 +564,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n\n        data->finished_handshake = 0;\n        if (args.object_info) {\n-               struct ref *ref_cpy_reader = object_info_refs->next;\n+               struct ref *ref_cpy_reader = object_info_refs;\n                for (int i = 0; ref_cpy_reader; i++) {\n                        oid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &(*args.object_info_data)[i], OBJECT_INFO_LOOKUP_REPLACE);\n                        ref_cpy_reader = ref_cpy_reader->next;\n----8<-----8<----\n\nTo be honest, I'm not sure it works, because fetch_object_info() always\nseem to return a non-zero value. I'm not sure this is due to missing\ncode coverage, or a bug. I guess it's worth looking into.\n\n-- \nToon\n"},{"id":"498347","messageId":"87h6czqblk.fsf@iotcl.com","threadId":"61695","inReplyTo":"20240628190503.67389-7-eric.peijian@gmail.com","subject":"Re: [PATCH 6/6] cat-file: add remote-object-info to batch-command","fromName":"Toon claes","fromEmail":"toon@iotcl.com","sentAt":"2024-07-09T07:16:23Z","receivedAt":"2024-07-09T07:16:40Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 72a78cdc8c..34958a1747 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> ...\n> +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> +{\n> +\tint retval = 0;\n> +\tstruct remote *remote = NULL;\n> +\tstruct object_id oid;\n> +\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> +\tstatic struct transport *gtransport;\n> +\n> +\t/*\n> +\t * Change the format to \"%(objectname) %(objectsize)\" when\n> +\t * remote-object-info command is used. Once we start supporting objecttype\n> +\t * the default format should change to DEFAULT_FORMAT\n> +\t*/\n\nI believe this comment has become outdated, or got moved around\nincorrectly.\n\n> diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\n> new file mode 100755\n> index 0000000000..7a7bdfeb91\n> --- /dev/null\n> +++ b/t/t1017-cat-file-remote-object-info.sh\n> ...\n> +stop_git_daemon\n> +\n> +# Test --batch-command remote-object-info with 'http://' transport\n> +\n> +. \"$TEST_DIRECTORY\"/lib-httpd.sh\n> +start_httpd\n\nstart_httpd skips the remainder of the tests if it fails to start the\nhttpd server. That's why I see various other tests which have this at\nthe end:\n\n  # DO NOT add non-httpd-specific tests here, because the last part of this\n  # test script is only executed when httpd is available and enabled.\n\nSo I would suggest to add this comment as well, and move the file://\ntests above start_httpd.\n\n\n-- \nToon\n"},{"id":"498369","messageId":"xmqqttgyfrnb.fsf@gitster.g","threadId":"61695","inReplyTo":"87ikxfqbm9.fsf@iotcl.com","subject":"Re: [PATCH 4/6] transport: add client support for object-info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-09T16:37:28Z","receivedAt":"2024-07-09T16:37:33Z","isPatch":true,"body":"Toon claes <toon@iotcl.com> writes:\n\n>> +\t\t\ttemp_ref->old_oid = *(transport->smart_options->object_info_oids->oid + i);\n>\n> Any reason why you're not using the subscript operator (square brackets)\n> like this:\n>\n> +\t\t\ttemp_ref->old_oid = transport->smart_options->object_info_oids->oid[i];\n\nMuch nicer, but fold such overly long lines, please,\n\n\t\t\ttemp_ref->old_oid = transport->smart_options->\n\t\t\t\tobject_info_oids->oid[i];\n\nto make them readable.\n\n\n\n> ...\n> To be honest, I'm not sure it works, because fetch_object_info() always\n> seem to return a non-zero value. I'm not sure this is due to missing\n> code coverage, or a bug. I guess it's worth looking into.\n"},{"id":"498432","messageId":"CAOLa=ZRi2w_CXovpg3h1mfMsOiLWrpS8vL+NzS5o_NwhFdysTg@mail.gmail.com","threadId":"61695","inReplyTo":"CAN2LT1Ctwdij9-DujKeuzPX71mzEoMVbdrTkJ8bpRnX9NAZy9Q@mail.gmail.com","subject":"Re: [PATCH 1/6] fetch-pack: refactor packet writing","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-07-10T09:39:34Z","receivedAt":"2024-07-10T09:39:36Z","isPatch":true,"body":"Peijian Ju <eric.peijian@gmail.com> writes:\n[snip]\n\n>> Right, this commit in itself looks good. But I was curious why we need\n>> this, so I did a sneak peak into the following commits.\n>>\n>> To summarize, we want to call:\n>>    `write_command_and_capabilities(..., \"object-info\");`\n>> in the upcoming patches to get the object-info details from the server.\n>> But isn't this function too specific to the \"fetch\" command to be\n>> generalized to be for \"object-info\" too?\n>>\n>> Wouldn't it make sense to add a custom function for 'object-info' in\n>> 'connect.c'? Like how we currently have `get_remote_bundle_uri()` for\n>> 'bundle-uri' and `get_remote_refs` for 'ls-refs'?\n>\n> Thank you. I am reading through the old comments left by Taylor\n> at https://lore.kernel.org/git/YkOPyc9tUfe2Tozx@nand.local/\n>\n>   \" Makes obvious sense, and this was something that jumped out to me when I\n>   looked at the first and second versions of this patch. I'm glad that\n>   this is getting factored out.\"\n>\n>\n> It seems refactoring this into a more general function is on purpose.\n> It is encouraged to use this general function to request capability\n> rather than adding a custom function.\n> Taylor’s comment was 2 years ago, but I think refactoring this into a\n> more general function to\n> enforce DRY still makes sense.\n\nIt would make sense then to move the existing users to also use\n`write_command_and_capabilities` eventually. I guess this could be done\nin a follow up series.\n\nThen I would say `write_command_and_capabilities()` should be moved to\n`transport.c`, no?\n"},{"id":"498433","messageId":"CAOLa=ZSLoHe_xii2A_Phx1NzQLPvLwWUoFhJ2bOrihGvhD6rzg@mail.gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-5-eric.peijian@gmail.com","subject":"Re: [PATCH 4/6] transport: add client support for object-info","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-07-10T10:13:53Z","receivedAt":"2024-07-10T10:13:56Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n> From: Calvin Wan <calvinwan@google.com>\n>\n> Sometimes it is useful to get information about an object without having\n> to download it completely. The server logic has already been implemented\n> as “a2ba162cda (object-info: support for retrieving object info,\n\nNit: s/as/in\n\n> 2021-04-20)”.\n>\n> Add client functions to communicate with the server.\n>\n> The client currently supports requesting a list of object ids with\n> features 'size' and 'type' from a v2 server. If a server does not\n\nBut do we support type? I thought we only added support for 'size'.\n\n> advertise either of the requested features, then the client falls back\n> to making the request through 'fetch'.\n>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n>  fetch-pack.c       |  24 +++++++++++\n>  fetch-pack.h       |  10 +++++\n>  transport-helper.c |   8 +++-\n>  transport.c        | 102 ++++++++++++++++++++++++++++++++++++++++++---\n>  transport.h        |  11 +++++\n>  5 files changed, 148 insertions(+), 7 deletions(-)\n>\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index da0de9c537..d533cac1d8 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1345,6 +1345,27 @@ static void write_command_and_capabilities(struct strbuf *req_buf,\n>  \tpacket_buf_delim(req_buf);\n>  }\n>\n> +void send_object_info_request(int fd_out, struct object_info_args *args)\n> +{\n> +\tstruct strbuf req_buf = STRBUF_INIT;\n> +\n> +\twrite_command_and_capabilities(&req_buf, args->server_options, \"object-info\");\n> +\n> +\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n> +\t\tpacket_buf_write(&req_buf, \"size\");\n> +\n> +\tif (args->oids) {\n> +\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n> +\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n> +\t}\n> +\n> +\tpacket_buf_flush(&req_buf);\n> +\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n> +\t\tdie_errno(_(\"unable to write request to remote\"));\n> +\n> +\tstrbuf_release(&req_buf);\n> +}\n> +\n>  static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n>  \t\t\t      struct fetch_pack_args *args,\n>  \t\t\t      const struct ref *wants, struct oidset *common,\n> @@ -1682,6 +1703,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n>  \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n>  \t\targs->deepen = 1;\n>\n> +\tif (args->object_info)\n> +\t\tstate = FETCH_SEND_REQUEST;\n> +\n>  \twhile (state != FETCH_DONE) {\n>  \t\tswitch (state) {\n>  \t\tcase FETCH_CHECK_LOCAL:\n> diff --git a/fetch-pack.h b/fetch-pack.h\n> index 6775d26517..16e4dc0824 100644\n> --- a/fetch-pack.h\n> +++ b/fetch-pack.h\n> @@ -16,6 +16,7 @@ struct fetch_pack_args {\n>  \tconst struct string_list *deepen_not;\n>  \tstruct list_objects_filter_options filter_options;\n>  \tconst struct string_list *server_options;\n> +\tstruct object_info **object_info_data;\n>\n>  \t/*\n>  \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n> @@ -42,6 +43,7 @@ struct fetch_pack_args {\n>  \tunsigned reject_shallow_remote:1;\n>  \tunsigned deepen:1;\n>  \tunsigned refetch:1;\n> +\tunsigned object_info:1;\n>\n>  \t/*\n>  \t * Indicate that the remote of this request is a promisor remote. The\n> @@ -68,6 +70,12 @@ struct fetch_pack_args {\n>  \tunsigned connectivity_checked:1;\n>  };\n>\n> +struct object_info_args {\n> +\tstruct string_list *object_info_options;\n> +\tconst struct string_list *server_options;\n> +\tstruct oid_array *oids;\n> +};\n> +\n>  /*\n>   * sought represents remote references that should be updated from.\n>   * On return, the names that were found on the remote will have been\n> @@ -101,4 +109,6 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n>   */\n>  int report_unmatched_refs(struct ref **sought, int nr_sought);\n>\n> +void send_object_info_request(int fd_out, struct object_info_args *args);\n> +\n>  #endif\n> diff --git a/transport-helper.c b/transport-helper.c\n> index 9820947ab2..670d1e7068 100644\n> --- a/transport-helper.c\n> +++ b/transport-helper.c\n> @@ -697,13 +697,17 @@ static int fetch_refs(struct transport *transport,\n>\n>  \t/*\n>  \t * If we reach here, then the server, the client, and/or the transport\n> -\t * helper does not support protocol v2. --negotiate-only requires\n> -\t * protocol v2.\n> +\t * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n> +\t * require protocol v2.\n>  \t */\n>  \tif (data->transport_options.acked_commits) {\n>  \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n>  \t\treturn -1;\n>  \t}\n> +\tif (transport->smart_options->object_info) {\n> +\t\t// fail the command explicitly to avoid further commands input\n> +\t\tdie(_(\"remote-object-info requires protocol v2\"));\n> +\t}\n>\n>  \tif (!data->get_refs_list_called)\n>  \t\tget_refs_list_using_list(transport, 0);\n> diff --git a/transport.c b/transport.c\n> index 83ddea8fbc..2847aa3f3c 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -363,6 +363,73 @@ static struct ref *handshake(struct transport *transport, int for_push,\n>  \treturn refs;\n>  }\n>\n> +static int fetch_object_info(struct transport *transport, struct object_info **object_info_data)\n> +{\n> +\tint size_index = -1;\n> +\tstruct git_transport_data *data = transport->data;\n> +\tstruct object_info_args args;\n> +\tstruct packet_reader reader;\n> +\n> +\tmemset(&args, 0, sizeof(args));\n\nNit: we could `struct object_info_args args = { 0 };` above instead.\n\n> +\targs.server_options = transport->server_options;\n> +\targs.object_info_options = transport->smart_options->object_info_options;\n> +\targs.oids = transport->smart_options->object_info_oids;\n> +\n> +\tconnect_setup(transport, 0);\n> +\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n> +\t\t\tPACKET_READ_CHOMP_NEWLINE |\n> +\t\t\tPACKET_READ_GENTLE_ON_EOF |\n> +\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n> +\tdata->version = discover_version(&reader);\n> +\n> +\ttransport->hash_algo = reader.hash_algo;\n> +\n> +\tswitch (data->version) {\n> +\tcase protocol_v2:\n> +\t\tif (!server_supports_v2(\"object-info\"))\n> +\t\t\treturn -1;\n> +\t\tif (unsorted_string_list_has_string(args.object_info_options, \"size\")\n> +\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0)) {\n> +\t\t\treturn -1;\n> +\t\t}\n> +\t\tsend_object_info_request(data->fd[1], &args);\n> +\t\tbreak;\n> +\tcase protocol_v1:\n> +\tcase protocol_v0:\n> +\t\tdie(_(\"wrong protocol version. expected v2\"));\n> +\tcase protocol_unknown_version:\n> +\t\tBUG(\"unknown protocol version\");\n> +\t}\n> +\n> +\tfor (size_t i = 0; i < args.object_info_options->nr; i++) {\n> +\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL) {\n> +\t\t\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n> +\t\t\treturn -1;\n> +\t\t}\n> +\t\tif (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n> +\t\t\tif (!strcmp(reader.line, \"size\"))\n> +\t\t\t\tsize_index = i;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tfor (size_t i = 0; packet_reader_read(&reader) == PACKET_READ_NORMAL && i < args.oids->nr; i++){\n> +\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n\nWe need to also call `string_list_clear()` at the end of this block.\n\n> +\n> +\t\tstring_list_split(&object_info_values, reader.line, ' ', -1);\n> +\t\tif (0 <= size_index) {\n> +\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n> +\t\t\t\tdie(\"object-info: not our ref %s\",\n> +\t\t\t\t\tobject_info_values.items[0].string);\n> +\t\t\t*(*object_info_data)[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n\nPerhaps `*object_info_data[i]->sizep =\nstrtoul(object_info_values.items[1 + size_index].string, NULL, 10);`?\n\nSo, this is allocated in 'cat-file' and set here? Wouldn't it be nicer\nto also do the alloc here?\n\n> +\t\t}\n> +\t}\n> +\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n> +\n> +\treturn 0;\n> +}\n> +\n>  static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n>  \t\t\t\t\tstruct transport_ls_refs_options *options)\n>  {\n> @@ -410,6 +477,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \tstruct ref *refs = NULL;\n>  \tstruct fetch_pack_args args;\n>  \tstruct ref *refs_tmp = NULL;\n> +\tstruct ref *object_info_refs = xcalloc(1, sizeof (struct ref));\n>\n>  \tmemset(&args, 0, sizeof(args));\n>  \targs.uploadpack = data->options.uploadpack;\n> @@ -436,11 +504,27 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \targs.server_options = transport->server_options;\n>  \targs.negotiation_tips = data->options.negotiation_tips;\n>  \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n> -\n> -\tif (!data->finished_handshake) {\n> -\t\tint i;\n> +\targs.object_info = transport->smart_options->object_info;\n> +\n> +\tif (transport->smart_options && transport->smart_options->object_info) {\n> +\t\tstruct ref *ref = object_info_refs;\n> +\n> +\t\tif (!fetch_object_info(transport, data->options.object_info_data))\n> +\t\t\tgoto cleanup;\n> +\t\targs.object_info_data = data->options.object_info_data;\n> +\t\targs.quiet = 1;\n> +\t\targs.no_progress = 1;\n> +\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> +\t\t\tstruct ref *temp_ref = xcalloc(1, sizeof (struct ref));\n> +\t\t\ttemp_ref->old_oid = *(transport->smart_options->object_info_oids->oid + i);\n> +\t\t\ttemp_ref->exact_oid = 1;\n> +\t\t\tref->next = temp_ref;\n> +\t\t\tref = ref->next;\n> +\t\t}\n> +\t\ttransport->remote_refs = object_info_refs->next;\n> +\t} else if (!data->finished_handshake) {\n>  \t\tint must_list_refs = 0;\n> -\t\tfor (i = 0; i < nr_heads; i++) {\n> +\t\tfor (int i = 0; i < nr_heads; i++) {\n>  \t\t\tif (!to_fetch[i]->exact_oid) {\n>  \t\t\t\tmust_list_refs = 1;\n>  \t\t\t\tbreak;\n> @@ -478,11 +562,18 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \t\t\t  &transport->pack_lockfiles, data->version);\n>\n>  \tdata->finished_handshake = 0;\n> +\tif (args.object_info) {\n> +\t\tstruct ref *ref_cpy_reader = object_info_refs->next;\n> +\t\tfor (int i = 0; ref_cpy_reader; i++) {\n> +\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &(*args.object_info_data)[i], OBJECT_INFO_LOOKUP_REPLACE);\n> +\t\t\tref_cpy_reader = ref_cpy_reader->next;\n> +\t\t}\n> +\t}\n>  \tdata->options.self_contained_and_connected =\n>  \t\targs.self_contained_and_connected;\n>  \tdata->options.connectivity_checked = args.connectivity_checked;\n>\n> -\tif (!refs)\n> +\tif (!refs && !args.object_info)\n>  \t\tret = -1;\n>  \tif (report_unmatched_refs(to_fetch, nr_heads))\n>  \t\tret = -1;\n> @@ -498,6 +589,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \tfree_refs(refs_tmp);\n>  \tfree_refs(refs);\n>  \tlist_objects_filter_release(&args.filter_options);\n> +\tfree_refs(object_info_refs);\n\nShouldn't we loop through `object_info_refs->next` and free all of them ?\n\n>  \treturn ret;\n>  }\n>\n> diff --git a/transport.h b/transport.h\n> index 6393cd9823..5a3cda1860 100644\n> --- a/transport.h\n> +++ b/transport.h\n> @@ -5,6 +5,7 @@\n>  #include \"remote.h\"\n>  #include \"list-objects-filter-options.h\"\n>  #include \"string-list.h\"\n> +#include \"object-store.h\"\n>\n>  struct git_transport_options {\n>  \tunsigned thin : 1;\n> @@ -30,6 +31,12 @@ struct git_transport_options {\n>  \t */\n>  \tunsigned connectivity_checked:1;\n>\n> +\t/*\n> +\t * Transport will attempt to pull only object-info. Fallbacks\n> +\t * to pulling entire object if object-info is not supported.\n> +\t */\n> +\tunsigned object_info : 1;\n> +\n>  \tint depth;\n>  \tconst char *deepen_since;\n>  \tconst struct string_list *deepen_not;\n> @@ -53,6 +60,10 @@ struct git_transport_options {\n>  \t * common commits to this oidset instead of fetching any packfiles.\n>  \t */\n>  \tstruct oidset *acked_commits;\n> +\n> +\tstruct oid_array *object_info_oids;\n> +\tstruct object_info **object_info_data;\n> +\tstruct string_list *object_info_options;\n>  };\n>\n>  enum transport_family {\n> --\n> 2.45.2\n\nI wondering if we can add tests at this stage.\n"},{"id":"498434","messageId":"CAOLa=ZRH7=Uj77Wd4WQW=KR8ppKw6nXZRH7pNZpHBPz4HhX8MQ@mail.gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-6-eric.peijian@gmail.com","subject":"Re: [PATCH 5/6] cat-file: add declaration of variable i inside its for loop","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-07-10T10:16:06Z","receivedAt":"2024-07-10T10:16:08Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n> Some code declares variable i and only uses it\n> in a for loop, not in any other logic outside the loop.\n>\n> Change the declaration of i to be inside the for loop for readability.\n>\n\nIf we're doing this anyways, we could replace the 'int' with 'size_t'\ntoo.\n\n[snip]\n"},{"id":"498438","messageId":"CAOLa=ZRL8ByTi3+8ej1Ugh75CU5Rx2grGpNx5ESKOJCrEYg2XA@mail.gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-7-eric.peijian@gmail.com","subject":"Re: [PATCH 6/6] cat-file: add remote-object-info to batch-command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-07-10T12:08:38Z","receivedAt":"2024-07-10T12:08:40Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n> From: Calvin Wan <calvinwan@google.com>\n>\n> Since the `info` command in cat-file --batch-command prints object info\n> for a given object, it is natural to add another command in cat-file\n> --batch-command to print object info for a given object from a remote.\n> Add `remote-object-info` to cat-file --batch-command.\n>\n> While `info` takes object ids one at a time, this creates overhead when\n> making requests to a server so `remote-object-info` instead can take\n> multiple object ids at once.\n>\n> cat-file --batch-command is generally implemented in the following\n> manner:\n>\n>  - Receive and parse input from user\n\nSo this refers input delimited by newline or '\\0'.\n\n>  - Call respective function attached to command\n>  - Set batch mode state, get object info, print object info\n>\n\nDoesn't the batch mode get set before the input parsing begins?\n\n> In --buffer mode, this changes to:\n>\n>  - Receive and parse input from user\n>  - Store respective function attached to command in a queue\n>  - After flush, loop through commands in queue\n>     - Call respective function attached to command\n>     - Set batch mode state, get object info, print object info\n>\n> Notice how the getting and printing of object info is accomplished one\n> at a time. As described above, this creates a problem for making\n> requests to a server. Therefore, `remote-object-info` is implemented in\n> the following manner:\n>\n>  - Receive and parse input from user\n>  If command is `remote-object-info`:\n>     - Get object info from remote\n>     - Loop through object info\n>         - Call respective function attached to `info`\n>         - Set batch mode state, use passed in object info, print object\n>           info\n>  Else:\n>     - Call respective function attached to command\n>     - Parse input, get object info, print object info\n>\n\nSo this is because we want 'remote-object-info' to also use\n'parse_cmd_info' similar to 'info'. But I'm not understanding why,\nespecially since 'parse_cmd_info' calls 'batch_one_object', and we skip\nmost of that code for 'remote-object-info'.\n\nWouldn't it be cleaner to just define our own 'batch_remote_object' and\ncreate 'parse_cmd_remote_info' ?\n\n> And finally for --buffer mode `remote-object-info`:\n>  - Receive and parse input from user\n>  - Store respective function attached to command in a queue\n>  - After flush, loop through commands in queue:\n>     If command is `remote-object-info`:\n>         - Get object info from remote\n>         - Loop through object info\n>             - Call respective function attached to `info`\n>             - Set batch mode state, use passed in object info, print\n>               object info\n>     Else:\n>         - Call respective function attached to command\n>         - Set batch mode state, get object info, print object info\n>\n> To summarize, `remote-object-info` gets object info from the remote and\n> then generates multiple `info` commands with the object info passed in.\n>\n> In order for remote-object-info to avoid remote communication overhead\n> in the non-buffer mode, the objects are passed in as such:\n>\n> remote-object-info <remote> <oid> <oid> ... <oid>\n>\n> rather than\n>\n> remote-object-info <remote> <oid>\n> remote-object-info <remote> <oid>\n> ...\n> remote-object-info <remote> <oid>\n>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n>  Documentation/git-cat-file.txt         |  22 +-\n>  builtin/cat-file.c                     | 231 ++++++++++----\n>  object-file.c                          |  11 +\n>  object-store-ll.h                      |   3 +\n>  t/t1017-cat-file-remote-object-info.sh | 412 +++++++++++++++++++++++++\n>  5 files changed, 620 insertions(+), 59 deletions(-)\n>  create mode 100755 t/t1017-cat-file-remote-object-info.sh\n>\n> diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\n> index bd95a6c10a..ab0647bb39 100644\n> --- a/Documentation/git-cat-file.txt\n> +++ b/Documentation/git-cat-file.txt\n> @@ -149,6 +149,12 @@ info <object>::\n>  \tPrint object info for object reference `<object>`. This corresponds to the\n>  \toutput of `--batch-check`.\n>\n> +remote-object-info <remote> <object>...::\n> +\tPrint object info for object references `<object>` at specified <remote> without\n> +\tdownloading object from remote.\n> +\tError when no object references is provided.\n> +\tThis command may be combined with `--buffer`.\n> +\n>  flush::\n>  \tUsed with `--buffer` to execute all preceding commands that were issued\n>  \tsince the beginning or since the last flush was issued. When `--buffer`\n> @@ -290,7 +296,8 @@ newline. The available atoms are:\n>  \tThe full hex representation of the object name.\n>\n>  `objecttype`::\n> -\tThe type of the object (the same as `cat-file -t` reports).\n> +\tThe type of the object (the same as `cat-file -t` reports). See\n> +\t`CAVEATS` below. Not supported by `remote-object-info`.\n>\n>  `objectsize`::\n>  \tThe size, in bytes, of the object (the same as `cat-file -s`\n> @@ -298,13 +305,14 @@ newline. The available atoms are:\n>\n>  `objectsize:disk`::\n>  \tThe size, in bytes, that the object takes up on disk. See the\n> -\tnote about on-disk sizes in the `CAVEATS` section below.\n> +\tnote about on-disk sizes in the `CAVEATS` section below. Not\n> +\tsupported by `remote-object-info`.\n>\n>  `deltabase`::\n>  \tIf the object is stored as a delta on-disk, this expands to the\n>  \tfull hex representation of the delta base object name.\n>  \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n> -\tbelow.\n> +\tbelow. Not supported by `remote-object-info`.\n>\n>  `rest`::\n>  \tIf this atom is used in the output string, input lines are split\n> @@ -314,7 +322,9 @@ newline. The available atoms are:\n>  \tline) are output in place of the `%(rest)` atom.\n>\n>  If no format is specified, the default format is `%(objectname)\n> -%(objecttype) %(objectsize)`.\n> +%(objecttype) %(objectsize)`, except remote-object-info command who uses\n> +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> +When \"%(objecttype)\" is supported, default format should be unified.\n>\n>  If `--batch` is specified, or if `--batch-command` is used with the `contents`\n>  command, the object information is followed by the object contents (consisting\n> @@ -396,6 +406,10 @@ scripting purposes.\n>  CAVEATS\n>  -------\n>\n> +Note that since objecttype, objectsize:disk and deltabase are currently not supported by the\n> +remote-object-info, git will error and exit when they are in the format string.\n> +\n> +\n>  Note that the sizes of objects on disk are reported accurately, but care\n>  should be taken in drawing conclusions about which refs or objects are\n>  responsible for disk usage. The size of a packed non-delta object may be\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 72a78cdc8c..34958a1747 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -24,6 +24,9 @@\n>  #include \"promisor-remote.h\"\n>  #include \"mailmap.h\"\n>  #include \"write-or-die.h\"\n> +#include \"alias.h\"\n> +#include \"remote.h\"\n> +#include \"transport.h\"\n>\n>  enum batch_mode {\n>  \tBATCH_MODE_CONTENTS,\n> @@ -42,9 +45,14 @@ struct batch_options {\n>  \tchar input_delim;\n>  \tchar output_delim;\n>  \tconst char *format;\n> +\tint use_remote_info;\n>  };\n>\n> +#define DEFAULT_FORMAT \"%(objectname) %(objecttype) %(objectsize)\"\n> +\n>  static const char *force_path;\n> +static struct object_info *remote_object_info;\n> +static struct oid_array object_info_oids = OID_ARRAY_INIT;\n>\n>  static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n>  static int use_mailmap;\n> @@ -508,7 +516,6 @@ static void batch_object_write(const char *obj_name,\n>  \t}\n>\n>  \tbatch_write(opt, scratch->buf, scratch->len);\n> -\n\nNit: why remove this?\n\n>  \tif (opt->batch_mode == BATCH_MODE_CONTENTS) {\n>  \t\tprint_object_or_die(opt, data);\n>  \t\tbatch_write(opt, &opt->output_delim, 1);\n> @@ -526,51 +533,118 @@ static void batch_one_object(const char *obj_name,\n>  \t\t(opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0);\n>  \tenum get_oid_result result;\n>\n> -\tresult = get_oid_with_context(the_repository, obj_name,\n> -\t\t\t\t      flags, &data->oid, &ctx);\n> -\tif (result != FOUND) {\n> -\t\tswitch (result) {\n> -\t\tcase MISSING_OBJECT:\n> -\t\t\tprintf(\"%s missing%c\", obj_name, opt->output_delim);\n> -\t\t\tbreak;\n> -\t\tcase SHORT_NAME_AMBIGUOUS:\n> -\t\t\tprintf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n> -\t\t\tbreak;\n> -\t\tcase DANGLING_SYMLINK:\n> -\t\t\tprintf(\"dangling %\"PRIuMAX\"%c%s%c\",\n> -\t\t\t       (uintmax_t)strlen(obj_name),\n> -\t\t\t       opt->output_delim, obj_name, opt->output_delim);\n> -\t\t\tbreak;\n> -\t\tcase SYMLINK_LOOP:\n> -\t\t\tprintf(\"loop %\"PRIuMAX\"%c%s%c\",\n> -\t\t\t       (uintmax_t)strlen(obj_name),\n> -\t\t\t       opt->output_delim, obj_name, opt->output_delim);\n> -\t\t\tbreak;\n> -\t\tcase NOT_DIR:\n> -\t\t\tprintf(\"notdir %\"PRIuMAX\"%c%s%c\",\n> -\t\t\t       (uintmax_t)strlen(obj_name),\n> -\t\t\t       opt->output_delim, obj_name, opt->output_delim);\n> -\t\t\tbreak;\n> -\t\tdefault:\n> -\t\t\tBUG(\"unknown get_sha1_with_context result %d\\n\",\n> -\t\t\t       result);\n> -\t\t\tbreak;\n> +\tif (!opt->use_remote_info) {\n> +\t\tresult = get_oid_with_context(the_repository, obj_name,\n> +\t\t\t\t\t\tflags, &data->oid, &ctx);\n> +\t\tif (result != FOUND) {\n> +\t\t\tswitch (result) {\n> +\t\t\tcase MISSING_OBJECT:\n> +\t\t\t\tprintf(\"%s missing%c\", obj_name, opt->output_delim);\n> +\t\t\t\tbreak;\n> +\t\t\tcase SHORT_NAME_AMBIGUOUS:\n> +\t\t\t\tprintf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n> +\t\t\t\tbreak;\n> +\t\t\tcase DANGLING_SYMLINK:\n> +\t\t\t\tprintf(\"dangling %\"PRIuMAX\"%c%s%c\",\n> +\t\t\t\t\t(uintmax_t)strlen(obj_name),\n> +\t\t\t\t\topt->output_delim, obj_name, opt->output_delim);\n> +\t\t\t\tbreak;\n> +\t\t\tcase SYMLINK_LOOP:\n> +\t\t\t\tprintf(\"loop %\"PRIuMAX\"%c%s%c\",\n> +\t\t\t\t\t(uintmax_t)strlen(obj_name),\n> +\t\t\t\t\topt->output_delim, obj_name, opt->output_delim);\n> +\t\t\t\tbreak;\n> +\t\t\tcase NOT_DIR:\n> +\t\t\t\tprintf(\"notdir %\"PRIuMAX\"%c%s%c\",\n> +\t\t\t\t\t(uintmax_t)strlen(obj_name),\n> +\t\t\t\t\topt->output_delim, obj_name, opt->output_delim);\n> +\t\t\t\tbreak;\n> +\t\t\tdefault:\n> +\t\t\t\tBUG(\"unknown get_sha1_with_context result %d\\n\",\n> +\t\t\t\t\tresult);\n> +\t\t\t\tbreak;\n> +\t\t\t}\n> +\t\t\tfflush(stdout);\n> +\t\t\treturn;\n>  \t\t}\n> -\t\tfflush(stdout);\n> -\t\treturn;\n> -\t}\n>\n> -\tif (ctx.mode == 0) {\n> -\t\tprintf(\"symlink %\"PRIuMAX\"%c%s%c\",\n> -\t\t       (uintmax_t)ctx.symlink_path.len,\n> -\t\t       opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n> -\t\tfflush(stdout);\n> -\t\treturn;\n> +\t\tif (ctx.mode == 0) {\n> +\t\t\tprintf(\"symlink %\"PRIuMAX\"%c%s%c\",\n> +\t\t\t\t(uintmax_t)ctx.symlink_path.len,\n> +\t\t\t\topt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n> +\t\t\tfflush(stdout);\n> +\t\t\treturn;\n> +\t\t}\n>  \t}\n>\n>  \tbatch_object_write(obj_name, scratch, opt, data, NULL, 0);\n>  }\n>\n> +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> +{\n> +\tint retval = 0;\n> +\tstruct remote *remote = NULL;\n\nWe need to call `remote_clear()` on this at the end.\n\n> +\tstruct object_id oid;\n> +\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n\nThis needs to be cleared.\n\n> +\tstatic struct transport *gtransport;\n\nShouldn't we call `transport_disconnect(transport);`?\n\n> +\t/*\n> +\t * Change the format to \"%(objectname) %(objectsize)\" when\n> +\t * remote-object-info command is used. Once we start supporting objecttype\n> +\t * the default format should change to DEFAULT_FORMAT\n> +\t*/\n> +\tif (!opt->format) {\n> +\t\topt->format = \"%(objectname) %(objectsize)\";\n> +\t}\n> +\n> +\tremote = remote_get(argv[0]);\n> +\tif (!remote)\n> +\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n> +\toid_array_clear(&object_info_oids);\n> +\tfor (size_t i = 1; i < argc; i++) {\n> +\t\tif (get_oid_hex(argv[i], &oid))\n> +\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n> +\t\toid_array_append(&object_info_oids, &oid);\n> +\t}\n> +\n> +\tgtransport = transport_get(remote, NULL);\n> +\tif (gtransport->smart_options) {\n> +\t\tint include_size = 0;\n> +\n> +\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> +\t\tgtransport->smart_options->object_info = 1;\n> +\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n> +\t\t/*\n> +\t\t * 'size' is the only option currently supported.\n> +\t\t * Other options that are passed in the format will exit with error.\n> +\t\t */\n> +\t\tif (strstr(opt->format, \"%(objectsize)\")) {\n> +\t\t\tstring_list_append(&object_info_options, \"size\");\n> +\t\t\tinclude_size = 1;\n> +\t\t}\n> +\t\tif (strstr(opt->format, \"%(objecttype)\")) {\n> +\t\t\tdie(_(\"objecttype is currently not supported with remote-object-info\"));\n> +\t\t}\n> +\t\tif (strstr(opt->format, \"%(objectsize:disk)\"))\n> +\t\t\tdie(_(\"objectsize:disk is currently not supported with remote-object-info\"));\n> +\t\tif (strstr(opt->format, \"%(deltabase)\"))\n> +\t\t\tdie(_(\"deltabase is currently not supported with remote-object-info\"));\n>\n\nThis whole block could be replaced by an else..\n\nif (strstr(opt->format, \"%(objectsize)\")) {\n\tstring_list_append(&object_info_options, \"size\");\n\tinclude_size = 1;\n} else {\n\tdie(_(\"%s is currently not supported with remote-object-info\", opt->format));\n}\n\n> +\t\tif (object_info_options.nr > 0) {\n> +\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n> +\t\t\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n> +\t\t\t\tif (include_size)\n> +\t\t\t\t\tremote_object_info[i].sizep = xcalloc(1, sizeof(long));\n> +\t\t\t}\n> +\t\t\tgtransport->smart_options->object_info_data = &remote_object_info;\n> +\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n> +\t\t}\n> +\t} else {\n> +\t\tretval = -1;\n> +\t}\n> +\n> +\treturn retval;\n> +}\n> +\n>  struct object_cb_data {\n>  \tstruct batch_options *opt;\n>  \tstruct expand_data *expand;\n> @@ -642,6 +716,7 @@ typedef void (*parse_cmd_fn_t)(struct batch_options *, const char *,\n>  struct queued_cmd {\n>  \tparse_cmd_fn_t fn;\n>  \tchar *line;\n> +\tconst char *name;\n>  };\n>\n>  static void parse_cmd_contents(struct batch_options *opt,\n> @@ -662,6 +737,55 @@ static void parse_cmd_info(struct batch_options *opt,\n>  \tbatch_one_object(line, output, opt, data);\n>  }\n>\n> +static const struct parse_cmd {\n> +\tconst char *name;\n> +\tparse_cmd_fn_t fn;\n> +\tunsigned takes_args;\n> +} commands[] = {\n> +\t{ \"contents\", parse_cmd_contents, 1 },\n> +\t{ \"info\", parse_cmd_info, 1 },\n> +\t{ \"remote-object-info\", parse_cmd_info, 1 },\n> +\t{ \"flush\", NULL, 0 },\n> +};\n> +\n> +static void parse_remote_info(struct batch_options *opt,\n> +\t\t\t   char *line,\n> +\t\t\t   struct strbuf *output,\n> +\t\t\t   struct expand_data *data,\n> +\t\t\t   const struct parse_cmd *p_cmd,\n> +\t\t\t   struct queued_cmd *q_cmd)\n> +{\n> +\tint count;\n> +\tconst char **argv;\n> +\n> +\tcount = split_cmdline(line, &argv);\n> +\tif (get_remote_info(opt, count, argv))\n> +\t\tgoto cleanup;\n> +\topt->use_remote_info = 1;\n> +\tdata->skip_object_info = 1;\n> +\tdata->mark_query = 0;\n> +\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n> +\t\tif (remote_object_info[i].sizep)\n> +\t\t\tdata->size = *remote_object_info[i].sizep;\n> +\t\tif (remote_object_info[i].typep)\n> +\t\t\tdata->type = *remote_object_info[i].typep;\n> +\n\nWe don't even set the type, so this shouldn't ever be possible right?\n\n> +\t\tdata->oid = object_info_oids.oid[i];\n> +\t\tif (p_cmd)\n> +\t\t\tp_cmd->fn(opt, argv[i+1], output, data);\n> +\t\telse\n> +\t\t\tq_cmd->fn(opt, argv[i+1], output, data);\n> +\t}\n> +\topt->use_remote_info = 0;\n> +\tdata->skip_object_info = 0;\n> +\tdata->mark_query = 1;\n> +\n> +cleanup:\n> +\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n> +\t\tfree_object_info_contents(&remote_object_info[i]);\n> +\tfree(remote_object_info);\n\nargv needs to free'd too\n\n> +}\n> +\n>  static void dispatch_calls(struct batch_options *opt,\n>  \t\tstruct strbuf *output,\n>  \t\tstruct expand_data *data,\n> @@ -671,8 +795,12 @@ static void dispatch_calls(struct batch_options *opt,\n>  \tif (!opt->buffer_output)\n>  \t\tdie(_(\"flush is only for --buffer mode\"));\n>\n> -\tfor (int i = 0; i < nr; i++)\n> -\t\tcmd[i].fn(opt, cmd[i].line, output, data);\n> +\tfor (int i = 0; i < nr; i++) {\n> +\t\tif (!strcmp(cmd[i].name, \"remote-object-info\"))\n> +\t\t\tparse_remote_info(opt, cmd[i].line, output, data, NULL, &cmd[i]);\n> +\t\telse\n> +\t\t\tcmd[i].fn(opt, cmd[i].line, output, data);\n> +\t}\n>\n>  \tfflush(stdout);\n>  }\n> @@ -685,17 +813,6 @@ static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n>  \t*nr = 0;\n>  }\n>\n> -\n> -static const struct parse_cmd {\n> -\tconst char *name;\n> -\tparse_cmd_fn_t fn;\n> -\tunsigned takes_args;\n> -} commands[] = {\n> -\t{ \"contents\", parse_cmd_contents, 1},\n> -\t{ \"info\", parse_cmd_info, 1},\n> -\t{ \"flush\", NULL, 0},\n> -};\n> -\n>  static void batch_objects_command(struct batch_options *opt,\n>  \t\t\t\t    struct strbuf *output,\n>  \t\t\t\t    struct expand_data *data)\n> @@ -740,11 +857,17 @@ static void batch_objects_command(struct batch_options *opt,\n>  \t\t\tdispatch_calls(opt, output, data, queued_cmd, nr);\n>  \t\t\tfree_cmds(queued_cmd, &nr);\n>  \t\t} else if (!opt->buffer_output) {\n> -\t\t\tcmd->fn(opt, p, output, data);\n> +\t\t\tif (!strcmp(cmd->name, \"remote-object-info\")) {\n> +\t\t\t\tchar *line = xstrdup_or_null(p);\n\nThis needs to be free'd.\n\n> +\t\t\t\tparse_remote_info(opt, line, output, data, cmd, NULL);\n\n\n\n> +\t\t\t} else {\n> +\t\t\t\tcmd->fn(opt, p, output, data);\n> +\t\t\t}\n>  \t\t} else {\n>  \t\t\tALLOC_GROW(queued_cmd, nr + 1, alloc);\n>  \t\t\tcall.fn = cmd->fn;\n>  \t\t\tcall.line = xstrdup_or_null(p);\n> +\t\t\tcall.name = cmd->name;\n>  \t\t\tqueued_cmd[nr++] = call;\n>  \t\t}\n>  \t}\n> @@ -761,8 +884,6 @@ static void batch_objects_command(struct batch_options *opt,\n>  \tstrbuf_release(&input);\n>  }\n>\n> -#define DEFAULT_FORMAT \"%(objectname) %(objecttype) %(objectsize)\"\n> -\n>  static int batch_objects(struct batch_options *opt)\n>  {\n>  \tstruct strbuf input = STRBUF_INIT;\n> diff --git a/object-file.c b/object-file.c\n> index d3cf4b8b2e..6aaa167942 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -2988,3 +2988,14 @@ int read_loose_object(const char *path,\n>  \t\tmunmap(map, mapsize);\n>  \treturn ret;\n>  }\n> +\n> +void free_object_info_contents(struct object_info *object_info)\n> +{\n> +\tif (!object_info)\n> +\t\treturn;\n> +\tfree(object_info->typep);\n> +\tfree(object_info->sizep);\n> +\tfree(object_info->disk_sizep);\n> +\tfree(object_info->delta_base_oid);\n> +\tfree(object_info->type_name);\n> +}\n> diff --git a/object-store-ll.h b/object-store-ll.h\n> index c5f2bb2fc2..333e19cd1e 100644\n> --- a/object-store-ll.h\n> +++ b/object-store-ll.h\n> @@ -533,4 +533,7 @@ int for_each_object_in_pack(struct packed_git *p,\n>  int for_each_packed_object(each_packed_object_fn, void *,\n>  \t\t\t   enum for_each_object_flags flags);\n>\n> +/* Free pointers inside of object_info, but not object_info itself */\n> +void free_object_info_contents(struct object_info *object_info);\n> +\n>  #endif /* OBJECT_STORE_LL_H */\n> diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\n> new file mode 100755\n> index 0000000000..7a7bdfeb91\n> --- /dev/null\n> +++ b/t/t1017-cat-file-remote-object-info.sh\n> @@ -0,0 +1,412 @@\n> +#!/bin/sh\n> +\n> +test_description='git cat-file --batch-command with remote-object-info command'\n> +\n> +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> +\n> +. ./test-lib.sh\n> +\n> +echo_without_newline () {\n> +    printf '%s' \"$*\"\n> +}\n> +\n> +strlen () {\n> +    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> +}\n> +\n> +hello_content=\"Hello World\"\n> +hello_size=$(strlen \"$hello_content\")\n> +hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n> +\n> +tree_size=$(($(test_oid rawsz) + 13))\n> +\n> +commit_message=\"Initial commit\"\n> +commit_size=$(($(test_oid hexsz) + 137))\n>\n\nWhy 13 and 137?\n\n> +\n> +tag_header_without_oid=\"type blob\n> +tag hellotag\n> +tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n> +tag_header_without_timestamp=\"object $hello_oid\n> +$tag_header_without_oid\"\n> +tag_description=\"This is a tag\"\n> +tag_content=\"$tag_header_without_timestamp 0 +0000\n> +\n> +$tag_description\"\n> +\n> +tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n> +tag_size=$(strlen \"$tag_content\")\n> +\n> +# This section tests --batch-command with remote-object-info command\n> +# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n> +# the filters are set to \"%(objectname) %(objectsize)\".\n> +# Tests with the default filter are used to test the fallback to 'fetch' command\n> +\n> +\n> +# Test --batch-command remote-object-info with 'git://' transport\n> +\n> +. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n> +start_git_daemon --export-all --enable=receive-pack\n> +daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n> +\n> +test_expect_success 'create repo to be served by git-daemon' '\n> +\tgit init \"$daemon_parent\" &&\n> +\n> +\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n> +\tgit -C \"$daemon_parent\" update-index --add hello &&\n> +\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n> +'\n> +\n> +set_transport_variables () {\n> +\thello_sha1=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n> +\ttree_sha1=$(git -C \"$1\" write-tree)\n> +\tcommit_sha1=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_sha1)\n> +\ttag_sha1=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n> +\ttag_size=$(strlen \"$tag_content\")\n> +}\n> +\n> +\n\nextra newline here\n\n> +test_expect_success 'batch-command remote-object-info git://' '\n> +\t(\n> +\t\tset_transport_variables \"$daemon_parent\" &&\n> +\t\tcd \"$daemon_parent\" &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> +\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1\n> +\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_sha1\n> +\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1\n> +\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_sha1\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n> +\t(\n> +\t\tset_transport_variables \"$daemon_parent\" &&\n> +\t\tcd \"$daemon_parent\" &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> +\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'batch-command remote-object-info http:// default filter' '\n> +\t(\n> +\t\tset_transport_variables \"$daemon_parent\" &&\n> +\t\tcd \"$daemon_parent\" &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n> +\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1\n> +\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1 $tag_sha1\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'batch-command --buffer remote-object-info git://' '\n> +\t(\n> +\t\tset_transport_variables \"$daemon_parent\" &&\n> +\t\tcd \"$daemon_parent\" &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n> +\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1\n> +\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1 $tag_sha1\n> +\t\tflush\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +stop_git_daemon\n> +\n> +# Test --batch-command remote-object-info with 'http://' transport\n> +\n> +. \"$TEST_DIRECTORY\"/lib-httpd.sh\n> +start_httpd\n> +\n> +test_expect_success 'create repo to be served by http:// transport' '\n> +\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n> +\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n> +\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n> +\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello\n> +'\n> +\n> +\n> +test_expect_success 'batch-command remote-object-info http://' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_sha1\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_sha1\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'batch-command remote-object-info http:// one line' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'batch-command --buffer remote-object-info http://' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\n> +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1 $tag_sha1\n> +\t\tflush\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'batch-command remote-object-info http:// default filter' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\n> +\t\tgit cat-file --batch-command >actual <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1 $tag_sha1\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\n> +\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n> +\t\tEOF\n> +\t\ttest_grep \"objectsize:disk is currently not supported with remote-object-info\" err\n> +\t)\n> +'\n> +\n> +test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\n> +\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n> +\t\tEOF\n> +\t\ttest_grep \"deltabase is currently not supported with remote-object-info\" err\n> +\t)\n> +'\n> +\n> +test_expect_success 'remote-object-info fails on server with legacy protocol' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\n> +\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n> +\t\tEOF\n> +\t\ttest_grep \"remote-object-info requires protocol v2\" err\n> +\t)\n> +'\n> +\n> +test_expect_success 'remote-object-info fails on server with legacy protocol fallback' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\n> +\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n> +\t\tEOF\n> +\t\ttest_grep \"remote-object-info requires protocol v2\" err\n> +\t)\n> +'\n> +\n> +test_expect_success 'remote-object-info fails on malformed OID' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n> +\n> +\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n> +\t\tEOF\n> +\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n> +\t)\n> +'\n> +\n> +test_expect_success 'remote-object-info fails on malformed OID fallback' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n> +\n> +\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n> +\t\tEOF\n> +\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n> +\t)\n> +'\n> +\n> +test_expect_success 'remote-object-info fails on missing OID' '\n> +\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n> +\ttest_commit -C missing_oid_repo message1 c.txt &&\n> +\t(\n> +\t\tcd missing_oid_repo &&\n> +\n> +\t\tobject_id=$(git rev-parse message1:c.txt) &&\n> +\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n> +\t\tEOF\n> +\t\ttest_grep \"object-info: not our ref $object_id\" err\n> +\t)\n> +'\n> +\n> +# shellcheck disable=SC2016\n> +test_expect_success 'remote-object-info fails on missing OID fallback' '\n> +\t(\n> +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> +\t\tcd missing_oid_repo &&\n> +\t\tobject_id=$(git rev-parse message1:c.txt) &&\n> +\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n> +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n> +\t\tEOF\n> +\t\ttest_grep \"fatal: object-info: not our ref $object_id\" err\n> +\t)\n> +'\n> +\n> +# Test --batch-command remote-object-info with 'file://' transport\n> +\n> +# shellcheck disable=SC2016\n> +test_expect_success 'create repo to be served by file:// transport' '\n> +\tgit init server &&\n> +\tgit -C server config protocol.version 2 &&\n> +\tgit -C server config transfer.advertiseobjectinfo true &&\n> +\techo_without_newline \"$hello_content\" > server/hello &&\n> +\tgit -C server update-index --add hello\n> +'\n> +\n> +\n> +test_expect_success 'batch-command remote-object-info file://' '\n> +\t(\n> +\t\tset_transport_variables \"server\" &&\n> +\t\tcd server &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> +\t\tremote-object-info \"file://$(pwd)\" $hello_sha1\n> +\t\tremote-object-info \"file://$(pwd)\" $tree_sha1\n> +\t\tremote-object-info \"file://$(pwd)\" $commit_sha1\n> +\t\tremote-object-info \"file://$(pwd)\" $tag_sha1\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n> +\t(\n> +\t\tset_transport_variables \"server\" &&\n> +\t\tcd server &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> +\t\tremote-object-info \"file://$(pwd)\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'batch-command --buffer remote-object-info file://' '\n> +\t(\n> +\t\tset_transport_variables \"server\" &&\n> +\t\tcd server &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n> +\t\tremote-object-info \"file://$(pwd)\" $hello_sha1 $tree_sha1\n> +\t\tremote-object-info \"file://$(pwd)\" $commit_sha1 $tag_sha1\n> +\t\tflush\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'batch-command remote-object-info file:// default filter' '\n> +\t(\n> +\t\tset_transport_variables \"server\" &&\n> +\t\tcd server &&\n> +\n> +\t\techo \"$hello_sha1 $hello_size\" >expect &&\n> +\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n> +\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n> +\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n> +\n> +\t\tgit cat-file --batch-command >actual <<-EOF &&\n> +\t\tremote-object-info \"file://$(pwd)\" $hello_sha1 $tree_sha1\n> +\t\tremote-object-info \"file://$(pwd)\" $commit_sha1 $tag_sha1\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_done\n\nSome more tests I'd like to see\n- Testing against the '-Z' option.\n- Testing the fallback to fetch whole object when the server doesn't\nsupport 'remote-object-info'.\n\nThanks\n"},{"id":"498592","messageId":"CAN2LT1AKqSJvUiXphH8MZ5vwOKLy=qAhBUy7=y_yFF8JfO2KuA@mail.gmail.com","threadId":"61695","inReplyTo":"7lc6m7627sojdzabmz6pvaulbrcods6tqf7bnf3vxqsxzievjl@ixbispu2b3ch","subject":"Re: [PATCH 6/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-12T17:41:25Z","receivedAt":"2024-07-12T17:41:37Z","isPatch":true,"body":"On Mon, Jul 8, 2024 at 9:51 PM Justin Tobler <jltobler@gmail.com> wrote:\n>\n> On 24/06/28 03:05PM, Eric Ju wrote:\n> > From: Calvin Wan <calvinwan@google.com>\n> >\n> > Since the `info` command in cat-file --batch-command prints object info\n> > for a given object, it is natural to add another command in cat-file\n> > --batch-command to print object info for a given object from a remote.\n> > Add `remote-object-info` to cat-file --batch-command.\n> >\n> > While `info` takes object ids one at a time, this creates overhead when\n> > making requests to a server so `remote-object-info` instead can take\n> > multiple object ids at once.\n> >\n> > cat-file --batch-command is generally implemented in the following\n> > manner:\n> >\n> >  - Receive and parse input from user\n> >  - Call respective function attached to command\n> >  - Set batch mode state, get object info, print object info\n> >\n> > In --buffer mode, this changes to:\n> >\n> >  - Receive and parse input from user\n> >  - Store respective function attached to command in a queue\n> >  - After flush, loop through commands in queue\n> >     - Call respective function attached to command\n> >     - Set batch mode state, get object info, print object info\n>\n> So the problem is that there is overhead associated with getting object\n> info from the remote. Therefore, remote-object-info also supports\n> batching objects together. This seems reasonable.\n>\n\nThank you, Justin. Yes, you are right, whenever remote-object-info is\ncalled there is an overhead. I will explain where this overhead\nhappens in the following reply.\n\n> >\n> > Notice how the getting and printing of object info is accomplished one\n> > at a time. As described above, this creates a problem for making\n> > requests to a server. Therefore, `remote-object-info` is implemented in\n> > the following manner:\n> >\n> >  - Receive and parse input from user\n> >  If command is `remote-object-info`:\n> >     - Get object info from remote\n> >     - Loop through object info\n> >         - Call respective function attached to `info`\n> >         - Set batch mode state, use passed in object info, print object\n> >           info\n> >  Else:\n> >     - Call respective function attached to command\n> >     - Parse input, get object info, print object info\n> >\n> > And finally for --buffer mode `remote-object-info`:\n> >  - Receive and parse input from user\n> >  - Store respective function attached to command in a queue\n> >  - After flush, loop through commands in queue:\n> >     If command is `remote-object-info`:\n> >         - Get object info from remote\n> >         - Loop through object info\n> >             - Call respective function attached to `info`\n> >             - Set batch mode state, use passed in object info, print\n> >               object info\n> >     Else:\n> >         - Call respective function attached to command\n> >         - Set batch mode state, get object info, print object info\n> >\n> > To summarize, `remote-object-info` gets object info from the remote and\n> > then generates multiple `info` commands with the object info passed in.\n> >\n> > In order for remote-object-info to avoid remote communication overhead\n> > in the non-buffer mode, the objects are passed in as such:\n>\n> Even in non-buffer mode, having separate remote-object-info commands\n> would result in additional overhead correct? From my understanding each\n> command is executed sequently, so multiples of remote-object-info would\n> always result in additional overhead.\n>\n\nThank you. No matter what mode it is (buffer or non-buffer), the\noverhead of remote-object-info is always there. To my understanding,\nthere are two parts in the overhead:\n1. Setting up a connection. This is happening in `connect_setup()` in\n`fetch_object_info()` function.\n2. Sending request buf. This includes initializing the packet reader\nin `packet_reader_init()` and putting OIDs in the request buff in\n`send_object_info_request()`. Both of them are called in the\n`fetch_object_info()` function.\n\nIt would be more efficient to send multiple OIDs over one request\npacket in one connection in the form of  remote-object-info <remote>\n<oid> <oid> ... <oid>\n\n> >\n> > remote-object-info <remote> <oid> <oid> ... <oid>\n> >\n> > rather than\n> >\n> > remote-object-info <remote> <oid>\n> > remote-object-info <remote> <oid>\n> > ...\n> > remote-object-info <remote> <oid>\n> >\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n>\n> I think the sign-offs are supposed to go at the bottom.\n>\n\nThank you. I am fixing it in v2.\n\n> [snip]\n> > @@ -526,51 +533,118 @@ static void batch_one_object(const char *obj_name,\n> >               (opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0);\n> >       enum get_oid_result result;\n> >\n> > -     result = get_oid_with_context(the_repository, obj_name,\n> > -                                   flags, &data->oid, &ctx);\n> > -     if (result != FOUND) {\n> > -             switch (result) {\n> > -             case MISSING_OBJECT:\n> > -                     printf(\"%s missing%c\", obj_name, opt->output_delim);\n> > -                     break;\n> > -             case SHORT_NAME_AMBIGUOUS:\n> > -                     printf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n> > -                     break;\n> > -             case DANGLING_SYMLINK:\n> > -                     printf(\"dangling %\"PRIuMAX\"%c%s%c\",\n> > -                            (uintmax_t)strlen(obj_name),\n> > -                            opt->output_delim, obj_name, opt->output_delim);\n> > -                     break;\n> > -             case SYMLINK_LOOP:\n> > -                     printf(\"loop %\"PRIuMAX\"%c%s%c\",\n> > -                            (uintmax_t)strlen(obj_name),\n> > -                            opt->output_delim, obj_name, opt->output_delim);\n> > -                     break;\n> > -             case NOT_DIR:\n> > -                     printf(\"notdir %\"PRIuMAX\"%c%s%c\",\n> > -                            (uintmax_t)strlen(obj_name),\n> > -                            opt->output_delim, obj_name, opt->output_delim);\n> > -                     break;\n> > -             default:\n> > -                     BUG(\"unknown get_sha1_with_context result %d\\n\",\n> > -                            result);\n> > -                     break;\n> > +     if (!opt->use_remote_info) {\n>\n> When using the remote-object-info command, the object in question is\n> supposed to be on the remote and may not exist locally. Therefore we\n> skip over `get_oid_with_context()`.\n>\n\nThank you. Yes, that is the reason. I reworded your comment and added\nit to the code in v2 to make it easier to follow.\n\n> > +             result = get_oid_with_context(the_repository, obj_name,\n> > +                                             flags, &data->oid, &ctx);\n> > +             if (result != FOUND) {\n> > +                     switch (result) {\n> > +                     case MISSING_OBJECT:\n> > +                             printf(\"%s missing%c\", obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case SHORT_NAME_AMBIGUOUS:\n> > +                             printf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case DANGLING_SYMLINK:\n> > +                             printf(\"dangling %\"PRIuMAX\"%c%s%c\",\n> > +                                     (uintmax_t)strlen(obj_name),\n> > +                                     opt->output_delim, obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case SYMLINK_LOOP:\n> > +                             printf(\"loop %\"PRIuMAX\"%c%s%c\",\n> > +                                     (uintmax_t)strlen(obj_name),\n> > +                                     opt->output_delim, obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case NOT_DIR:\n> > +                             printf(\"notdir %\"PRIuMAX\"%c%s%c\",\n> > +                                     (uintmax_t)strlen(obj_name),\n> > +                                     opt->output_delim, obj_name, opt->output_delim);\n> > +                             break;\n> > +                     default:\n> > +                             BUG(\"unknown get_sha1_with_context result %d\\n\",\n> > +                                     result);\n> > +                             break;\n> > +                     }\n> > +                     fflush(stdout);\n> > +                     return;\n> >               }\n> > -             fflush(stdout);\n> > -             return;\n> > -     }\n> >\n> > -     if (ctx.mode == 0) {\n> > -             printf(\"symlink %\"PRIuMAX\"%c%s%c\",\n> > -                    (uintmax_t)ctx.symlink_path.len,\n> > -                    opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n> > -             fflush(stdout);\n> > -             return;\n> > +             if (ctx.mode == 0) {\n> > +                     printf(\"symlink %\"PRIuMAX\"%c%s%c\",\n> > +                             (uintmax_t)ctx.symlink_path.len,\n> > +                             opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n> > +                     fflush(stdout);\n> > +                     return;\n> > +             }\n> >       }\n> >\n> >       batch_object_write(obj_name, scratch, opt, data, NULL, 0);\n> >  }\n> >\n> > +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> > +{\n> > +     int retval = 0;\n> > +     struct remote *remote = NULL;\n> > +     struct object_id oid;\n> > +     struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> > +     static struct transport *gtransport;\n> > +\n> > +     /*\n> > +      * Change the format to \"%(objectname) %(objectsize)\" when\n> > +      * remote-object-info command is used. Once we start supporting objecttype\n> > +      * the default format should change to DEFAULT_FORMAT\n> > +     */\n> > +     if (!opt->format) {\n> > +             opt->format = \"%(objectname) %(objectsize)\";\n> > +     }\n>\n> We should omit the parenthesis for single line if statements.\n>\n\nThank you. Fixed in V2.\n\n> > +\n> > +     remote = remote_get(argv[0]);\n> > +     if (!remote)\n> > +             die(_(\"must supply valid remote when using remote-object-info\"));\n> > +     oid_array_clear(&object_info_oids);\n> > +     for (size_t i = 1; i < argc; i++) {\n> > +             if (get_oid_hex(argv[i], &oid))\n> > +                     die(_(\"Not a valid object name %s\"), argv[i]);\n> > +             oid_array_append(&object_info_oids, &oid);\n> > +     }\n> > +\n> > +     gtransport = transport_get(remote, NULL);\n> > +     if (gtransport->smart_options) {\n> > +             int include_size = 0;\n> > +\n> > +             CALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> > +             gtransport->smart_options->object_info = 1;\n> > +             gtransport->smart_options->object_info_oids = &object_info_oids;\n> > +             /*\n> > +              * 'size' is the only option currently supported.\n> > +              * Other options that are passed in the format will exit with error.\n> > +              */\n> > +             if (strstr(opt->format, \"%(objectsize)\")) {\n> > +                     string_list_append(&object_info_options, \"size\");\n> > +                     include_size = 1;\n> > +             }\n> > +             if (strstr(opt->format, \"%(objecttype)\")) {\n> > +                     die(_(\"objecttype is currently not supported with remote-object-info\"));\n> > +             }\n>\n> Another single line if statement above that should omit the parenthesis.\n>\n\nThank you. Fixed in V2.\n\n> > +             if (strstr(opt->format, \"%(objectsize:disk)\"))\n> > +                     die(_(\"objectsize:disk is currently not supported with remote-object-info\"));\n> > +             if (strstr(opt->format, \"%(deltabase)\"))\n> > +                     die(_(\"deltabase is currently not supported with remote-object-info\"));\n> > +             if (object_info_options.nr > 0) {\n> > +                     gtransport->smart_options->object_info_options = &object_info_options;\n> > +                     for (size_t i = 0; i < object_info_oids.nr; i++) {\n> > +                             if (include_size)\n> > +                                     remote_object_info[i].sizep = xcalloc(1, sizeof(long));\n> > +                     }\n> > +                     gtransport->smart_options->object_info_data = &remote_object_info;\n> > +                     retval = transport_fetch_refs(gtransport, NULL);\n> > +             }\n> > +     } else {\n> > +             retval = -1;\n> > +     }\n> > +\n> > +     return retval;\n> > +}\n> > +\n> >  struct object_cb_data {\n> >       struct batch_options *opt;\n> >       struct expand_data *expand;\n> > @@ -642,6 +716,7 @@ typedef void (*parse_cmd_fn_t)(struct batch_options *, const char *,\n> >  struct queued_cmd {\n> >       parse_cmd_fn_t fn;\n> >       char *line;\n> > +     const char *name;\n>\n> Since special handling is needed for the remote-object-info command, we\n> record the queued command names to check against later.\n>\n\nYes. We need to compare the function name to do special handling\nlater. But I think we can have a better solution here instead of doing\na name comparison. Please see my reply below.\n\n> >  };\n> >\n> >  static void parse_cmd_contents(struct batch_options *opt,\n> > @@ -662,6 +737,55 @@ static void parse_cmd_info(struct batch_options *opt,\n> >       batch_one_object(line, output, opt, data);\n> >  }\n> >\n> > +static const struct parse_cmd {\n> > +     const char *name;\n> > +     parse_cmd_fn_t fn;\n> > +     unsigned takes_args;\n> > +} commands[] = {\n> > +     { \"contents\", parse_cmd_contents, 1 },\n> > +     { \"info\", parse_cmd_info, 1 },\n> > +     { \"remote-object-info\", parse_cmd_info, 1 },\n> > +     { \"flush\", NULL, 0 },\n> > +};\n> > +\n> > +static void parse_remote_info(struct batch_options *opt,\n> > +                        char *line,\n> > +                        struct strbuf *output,\n> > +                        struct expand_data *data,\n> > +                        const struct parse_cmd *p_cmd,\n> > +                        struct queued_cmd *q_cmd)\n>\n> It seems a little confusing to me that `parse_remote_info()` accepts\n> both a `parse_cmd` and `queued_cmd`, but only expects to use one or the\n> other. It looks like this is done because `dispatch_calls()` already\n> accepts `queued_cmd`, but now needs to call `parse_remote_info()`.\n>\n> Since it is only the underlying command function that is needed by\n> `parse_remote_info()`\n>\n\nThank you. I agree. I did some refactoring. Please see me reply below.\n\n\n> > +{\n> > +     int count;\n> > +     const char **argv;\n> > +\n> > +     count = split_cmdline(line, &argv);\n> > +     if (get_remote_info(opt, count, argv))\n> > +             goto cleanup;\n> > +     opt->use_remote_info = 1;\n> > +     data->skip_object_info = 1;\n> > +     data->mark_query = 0;\n> > +     for (size_t i = 0; i < object_info_oids.nr; i++) {\n> > +             if (remote_object_info[i].sizep)\n> > +                     data->size = *remote_object_info[i].sizep;\n> > +             if (remote_object_info[i].typep)\n> > +                     data->type = *remote_object_info[i].typep;\n> > +\n> > +             data->oid = object_info_oids.oid[i];\n> > +             if (p_cmd)\n> > +                     p_cmd->fn(opt, argv[i+1], output, data);\n> > +             else\n> > +                     q_cmd->fn(opt, argv[i+1], output, data);\n> > +     }\n> > +     opt->use_remote_info = 0;\n> > +     data->skip_object_info = 0;\n> > +     data->mark_query = 1;\n> > +\n> > +cleanup:\n> > +     for (size_t i = 0; i < object_info_oids.nr; i++)\n> > +             free_object_info_contents(&remote_object_info[i]);\n> > +     free(remote_object_info);\n> > +}\n> > +\n> >  static void dispatch_calls(struct batch_options *opt,\n> >               struct strbuf *output,\n> >               struct expand_data *data,\n> > @@ -671,8 +795,12 @@ static void dispatch_calls(struct batch_options *opt,\n> >       if (!opt->buffer_output)\n> >               die(_(\"flush is only for --buffer mode\"));\n> >\n> > -     for (int i = 0; i < nr; i++)\n> > -             cmd[i].fn(opt, cmd[i].line, output, data);\n> > +     for (int i = 0; i < nr; i++) {\n> > +             if (!strcmp(cmd[i].name, \"remote-object-info\"))\n> > +                     parse_remote_info(opt, cmd[i].line, output, data, NULL, &cmd[i]);\n>\n> If we adapt `parse_remote_info()` to accept the command function we\n> could pass cmd->fn here instead.\n>\n\nThank you. I think I can push it a bit further.\n\nUnder the hood, parse_remote_info will use parse_cmd_info to print the\nretrieved information to the client. That is why it had this line\noriginally:\n  ...\n   { \"remote-object-info\", parse_cmd_info, 1 },\n  ...\n\nInspired by your comment, I am thinking if I can adapt\nparse_remote_info() 's signature to the same as parse_cmd_info(). It\nwould make the code cleaner. To be specific. I can\n\n1. get rid of name cooperation in\n   ...\n   if (!strcmp(cmd[i].name, \"remote-object-info\"))\n       parse_remote_info(opt, cmd[i].line, output, data, NULL, &cmd[I]);\n   else\n       cmd[i].fn(opt, cmd[i].line, output, data);\n   ...\n   and I can just use `cmd[i].fn(opt, cmd[i].line, output, data)`\n\n2. get rid of\n   ...\n   if (p_cmd)\n        p_cmd->fn(opt, argv[i+1], output, data);\n   else\n        q_cmd->fn(opt, argv[i+1], output, data);\n   ...\n\nI will make this change in V2.\n\n> > +             else\n> > +                     cmd[i].fn(opt, cmd[i].line, output, data);\n> > +     }\n> >\n> >       fflush(stdout);\n> >  }\n> > @@ -685,17 +813,6 @@ static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n> >       *nr = 0;\n> >  }\n> >\n> > -\n> > -static const struct parse_cmd {\n> > -     const char *name;\n> > -     parse_cmd_fn_t fn;\n> > -     unsigned takes_args;\n> > -} commands[] = {\n> > -     { \"contents\", parse_cmd_contents, 1},\n> > -     { \"info\", parse_cmd_info, 1},\n> > -     { \"flush\", NULL, 0},\n> > -};\n> > -\n> >  static void batch_objects_command(struct batch_options *opt,\n> >                                   struct strbuf *output,\n> >                                   struct expand_data *data)\n> > @@ -740,11 +857,17 @@ static void batch_objects_command(struct batch_options *opt,\n> >                       dispatch_calls(opt, output, data, queued_cmd, nr);\n> >                       free_cmds(queued_cmd, &nr);\n> >               } else if (!opt->buffer_output) {\n> > -                     cmd->fn(opt, p, output, data);\n> > +                     if (!strcmp(cmd->name, \"remote-object-info\")) {\n> > +                             char *line = xstrdup_or_null(p);\n> > +                             parse_remote_info(opt, line, output, data, cmd, NULL);\n>\n> Same here, if we adapt `parse_remote_info()` to accept the command\n> function we could pass cmd->fn here instead.\n\nThank you. Please see my reply above.\n\n> > +                     } else {\n> > +                             cmd->fn(opt, p, output, data);\n> > +                     }\n> >               } else {\n> >                       ALLOC_GROW(queued_cmd, nr + 1, alloc);\n> >                       call.fn = cmd->fn;\n> >                       call.line = xstrdup_or_null(p);\n> > +                     call.name = cmd->name;\n> >                       queued_cmd[nr++] = call;\n> >               }\n> >       }\n> > @@ -761,8 +884,6 @@ static void batch_objects_command(struct batch_options *opt,\n> >       strbuf_release(&input);\n> >  }\n> >\n> > -#define DEFAULT_FORMAT \"%(objectname) %(objecttype) %(objectsize)\"\n> > -\n> >  static int batch_objects(struct batch_options *opt)\n> >  {\n> >       struct strbuf input = STRBUF_INIT;\n> [snip]\n\nOn Mon, Jul 8, 2024 at 9:51 PM Justin Tobler <jltobler@gmail.com> wrote:\n>\n> On 24/06/28 03:05PM, Eric Ju wrote:\n> > From: Calvin Wan <calvinwan@google.com>\n> >\n> > Since the `info` command in cat-file --batch-command prints object info\n> > for a given object, it is natural to add another command in cat-file\n> > --batch-command to print object info for a given object from a remote.\n> > Add `remote-object-info` to cat-file --batch-command.\n> >\n> > While `info` takes object ids one at a time, this creates overhead when\n> > making requests to a server so `remote-object-info` instead can take\n> > multiple object ids at once.\n> >\n> > cat-file --batch-command is generally implemented in the following\n> > manner:\n> >\n> >  - Receive and parse input from user\n> >  - Call respective function attached to command\n> >  - Set batch mode state, get object info, print object info\n> >\n> > In --buffer mode, this changes to:\n> >\n> >  - Receive and parse input from user\n> >  - Store respective function attached to command in a queue\n> >  - After flush, loop through commands in queue\n> >     - Call respective function attached to command\n> >     - Set batch mode state, get object info, print object info\n>\n> So the problem is that there is overhead associated with getting object\n> info from the remote. Therefore, remote-object-info also supports\n> batching objects together. This seems reasonable.\n>\n> >\n> > Notice how the getting and printing of object info is accomplished one\n> > at a time. As described above, this creates a problem for making\n> > requests to a server. Therefore, `remote-object-info` is implemented in\n> > the following manner:\n> >\n> >  - Receive and parse input from user\n> >  If command is `remote-object-info`:\n> >     - Get object info from remote\n> >     - Loop through object info\n> >         - Call respective function attached to `info`\n> >         - Set batch mode state, use passed in object info, print object\n> >           info\n> >  Else:\n> >     - Call respective function attached to command\n> >     - Parse input, get object info, print object info\n> >\n> > And finally for --buffer mode `remote-object-info`:\n> >  - Receive and parse input from user\n> >  - Store respective function attached to command in a queue\n> >  - After flush, loop through commands in queue:\n> >     If command is `remote-object-info`:\n> >         - Get object info from remote\n> >         - Loop through object info\n> >             - Call respective function attached to `info`\n> >             - Set batch mode state, use passed in object info, print\n> >               object info\n> >     Else:\n> >         - Call respective function attached to command\n> >         - Set batch mode state, get object info, print object info\n> >\n> > To summarize, `remote-object-info` gets object info from the remote and\n> > then generates multiple `info` commands with the object info passed in.\n> >\n> > In order for remote-object-info to avoid remote communication overhead\n> > in the non-buffer mode, the objects are passed in as such:\n>\n> Even in non-buffer mode, having separate remote-object-info commands\n> would result in additional overhead correct? From my understanding each\n> command is executed sequently, so multiples of remote-object-info would\n> always result in additional overhead.\n>\n> >\n> > remote-object-info <remote> <oid> <oid> ... <oid>\n> >\n> > rather than\n> >\n> > remote-object-info <remote> <oid>\n> > remote-object-info <remote> <oid>\n> > ...\n> > remote-object-info <remote> <oid>\n> >\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n>\n> I think the sign-offs are supposed to go at the bottom.\n>\n> [snip]\n> > @@ -526,51 +533,118 @@ static void batch_one_object(const char *obj_name,\n> >               (opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0);\n> >       enum get_oid_result result;\n> >\n> > -     result = get_oid_with_context(the_repository, obj_name,\n> > -                                   flags, &data->oid, &ctx);\n> > -     if (result != FOUND) {\n> > -             switch (result) {\n> > -             case MISSING_OBJECT:\n> > -                     printf(\"%s missing%c\", obj_name, opt->output_delim);\n> > -                     break;\n> > -             case SHORT_NAME_AMBIGUOUS:\n> > -                     printf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n> > -                     break;\n> > -             case DANGLING_SYMLINK:\n> > -                     printf(\"dangling %\"PRIuMAX\"%c%s%c\",\n> > -                            (uintmax_t)strlen(obj_name),\n> > -                            opt->output_delim, obj_name, opt->output_delim);\n> > -                     break;\n> > -             case SYMLINK_LOOP:\n> > -                     printf(\"loop %\"PRIuMAX\"%c%s%c\",\n> > -                            (uintmax_t)strlen(obj_name),\n> > -                            opt->output_delim, obj_name, opt->output_delim);\n> > -                     break;\n> > -             case NOT_DIR:\n> > -                     printf(\"notdir %\"PRIuMAX\"%c%s%c\",\n> > -                            (uintmax_t)strlen(obj_name),\n> > -                            opt->output_delim, obj_name, opt->output_delim);\n> > -                     break;\n> > -             default:\n> > -                     BUG(\"unknown get_sha1_with_context result %d\\n\",\n> > -                            result);\n> > -                     break;\n> > +     if (!opt->use_remote_info) {\n>\n> When using the remote-object-info command, the object in question is\n> supposed to be on the remote and may not exist locally. Therefore we\n> skip over `get_oid_with_context()`.\n>\n> > +             result = get_oid_with_context(the_repository, obj_name,\n> > +                                             flags, &data->oid, &ctx);\n> > +             if (result != FOUND) {\n> > +                     switch (result) {\n> > +                     case MISSING_OBJECT:\n> > +                             printf(\"%s missing%c\", obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case SHORT_NAME_AMBIGUOUS:\n> > +                             printf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case DANGLING_SYMLINK:\n> > +                             printf(\"dangling %\"PRIuMAX\"%c%s%c\",\n> > +                                     (uintmax_t)strlen(obj_name),\n> > +                                     opt->output_delim, obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case SYMLINK_LOOP:\n> > +                             printf(\"loop %\"PRIuMAX\"%c%s%c\",\n> > +                                     (uintmax_t)strlen(obj_name),\n> > +                                     opt->output_delim, obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case NOT_DIR:\n> > +                             printf(\"notdir %\"PRIuMAX\"%c%s%c\",\n> > +                                     (uintmax_t)strlen(obj_name),\n> > +                                     opt->output_delim, obj_name, opt->output_delim);\n> > +                             break;\n> > +                     default:\n> > +                             BUG(\"unknown get_sha1_with_context result %d\\n\",\n> > +                                     result);\n> > +                             break;\n> > +                     }\n> > +                     fflush(stdout);\n> > +                     return;\n> >               }\n> > -             fflush(stdout);\n> > -             return;\n> > -     }\n> >\n> > -     if (ctx.mode == 0) {\n> > -             printf(\"symlink %\"PRIuMAX\"%c%s%c\",\n> > -                    (uintmax_t)ctx.symlink_path.len,\n> > -                    opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n> > -             fflush(stdout);\n> > -             return;\n> > +             if (ctx.mode == 0) {\n> > +                     printf(\"symlink %\"PRIuMAX\"%c%s%c\",\n> > +                             (uintmax_t)ctx.symlink_path.len,\n> > +                             opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n> > +                     fflush(stdout);\n> > +                     return;\n> > +             }\n> >       }\n> >\n> >       batch_object_write(obj_name, scratch, opt, data, NULL, 0);\n> >  }\n> >\n> > +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> > +{\n> > +     int retval = 0;\n> > +     struct remote *remote = NULL;\n> > +     struct object_id oid;\n> > +     struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> > +     static struct transport *gtransport;\n> > +\n> > +     /*\n> > +      * Change the format to \"%(objectname) %(objectsize)\" when\n> > +      * remote-object-info command is used. Once we start supporting objecttype\n> > +      * the default format should change to DEFAULT_FORMAT\n> > +     */\n> > +     if (!opt->format) {\n> > +             opt->format = \"%(objectname) %(objectsize)\";\n> > +     }\n>\n> We should omit the parenthesis for single line if statements.\n>\n> > +\n> > +     remote = remote_get(argv[0]);\n> > +     if (!remote)\n> > +             die(_(\"must supply valid remote when using remote-object-info\"));\n> > +     oid_array_clear(&object_info_oids);\n> > +     for (size_t i = 1; i < argc; i++) {\n> > +             if (get_oid_hex(argv[i], &oid))\n> > +                     die(_(\"Not a valid object name %s\"), argv[i]);\n> > +             oid_array_append(&object_info_oids, &oid);\n> > +     }\n> > +\n> > +     gtransport = transport_get(remote, NULL);\n> > +     if (gtransport->smart_options) {\n> > +             int include_size = 0;\n> > +\n> > +             CALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> > +             gtransport->smart_options->object_info = 1;\n> > +             gtransport->smart_options->object_info_oids = &object_info_oids;\n> > +             /*\n> > +              * 'size' is the only option currently supported.\n> > +              * Other options that are passed in the format will exit with error.\n> > +              */\n> > +             if (strstr(opt->format, \"%(objectsize)\")) {\n> > +                     string_list_append(&object_info_options, \"size\");\n> > +                     include_size = 1;\n> > +             }\n> > +             if (strstr(opt->format, \"%(objecttype)\")) {\n> > +                     die(_(\"objecttype is currently not supported with remote-object-info\"));\n> > +             }\n>\n> Another single line if statement above that should omit the parenthesis.\n>\n> > +             if (strstr(opt->format, \"%(objectsize:disk)\"))\n> > +                     die(_(\"objectsize:disk is currently not supported with remote-object-info\"));\n> > +             if (strstr(opt->format, \"%(deltabase)\"))\n> > +                     die(_(\"deltabase is currently not supported with remote-object-info\"));\n> > +             if (object_info_options.nr > 0) {\n> > +                     gtransport->smart_options->object_info_options = &object_info_options;\n> > +                     for (size_t i = 0; i < object_info_oids.nr; i++) {\n> > +                             if (include_size)\n> > +                                     remote_object_info[i].sizep = xcalloc(1, sizeof(long));\n> > +                     }\n> > +                     gtransport->smart_options->object_info_data = &remote_object_info;\n> > +                     retval = transport_fetch_refs(gtransport, NULL);\n> > +             }\n> > +     } else {\n> > +             retval = -1;\n> > +     }\n> > +\n> > +     return retval;\n> > +}\n> > +\n> >  struct object_cb_data {\n> >       struct batch_options *opt;\n> >       struct expand_data *expand;\n> > @@ -642,6 +716,7 @@ typedef void (*parse_cmd_fn_t)(struct batch_options *, const char *,\n> >  struct queued_cmd {\n> >       parse_cmd_fn_t fn;\n> >       char *line;\n> > +     const char *name;\n>\n> Since special handling is needed for the remote-object-info command, we\n> record the queued command names to check against later.\n>\n> >  };\n> >\n> >  static void parse_cmd_contents(struct batch_options *opt,\n> > @@ -662,6 +737,55 @@ static void parse_cmd_info(struct batch_options *opt,\n> >       batch_one_object(line, output, opt, data);\n> >  }\n> >\n> > +static const struct parse_cmd {\n> > +     const char *name;\n> > +     parse_cmd_fn_t fn;\n> > +     unsigned takes_args;\n> > +} commands[] = {\n> > +     { \"contents\", parse_cmd_contents, 1 },\n> > +     { \"info\", parse_cmd_info, 1 },\n> > +     { \"remote-object-info\", parse_cmd_info, 1 },\n> > +     { \"flush\", NULL, 0 },\n> > +};\n> > +\n> > +static void parse_remote_info(struct batch_options *opt,\n> > +                        char *line,\n> > +                        struct strbuf *output,\n> > +                        struct expand_data *data,\n> > +                        const struct parse_cmd *p_cmd,\n> > +                        struct queued_cmd *q_cmd)\n>\n> It seems a little confusing to me that `parse_remote_info()` accepts\n> both a `parse_cmd` and `queued_cmd`, but only expects to use one or the\n> other. It looks like this is done because `dispatch_calls()` already\n> accepts `queued_cmd`, but now needs to call `parse_remote_info()`.\n>\n> Since it is only the underlying command function that is needed by\n> `parse_remote_info()`\n>\n> > +{\n> > +     int count;\n> > +     const char **argv;\n> > +\n> > +     count = split_cmdline(line, &argv);\n> > +     if (get_remote_info(opt, count, argv))\n> > +             goto cleanup;\n> > +     opt->use_remote_info = 1;\n> > +     data->skip_object_info = 1;\n> > +     data->mark_query = 0;\n> > +     for (size_t i = 0; i < object_info_oids.nr; i++) {\n> > +             if (remote_object_info[i].sizep)\n> > +                     data->size = *remote_object_info[i].sizep;\n> > +             if (remote_object_info[i].typep)\n> > +                     data->type = *remote_object_info[i].typep;\n> > +\n> > +             data->oid = object_info_oids.oid[i];\n> > +             if (p_cmd)\n> > +                     p_cmd->fn(opt, argv[i+1], output, data);\n> > +             else\n> > +                     q_cmd->fn(opt, argv[i+1], output, data);\n> > +     }\n> > +     opt->use_remote_info = 0;\n> > +     data->skip_object_info = 0;\n> > +     data->mark_query = 1;\n> > +\n> > +cleanup:\n> > +     for (size_t i = 0; i < object_info_oids.nr; i++)\n> > +             free_object_info_contents(&remote_object_info[i]);\n> > +     free(remote_object_info);\n> > +}\n> > +\n> >  static void dispatch_calls(struct batch_options *opt,\n> >               struct strbuf *output,\n> >               struct expand_data *data,\n> > @@ -671,8 +795,12 @@ static void dispatch_calls(struct batch_options *opt,\n> >       if (!opt->buffer_output)\n> >               die(_(\"flush is only for --buffer mode\"));\n> >\n> > -     for (int i = 0; i < nr; i++)\n> > -             cmd[i].fn(opt, cmd[i].line, output, data);\n> > +     for (int i = 0; i < nr; i++) {\n> > +             if (!strcmp(cmd[i].name, \"remote-object-info\"))\n> > +                     parse_remote_info(opt, cmd[i].line, output, data, NULL, &cmd[i]);\n>\n> If we adapt `parse_remote_info()` to accept the command function we\n> could pass cmd->fn here instead.\n>\n> > +             else\n> > +                     cmd[i].fn(opt, cmd[i].line, output, data);\n> > +     }\n> >\n> >       fflush(stdout);\n> >  }\n> > @@ -685,17 +813,6 @@ static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n> >       *nr = 0;\n> >  }\n> >\n> > -\n> > -static const struct parse_cmd {\n> > -     const char *name;\n> > -     parse_cmd_fn_t fn;\n> > -     unsigned takes_args;\n> > -} commands[] = {\n> > -     { \"contents\", parse_cmd_contents, 1},\n> > -     { \"info\", parse_cmd_info, 1},\n> > -     { \"flush\", NULL, 0},\n> > -};\n> > -\n> >  static void batch_objects_command(struct batch_options *opt,\n> >                                   struct strbuf *output,\n> >                                   struct expand_data *data)\n> > @@ -740,11 +857,17 @@ static void batch_objects_command(struct batch_options *opt,\n> >                       dispatch_calls(opt, output, data, queued_cmd, nr);\n> >                       free_cmds(queued_cmd, &nr);\n> >               } else if (!opt->buffer_output) {\n> > -                     cmd->fn(opt, p, output, data);\n> > +                     if (!strcmp(cmd->name, \"remote-object-info\")) {\n> > +                             char *line = xstrdup_or_null(p);\n> > +                             parse_remote_info(opt, line, output, data, cmd, NULL);\n>\n> Same here, if we adapt `parse_remote_info()` to accept the command\n> function we could pass cmd->fn here instead.\n>\n> > +                     } else {\n> > +                             cmd->fn(opt, p, output, data);\n> > +                     }\n> >               } else {\n> >                       ALLOC_GROW(queued_cmd, nr + 1, alloc);\n> >                       call.fn = cmd->fn;\n> >                       call.line = xstrdup_or_null(p);\n> > +                     call.name = cmd->name;\n> >                       queued_cmd[nr++] = call;\n> >               }\n> >       }\n> > @@ -761,8 +884,6 @@ static void batch_objects_command(struct batch_options *opt,\n> >       strbuf_release(&input);\n> >  }\n> >\n> > -#define DEFAULT_FORMAT \"%(objectname) %(objecttype) %(objectsize)\"\n> > -\n> >  static int batch_objects(struct batch_options *opt)\n> >  {\n> >       struct strbuf input = STRBUF_INIT;\n> [snip]\n"},{"id":"498604","messageId":"CAN2LT1D_L39EH53K=xAGx3PdK90-Wir52+mOpwwK42cc8nnHTQ@mail.gmail.com","threadId":"61695","inReplyTo":"87ikxfqbm9.fsf@iotcl.com","subject":"Re: [PATCH 4/6] transport: add client support for object-info","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-13T02:30:00Z","receivedAt":"2024-07-13T02:30:12Z","isPatch":true,"body":"On Tue, Jul 9, 2024 at 3:16 AM Toon claes <toon@iotcl.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> > diff --git a/transport.c b/transport.c\n> > index 83ddea8fbc..2847aa3f3c 100644\n> > --- a/transport.c\n> > +++ b/transport.c\n> > @@ -436,11 +504,27 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >       args.server_options = transport->server_options;\n> >       args.negotiation_tips = data->options.negotiation_tips;\n> >       args.reject_shallow_remote = transport->smart_options->reject_shallow;\n> > -\n> > -     if (!data->finished_handshake) {\n> > -             int i;\n> > +     args.object_info = transport->smart_options->object_info;\n> > +\n> > +     if (transport->smart_options && transport->smart_options->object_info) {\n> > +             struct ref *ref = object_info_refs;\n> > +\n> > +             if (!fetch_object_info(transport, data->options.object_info_data))\n> > +                     goto cleanup;\n> > +             args.object_info_data = data->options.object_info_data;\n> > +             args.quiet = 1;\n> > +             args.no_progress = 1;\n> > +             for (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> > +                     struct ref *temp_ref = xcalloc(1, sizeof (struct ref));\n> > +                     temp_ref->old_oid = *(transport->smart_options->object_info_oids->oid + i);\n>\n> Any reason why you're not using the subscript operator (square brackets)\n> like this:\n>\n> +                       temp_ref->old_oid = transport->smart_options->object_info_oids->oid[I];\n>\n\nThank you. Fixed in V2.\n\n> > +                     temp_ref->exact_oid = 1;\n> > +                     ref->next = temp_ref;\n> > +                     ref = ref->next;\n> > +             }\n> > +             transport->remote_refs = object_info_refs->next;\n>\n> I find it a bit weird you're allocating object_info_refs, only to use it\n> to point to the next. Can I suggest a little refactor:\n>\n\nThank you. I have to agree that the old implementation of iterating on\nthe object_info_refs linked list is a bit obscure.\nYour suggestion is easier to follow. I am replacing the old logic in V2.\n\n> ----8<-----8<----\n> diff --git a/transport.c b/transport.c\n> index 662faa004e..56cb3a1693 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -479,7 +479,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n>         struct ref *refs = NULL;\n>         struct fetch_pack_args args;\n>         struct ref *refs_tmp = NULL;\n> -       struct ref *object_info_refs = xcalloc(1, sizeof (struct ref));\n> +       struct ref *object_info_refs = NULL;\n>\n>         memset(&args, 0, sizeof(args));\n>         args.uploadpack = data->options.uploadpack;\n> @@ -509,7 +509,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n>         args.object_info = transport->smart_options->object_info;\n>\n>         if (transport->smart_options && transport->smart_options->object_info) {\n> -               struct ref *ref = object_info_refs;\n> +               struct ref *ref = object_info_refs = xcalloc(1, sizeof (struct ref));\n>\n>                 if (!fetch_object_info(transport, data->options.object_info_data))\n>                         goto cleanup;\n> @@ -517,13 +517,12 @@ static int fetch_refs_via_pack(struct transport *transport,\n>                 args.quiet = 1;\n>                 args.no_progress = 1;\n>                 for (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> -                       struct ref *temp_ref = xcalloc(1, sizeof (struct ref));\n> -                       temp_ref->old_oid = *(transport->smart_options->object_info_oids->oid + i);\n> -                       temp_ref->exact_oid = 1;\n> -                       ref->next = temp_ref;\n> +                       ref->old_oid = transport->smart_options->object_info_oids->oid[i];\n> +                       ref->exact_oid = 1;\n> +                       ref->next = xcalloc(1, sizeof (struct ref));\n>                         ref = ref->next;\n>                 }\n> -               transport->remote_refs = object_info_refs->next;\n> +               transport->remote_refs = object_info_refs;\n>         } else if (!data->finished_handshake) {\n>                 int must_list_refs = 0;\n>                 for (int i = 0; i < nr_heads; i++) {\n> @@ -565,7 +564,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n>\n>         data->finished_handshake = 0;\n>         if (args.object_info) {\n> -               struct ref *ref_cpy_reader = object_info_refs->next;\n> +               struct ref *ref_cpy_reader = object_info_refs;\n>                 for (int i = 0; ref_cpy_reader; i++) {\n>                         oid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &(*args.object_info_data)[i], OBJECT_INFO_LOOKUP_REPLACE);\n>                         ref_cpy_reader = ref_cpy_reader->next;\n> ----8<-----8<----\n>\n> To be honest, I'm not sure it works, because fetch_object_info() always\n> seem to return a non-zero value. I'm not sure this is due to missing\n> code coverage, or a bug. I guess it's worth looking into.\n>\n\nThank you. I tested your suggestion and it is working. I can confirm\nit when I did the following with my debugger\n    1. pause on a test case of t/t1017-cat-file-remote-object-info.sh\n    2. git cat-file \"--batch-command=%(objectname) %(objectsize)\"\n    3. remote-object-info http://127.0.0.1:11017/smart/http_parent\n5e1c309dae7f45e0f39b1bf3ac3cd9db12e7d689\nI set breakpoints all along and see that fetch_object_info() returned zero\n\nWould you mind sharing your test steps with me? I would love to dig deeper.\n\n> --\n> Toon\n"},{"id":"498605","messageId":"CAN2LT1AXfY6efX7kzR4A6kUefZx-KtmL3oDQ3OUine7jbc5haA@mail.gmail.com","threadId":"61695","inReplyTo":"xmqqttgyfrnb.fsf@gitster.g","subject":"Re: [PATCH 4/6] transport: add client support for object-info","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-13T02:32:54Z","receivedAt":"2024-07-13T02:33:06Z","isPatch":true,"body":"On Tue, Jul 9, 2024 at 12:37 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Toon claes <toon@iotcl.com> writes:\n>\n> >> +                    temp_ref->old_oid = *(transport->smart_options->object_info_oids->oid + i);\n> >\n> > Any reason why you're not using the subscript operator (square brackets)\n> > like this:\n> >\n> > +                     temp_ref->old_oid = transport->smart_options->object_info_oids->oid[i];\n>\n> Much nicer, but fold such overly long lines, please,\n>\n>                         temp_ref->old_oid = transport->smart_options->\n>                                 object_info_oids->oid[i];\n>\n> to make them readable.\n>\n>\n\nThank you, sir. I will follow the folding format in V2.\n\n>\n> > ...\n> > To be honest, I'm not sure it works, because fetch_object_info() always\n> > seem to return a non-zero value. I'm not sure this is due to missing\n> > code coverage, or a bug. I guess it's worth looking into.\n"},{"id":"498606","messageId":"CAN2LT1CEy34qNT4RUcQvBNskcdY_wVjBNqCVP70UpWShK+oOpA@mail.gmail.com","threadId":"61695","inReplyTo":"87h6czqblk.fsf@iotcl.com","subject":"Re: [PATCH 6/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-13T02:35:03Z","receivedAt":"2024-07-13T02:35:14Z","isPatch":true,"body":"On Tue, Jul 9, 2024 at 3:16 AM Toon claes <toon@iotcl.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > index 72a78cdc8c..34958a1747 100644\n> > --- a/builtin/cat-file.c\n> > +++ b/builtin/cat-file.c\n> > ...\n> > +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> > +{\n> > +     int retval = 0;\n> > +     struct remote *remote = NULL;\n> > +     struct object_id oid;\n> > +     struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> > +     static struct transport *gtransport;\n> > +\n> > +     /*\n> > +      * Change the format to \"%(objectname) %(objectsize)\" when\n> > +      * remote-object-info command is used. Once we start supporting objecttype\n> > +      * the default format should change to DEFAULT_FORMAT\n> > +     */\n>\n> I believe this comment has become outdated, or got moved around\n> incorrectly.\n>\n\nThank you Toon. Sorry, I didn't get it. This comment is not outdated.\nIt is before this code\n    if (!opt->format) {\n        opt->format = \"%(objectname) %(objectsize)\";\n    }\n\nAnd this is related to my 2nd open question in the cover letter\n\n    2. Right now, only the size is supported. If the batch command format\n        contains objectsize:disk or deltabase, it will die. The question\n        is about objecttype. In the current implementation, it will die too.\n        But dying on objecttype breaks the default format. We have changed the\n        default format to %(objectname) %(objectsize) when\nremote-object-info is used.\n        Any suggestions on this approach?\n\n> > diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\n> > new file mode 100755\n> > index 0000000000..7a7bdfeb91\n> > --- /dev/null\n> > +++ b/t/t1017-cat-file-remote-object-info.sh\n> > ...\n> > +stop_git_daemon\n> > +\n> > +# Test --batch-command remote-object-info with 'http://' transport\n> > +\n> > +. \"$TEST_DIRECTORY\"/lib-httpd.sh\n> > +start_httpd\n>\n> start_httpd skips the remainder of the tests if it fails to start the\n> httpd server. That's why I see various other tests which have this at\n> the end:\n>\n>   # DO NOT add non-httpd-specific tests here, because the last part of this\n>   # test script is only executed when httpd is available and enabled.\n>\n> So I would suggest to add this comment as well, and move the file://\n> tests above start_httpd.\n>\n\nThank you. Fixing it in V2\n\n\n> --\n> Toon\n"},{"id":"498738","messageId":"CAN2LT1AM5rYpwjZ+rhYerxDkL6mbxr7iDc=wvuhvNKS8VVXQ8w@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZRi2w_CXovpg3h1mfMsOiLWrpS8vL+NzS5o_NwhFdysTg@mail.gmail.com","subject":"Re: [PATCH 1/6] fetch-pack: refactor packet writing","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-15T16:40:28Z","receivedAt":"2024-07-15T16:40:40Z","isPatch":true,"body":"On Wed, Jul 10, 2024 at 5:39 AM Karthik Nayak <karthik.188@gmail.com> wrote:\n>\n> Peijian Ju <eric.peijian@gmail.com> writes:\n> [snip]\n>\n> >> Right, this commit in itself looks good. But I was curious why we need\n> >> this, so I did a sneak peak into the following commits.\n> >>\n> >> To summarize, we want to call:\n> >>    `write_command_and_capabilities(..., \"object-info\");`\n> >> in the upcoming patches to get the object-info details from the server.\n> >> But isn't this function too specific to the \"fetch\" command to be\n> >> generalized to be for \"object-info\" too?\n> >>\n> >> Wouldn't it make sense to add a custom function for 'object-info' in\n> >> 'connect.c'? Like how we currently have `get_remote_bundle_uri()` for\n> >> 'bundle-uri' and `get_remote_refs` for 'ls-refs'?\n> >\n> > Thank you. I am reading through the old comments left by Taylor\n> > at https://lore.kernel.org/git/YkOPyc9tUfe2Tozx@nand.local/\n> >\n> >   \" Makes obvious sense, and this was something that jumped out to me when I\n> >   looked at the first and second versions of this patch. I'm glad that\n> >   this is getting factored out.\"\n> >\n> >\n> > It seems refactoring this into a more general function is on purpose.\n> > It is encouraged to use this general function to request capability\n> > rather than adding a custom function.\n> > Taylor’s comment was 2 years ago, but I think refactoring this into a\n> > more general function to\n> > enforce DRY still makes sense.\n>\n> It would make sense then to move the existing users to also use\n> `write_command_and_capabilities` eventually. I guess this could be done\n> in a follow up series.\n>\n> Then I would say `write_command_and_capabilities()` should be moved to\n> `transport.c`, no?\n\nThank you. I am not sure about this. Currently, the file dependency is\nlike this:\n`transport.c`  -> `fetch-pack.c` -> `connect.c`  where \"->\" means \"depends on\".\n\nMoving `write_command_and_capabilities()` to `transport.c` would make\ncircle dependency.\n\nIf we want `write_command_and_capabilities()` to be a more general\nutility function,\nit seems make more sense to move it to `connect.c`. I saw a bunch of\nthese general utility functions\nin `connect.c` such as `send_capabilities()`. Some custom functions\nsuch as `get_remote_bundle_uri()` and `get_remote_refs`also lives in\nit.\n\nPlease let me know what you think. Thanks.\n"},{"id":"498770","messageId":"CAN2LT1B2mzSEvMOaEPKWUJ2BX6G3ZVR8ZpJJGFAYpNnQA1oE8g@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZSLoHe_xii2A_Phx1NzQLPvLwWUoFhJ2bOrihGvhD6rzg@mail.gmail.com","subject":"Re: [PATCH 4/6] transport: add client support for object-info","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-16T02:39:11Z","receivedAt":"2024-07-16T02:39:22Z","isPatch":true,"body":"On Wed, Jul 10, 2024 at 6:13 AM Karthik Nayak <karthik.188@gmail.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> > From: Calvin Wan <calvinwan@google.com>\n> >\n> > Sometimes it is useful to get information about an object without having\n> > to download it completely. The server logic has already been implemented\n> > as “a2ba162cda (object-info: support for retrieving object info,\n>\n> Nit: s/as/in\n>\n\nThank you. Fixed in V2.\n\n> > 2021-04-20)”.\n> >\n> > Add client functions to communicate with the server.\n> >\n> > The client currently supports requesting a list of object ids with\n> > features 'size' and 'type' from a v2 server. If a server does not\n>\n> But do we support type? I thought we only added support for 'size'.\n>\n\nThank you. Yes, only size is supported, I will revise it.\n\n> > advertise either of the requested features, then the client falls back\n> > to making the request through 'fetch'.\n> >\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> > ---\n> >  fetch-pack.c       |  24 +++++++++++\n> >  fetch-pack.h       |  10 +++++\n> >  transport-helper.c |   8 +++-\n> >  transport.c        | 102 ++++++++++++++++++++++++++++++++++++++++++---\n> >  transport.h        |  11 +++++\n> >  5 files changed, 148 insertions(+), 7 deletions(-)\n> >\n> > diff --git a/fetch-pack.c b/fetch-pack.c\n> > index da0de9c537..d533cac1d8 100644\n> > --- a/fetch-pack.c\n> > +++ b/fetch-pack.c\n> > @@ -1345,6 +1345,27 @@ static void write_command_and_capabilities(struct strbuf *req_buf,\n> >       packet_buf_delim(req_buf);\n> >  }\n> >\n> > +void send_object_info_request(int fd_out, struct object_info_args *args)\n> > +{\n> > +     struct strbuf req_buf = STRBUF_INIT;\n> > +\n> > +     write_command_and_capabilities(&req_buf, args->server_options, \"object-info\");\n> > +\n> > +     if (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n> > +             packet_buf_write(&req_buf, \"size\");\n> > +\n> > +     if (args->oids) {\n> > +             for (size_t i = 0; i < args->oids->nr; i++)\n> > +                     packet_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n> > +     }\n> > +\n> > +     packet_buf_flush(&req_buf);\n> > +     if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n> > +             die_errno(_(\"unable to write request to remote\"));\n> > +\n> > +     strbuf_release(&req_buf);\n> > +}\n> > +\n> >  static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n> >                             struct fetch_pack_args *args,\n> >                             const struct ref *wants, struct oidset *common,\n> > @@ -1682,6 +1703,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n> >       if (args->depth > 0 || args->deepen_since || args->deepen_not)\n> >               args->deepen = 1;\n> >\n> > +     if (args->object_info)\n> > +             state = FETCH_SEND_REQUEST;\n> > +\n> >       while (state != FETCH_DONE) {\n> >               switch (state) {\n> >               case FETCH_CHECK_LOCAL:\n> > diff --git a/fetch-pack.h b/fetch-pack.h\n> > index 6775d26517..16e4dc0824 100644\n> > --- a/fetch-pack.h\n> > +++ b/fetch-pack.h\n> > @@ -16,6 +16,7 @@ struct fetch_pack_args {\n> >       const struct string_list *deepen_not;\n> >       struct list_objects_filter_options filter_options;\n> >       const struct string_list *server_options;\n> > +     struct object_info **object_info_data;\n> >\n> >       /*\n> >        * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n> > @@ -42,6 +43,7 @@ struct fetch_pack_args {\n> >       unsigned reject_shallow_remote:1;\n> >       unsigned deepen:1;\n> >       unsigned refetch:1;\n> > +     unsigned object_info:1;\n> >\n> >       /*\n> >        * Indicate that the remote of this request is a promisor remote. The\n> > @@ -68,6 +70,12 @@ struct fetch_pack_args {\n> >       unsigned connectivity_checked:1;\n> >  };\n> >\n> > +struct object_info_args {\n> > +     struct string_list *object_info_options;\n> > +     const struct string_list *server_options;\n> > +     struct oid_array *oids;\n> > +};\n> > +\n> >  /*\n> >   * sought represents remote references that should be updated from.\n> >   * On return, the names that were found on the remote will have been\n> > @@ -101,4 +109,6 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n> >   */\n> >  int report_unmatched_refs(struct ref **sought, int nr_sought);\n> >\n> > +void send_object_info_request(int fd_out, struct object_info_args *args);\n> > +\n> >  #endif\n> > diff --git a/transport-helper.c b/transport-helper.c\n> > index 9820947ab2..670d1e7068 100644\n> > --- a/transport-helper.c\n> > +++ b/transport-helper.c\n> > @@ -697,13 +697,17 @@ static int fetch_refs(struct transport *transport,\n> >\n> >       /*\n> >        * If we reach here, then the server, the client, and/or the transport\n> > -      * helper does not support protocol v2. --negotiate-only requires\n> > -      * protocol v2.\n> > +      * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n> > +      * require protocol v2.\n> >        */\n> >       if (data->transport_options.acked_commits) {\n> >               warning(_(\"--negotiate-only requires protocol v2\"));\n> >               return -1;\n> >       }\n> > +     if (transport->smart_options->object_info) {\n> > +             // fail the command explicitly to avoid further commands input\n> > +             die(_(\"remote-object-info requires protocol v2\"));\n> > +     }\n> >\n> >       if (!data->get_refs_list_called)\n> >               get_refs_list_using_list(transport, 0);\n> > diff --git a/transport.c b/transport.c\n> > index 83ddea8fbc..2847aa3f3c 100644\n> > --- a/transport.c\n> > +++ b/transport.c\n> > @@ -363,6 +363,73 @@ static struct ref *handshake(struct transport *transport, int for_push,\n> >       return refs;\n> >  }\n> >\n> > +static int fetch_object_info(struct transport *transport, struct object_info **object_info_data)\n> > +{\n> > +     int size_index = -1;\n> > +     struct git_transport_data *data = transport->data;\n> > +     struct object_info_args args;\n> > +     struct packet_reader reader;\n> > +\n> > +     memset(&args, 0, sizeof(args));\n>\n> Nit: we could `struct object_info_args args = { 0 };` above instead.\n\nThank you. Your suggestion has better readability and maintainability.\nI am adopting it in V2.\n\n>\n> > +     args.server_options = transport->server_options;\n> > +     args.object_info_options = transport->smart_options->object_info_options;\n> > +     args.oids = transport->smart_options->object_info_oids;\n> > +\n> > +     connect_setup(transport, 0);\n> > +     packet_reader_init(&reader, data->fd[0], NULL, 0,\n> > +                     PACKET_READ_CHOMP_NEWLINE |\n> > +                     PACKET_READ_GENTLE_ON_EOF |\n> > +                     PACKET_READ_DIE_ON_ERR_PACKET);\n> > +     data->version = discover_version(&reader);\n> > +\n> > +     transport->hash_algo = reader.hash_algo;\n> > +\n> > +     switch (data->version) {\n> > +     case protocol_v2:\n> > +             if (!server_supports_v2(\"object-info\"))\n> > +                     return -1;\n> > +             if (unsorted_string_list_has_string(args.object_info_options, \"size\")\n> > +                             && !server_supports_feature(\"object-info\", \"size\", 0)) {\n> > +                     return -1;\n> > +             }\n> > +             send_object_info_request(data->fd[1], &args);\n> > +             break;\n> > +     case protocol_v1:\n> > +     case protocol_v0:\n> > +             die(_(\"wrong protocol version. expected v2\"));\n> > +     case protocol_unknown_version:\n> > +             BUG(\"unknown protocol version\");\n> > +     }\n> > +\n> > +     for (size_t i = 0; i < args.object_info_options->nr; i++) {\n> > +             if (packet_reader_read(&reader) != PACKET_READ_NORMAL) {\n> > +                     check_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n> > +                     return -1;\n> > +             }\n> > +             if (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n> > +                     if (!strcmp(reader.line, \"size\"))\n> > +                             size_index = i;\n> > +                     continue;\n> > +             }\n> > +             return -1;\n> > +     }\n> > +\n> > +     for (size_t i = 0; packet_reader_read(&reader) == PACKET_READ_NORMAL && i < args.oids->nr; i++){\n> > +             struct string_list object_info_values = STRING_LIST_INIT_DUP;\n>\n> We need to also call `string_list_clear()` at the end of this block.\n>\n> > +\n> > +             string_list_split(&object_info_values, reader.line, ' ', -1);\n> > +             if (0 <= size_index) {\n> > +                     if (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n> > +                             die(\"object-info: not our ref %s\",\n> > +                                     object_info_values.items[0].string);\n> > +                     *(*object_info_data)[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n>\n> Perhaps `*object_info_data[i]->sizep =\n> strtoul(object_info_values.items[1 + size_index].string, NULL, 10);`?\n>\n> So, this is allocated in 'cat-file' and set here? Wouldn't it be nicer\n> to also do the alloc here?\n>\n\n\n\n  > > Perhaps `*object_info_data[i]->sizep =\n  > > strtoul(object_info_values.items[1 + size_index].string, NULL, 10);`?\n\nThank you.\n\nSeems that `*(*object_info_data)[i].sizep`  and\n`object_info_data[i]->sizep` are not the same.\n\nGiven object_info_data is a pointer to a pointer to struct\nobject_info,  what `*(*object_info_data)[i].sizep` does is\n\n1. *object_info_data dereferences object_info_data, yielding a pointer\nto the first element of the array of struct object_info.\n2. (*object_info_data)[i] accesses the i-th element in the array of\nstruct object_info that *object_info_data points to.\n4, (*object_info_data)[i].sizep accesses the sizep member of the i-th\nstruct object_info.\n5. *(*object_info_data)[i].sizep dereferences the sizep pointer,\nyielding the value it points to.\n\nSo we are interested in the array of struct object_info with its first\nelement at *object_info_data. A more intuitive way of thinking it is\nthat if we think object_info_data as a 2-D array,\n*(*object_info_data)[i] is accessing the  object_info_data[0][i].\n\nFor `*object_info_data[i]->sizep`:\n\n1. object_info_data[i] accesses the i-th element in the array of\npointers to struct object_info.\n2. object_info_data[i]->sizep accesses the sizep member of the i-th\nstruct object_info that object_info_data[i] points to.\n3. *object_info_data[i]->sizep dereferences the sizep pointer,\nyielding the value it points to.\n\n*object_info_data[i]->sizep will treat object_info_data as an array of\npointers. In the mental model of 2D array, *object_info_data[i] is\nlike object_info_data[i][0]\n\nNevertheless, I do think using a pointer to a pointer is tricky and\nerror-prone. In V2, I am refactoring the code to use just a pointer\ninstead of a pointer to a pointer.  For example, in transport.h\n\n    git_transport_options {\n        ...\n        struct object_info *object_info_data;\n        ...\n    }\n\n\n  > > So, this is allocated in 'cat-file' and set here? Wouldn't it be nicer\n  > > to also do the alloc here?\n\nThank you.\nYes, this makes sense, V2 is refactoring the allocation into\n`fetch_object_info()` in transport.c\n\n> > +             }\n> > +     }\n> > +     check_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n> > +\n> > +     return 0;\n> > +}\n> > +\n> >  static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n> >                                       struct transport_ls_refs_options *options)\n> >  {\n> > @@ -410,6 +477,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >       struct ref *refs = NULL;\n> >       struct fetch_pack_args args;\n> >       struct ref *refs_tmp = NULL;\n> > +     struct ref *object_info_refs = xcalloc(1, sizeof (struct ref));\n> >\n> >       memset(&args, 0, sizeof(args));\n> >       args.uploadpack = data->options.uploadpack;\n> > @@ -436,11 +504,27 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >       args.server_options = transport->server_options;\n> >       args.negotiation_tips = data->options.negotiation_tips;\n> >       args.reject_shallow_remote = transport->smart_options->reject_shallow;\n> > -\n> > -     if (!data->finished_handshake) {\n> > -             int i;\n> > +     args.object_info = transport->smart_options->object_info;\n> > +\n> > +     if (transport->smart_options && transport->smart_options->object_info) {\n> > +             struct ref *ref = object_info_refs;\n> > +\n> > +             if (!fetch_object_info(transport, data->options.object_info_data))\n> > +                     goto cleanup;\n> > +             args.object_info_data = data->options.object_info_data;\n> > +             args.quiet = 1;\n> > +             args.no_progress = 1;\n> > +             for (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> > +                     struct ref *temp_ref = xcalloc(1, sizeof (struct ref));\n> > +                     temp_ref->old_oid = *(transport->smart_options->object_info_oids->oid + i);\n> > +                     temp_ref->exact_oid = 1;\n> > +                     ref->next = temp_ref;\n> > +                     ref = ref->next;\n> > +             }\n> > +             transport->remote_refs = object_info_refs->next;\n> > +     } else if (!data->finished_handshake) {\n> >               int must_list_refs = 0;\n> > -             for (i = 0; i < nr_heads; i++) {\n> > +             for (int i = 0; i < nr_heads; i++) {\n> >                       if (!to_fetch[i]->exact_oid) {\n> >                               must_list_refs = 1;\n> >                               break;\n> > @@ -478,11 +562,18 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >                         &transport->pack_lockfiles, data->version);\n> >\n> >       data->finished_handshake = 0;\n> > +     if (args.object_info) {\n> > +             struct ref *ref_cpy_reader = object_info_refs->next;\n> > +             for (int i = 0; ref_cpy_reader; i++) {\n> > +                     oid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &(*args.object_info_data)[i], OBJECT_INFO_LOOKUP_REPLACE);\n> > +                     ref_cpy_reader = ref_cpy_reader->next;\n> > +             }\n> > +     }\n> >       data->options.self_contained_and_connected =\n> >               args.self_contained_and_connected;\n> >       data->options.connectivity_checked = args.connectivity_checked;\n> >\n> > -     if (!refs)\n> > +     if (!refs && !args.object_info)\n> >               ret = -1;\n> >       if (report_unmatched_refs(to_fetch, nr_heads))\n> >               ret = -1;\n> > @@ -498,6 +589,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >       free_refs(refs_tmp);\n> >       free_refs(refs);\n> >       list_objects_filter_release(&args.filter_options);\n> > +     free_refs(object_info_refs);\n>\n> Shouldn't we loop through `object_info_refs->next` and free all of them ?\n>\n\nThank you. I think free_refs() has the logic to loop through\nobject_info_refs->next and feel the linked list.\n\n\n> >       return ret;\n> >  }\n> >\n> > diff --git a/transport.h b/transport.h\n> > index 6393cd9823..5a3cda1860 100644\n> > --- a/transport.h\n> > +++ b/transport.h\n> > @@ -5,6 +5,7 @@\n> >  #include \"remote.h\"\n> >  #include \"list-objects-filter-options.h\"\n> >  #include \"string-list.h\"\n> > +#include \"object-store.h\"\n> >\n> >  struct git_transport_options {\n> >       unsigned thin : 1;\n> > @@ -30,6 +31,12 @@ struct git_transport_options {\n> >        */\n> >       unsigned connectivity_checked:1;\n> >\n> > +     /*\n> > +      * Transport will attempt to pull only object-info. Fallbacks\n> > +      * to pulling entire object if object-info is not supported.\n> > +      */\n> > +     unsigned object_info : 1;\n> > +\n> >       int depth;\n> >       const char *deepen_since;\n> >       const struct string_list *deepen_not;\n> > @@ -53,6 +60,10 @@ struct git_transport_options {\n> >        * common commits to this oidset instead of fetching any packfiles.\n> >        */\n> >       struct oidset *acked_commits;\n> > +\n> > +     struct oid_array *object_info_oids;\n> > +     struct object_info **object_info_data;\n> > +     struct string_list *object_info_options;\n> >  };\n> >\n> >  enum transport_family {\n> > --\n> > 2.45.2\n>\n> I wondering if we can add tests at this stage.\n\nThank you. V2 is adding more tests to cover this.\n"},{"id":"498771","messageId":"CAN2LT1A2c6M21GtEhnBN_bNYOW7csKOgK9fK4-f1HKPa-Vb28w@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZRH7=Uj77Wd4WQW=KR8ppKw6nXZRH7pNZpHBPz4HhX8MQ@mail.gmail.com","subject":"Re: [PATCH 5/6] cat-file: add declaration of variable i inside its for loop","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-16T02:59:32Z","receivedAt":"2024-07-16T02:59:44Z","isPatch":true,"body":"On Wed, Jul 10, 2024 at 6:16 AM Karthik Nayak <karthik.188@gmail.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> > Some code declares variable i and only uses it\n> > in a for loop, not in any other logic outside the loop.\n> >\n> > Change the declaration of i to be inside the for loop for readability.\n> >\n>\n> If we're doing this anyways, we could replace the 'int' with 'size_t'\n> too.\n>\n\nThank you. Fixed in V2\n\n> [snip]\n"},{"id":"498810","messageId":"CAN2LT1BK=FzmxDv81v89dxCYxMMhsGt4T-kBRRdeh2x0pns_Lw@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZRL8ByTi3+8ej1Ugh75CU5Rx2grGpNx5ESKOJCrEYg2XA@mail.gmail.com","subject":"Re: [PATCH 6/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-17T02:38:24Z","receivedAt":"2024-07-17T02:38:37Z","isPatch":true,"body":"On Wed, Jul 10, 2024 at 8:08 AM Karthik Nayak <karthik.188@gmail.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> > From: Calvin Wan <calvinwan@google.com>\n> >\n> > Since the `info` command in cat-file --batch-command prints object info\n> > for a given object, it is natural to add another command in cat-file\n> > --batch-command to print object info for a given object from a remote.\n> > Add `remote-object-info` to cat-file --batch-command.\n> >\n> > While `info` takes object ids one at a time, this creates overhead when\n> > making requests to a server so `remote-object-info` instead can take\n> > multiple object ids at once.\n> >\n> > cat-file --batch-command is generally implemented in the following\n> > manner:\n> >\n> >  - Receive and parse input from user\n>\n> So this refers input delimited by newline or '\\0'.\n>\n\nThank you. The input should take both newline and '\\0' into\nconsideration. We are missing some test coverage on '\\0' delimited\ninput though. I am adding them in V2\n\n> >  - Call respective function attached to command\n> >  - Set batch mode state, get object info, print object info\n> >\n>\n> Doesn't the batch mode get set before the input parsing begins?\n>\n\nThank you. Yes, I am also unsure what Calvin \"Set batch mode state\"\nmeans here. This batch mode is determined when the cat-file command is\ncalled.  But I do see  `opt->batch_mode = BATCH_MODE_INFO;` in\n`parse_cmd_info and()` and\n`opt->batch_mode = BATCH_MODE_CONTENTS;` in `parse_cmd_contents()` I\nguess that is what Calvin refers to\n\nAnyway, I am removing \"Set batch mode state\" in V2 to avoid confusion,\nIt seems too detailed.\n\n> > In --buffer mode, this changes to:\n> >\n> >  - Receive and parse input from user\n> >  - Store respective function attached to command in a queue\n> >  - After flush, loop through commands in queue\n> >     - Call respective function attached to command\n> >     - Set batch mode state, get object info, print object info\n> >\n> > Notice how the getting and printing of object info is accomplished one\n> > at a time. As described above, this creates a problem for making\n> > requests to a server. Therefore, `remote-object-info` is implemented in\n> > the following manner:\n> >\n> >  - Receive and parse input from user\n> >  If command is `remote-object-info`:\n> >     - Get object info from remote\n> >     - Loop through object info\n> >         - Call respective function attached to `info`\n> >         - Set batch mode state, use passed in object info, print object\n> >           info\n> >  Else:\n> >     - Call respective function attached to command\n> >     - Parse input, get object info, print object info\n> >\n>\n> So this is because we want 'remote-object-info' to also use\n> 'parse_cmd_info' similar to 'info'. But I'm not understanding why,\n> especially since 'parse_cmd_info' calls 'batch_one_object', and we skip\n> most of that code for 'remote-object-info'.\n>\n> Wouldn't it be cleaner to just define our own 'batch_remote_object' and\n> create 'parse_cmd_remote_info' ?\n>\n\nThank you. That makes sense. Actually, I am pushing it a bit further in V2:\n1. The interface of parse_remote_info() is changed to parse_cmd_fn_t,\nand its name is changed to `parse_cmd_remote_info()`.\n2. In `static const struct parse_cmd{...} commands[]`, the\n\"remote-object-info\" is attached with parse_cmd_remote_info() directly\n3. In side parse_cmd_remote_info, we don't need\n`batch_remote_object()`, all we need is just `batch_object_write()` to\nprint the object info out. That will simply the code a lot.\n\nWe don't need to call parse_cmd_info; also, we can get rid of the name\ncompare logic, i.e. `if (!strcmp(cmd[i].name, \"remote-object-info\"))\n...`\n\n\n> > And finally for --buffer mode `remote-object-info`:\n> >  - Receive and parse input from user\n> >  - Store respective function attached to command in a queue\n> >  - After flush, loop through commands in queue:\n> >     If command is `remote-object-info`:\n> >         - Get object info from remote\n> >         - Loop through object info\n> >             - Call respective function attached to `info`\n> >             - Set batch mode state, use passed in object info, print\n> >               object info\n> >     Else:\n> >         - Call respective function attached to command\n> >         - Set batch mode state, get object info, print object info\n> >\n> > To summarize, `remote-object-info` gets object info from the remote and\n> > then generates multiple `info` commands with the object info passed in.\n> >\n> > In order for remote-object-info to avoid remote communication overhead\n> > in the non-buffer mode, the objects are passed in as such:\n> >\n> > remote-object-info <remote> <oid> <oid> ... <oid>\n> >\n> > rather than\n> >\n> > remote-object-info <remote> <oid>\n> > remote-object-info <remote> <oid>\n> > ...\n> > remote-object-info <remote> <oid>\n> >\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> > ---\n> >  Documentation/git-cat-file.txt         |  22 +-\n> >  builtin/cat-file.c                     | 231 ++++++++++----\n> >  object-file.c                          |  11 +\n> >  object-store-ll.h                      |   3 +\n> >  t/t1017-cat-file-remote-object-info.sh | 412 +++++++++++++++++++++++++\n> >  5 files changed, 620 insertions(+), 59 deletions(-)\n> >  create mode 100755 t/t1017-cat-file-remote-object-info.sh\n> >\n> > diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\n> > index bd95a6c10a..ab0647bb39 100644\n> > --- a/Documentation/git-cat-file.txt\n> > +++ b/Documentation/git-cat-file.txt\n> > @@ -149,6 +149,12 @@ info <object>::\n> >       Print object info for object reference `<object>`. This corresponds to the\n> >       output of `--batch-check`.\n> >\n> > +remote-object-info <remote> <object>...::\n> > +     Print object info for object references `<object>` at specified <remote> without\n> > +     downloading object from remote.\n> > +     Error when no object references is provided.\n> > +     This command may be combined with `--buffer`.\n> > +\n> >  flush::\n> >       Used with `--buffer` to execute all preceding commands that were issued\n> >       since the beginning or since the last flush was issued. When `--buffer`\n> > @@ -290,7 +296,8 @@ newline. The available atoms are:\n> >       The full hex representation of the object name.\n> >\n> >  `objecttype`::\n> > -     The type of the object (the same as `cat-file -t` reports).\n> > +     The type of the object (the same as `cat-file -t` reports). See\n> > +     `CAVEATS` below. Not supported by `remote-object-info`.\n> >\n> >  `objectsize`::\n> >       The size, in bytes, of the object (the same as `cat-file -s`\n> > @@ -298,13 +305,14 @@ newline. The available atoms are:\n> >\n> >  `objectsize:disk`::\n> >       The size, in bytes, that the object takes up on disk. See the\n> > -     note about on-disk sizes in the `CAVEATS` section below.\n> > +     note about on-disk sizes in the `CAVEATS` section below. Not\n> > +     supported by `remote-object-info`.\n> >\n> >  `deltabase`::\n> >       If the object is stored as a delta on-disk, this expands to the\n> >       full hex representation of the delta base object name.\n> >       Otherwise, expands to the null OID (all zeroes). See `CAVEATS`\n> > -     below.\n> > +     below. Not supported by `remote-object-info`.\n> >\n> >  `rest`::\n> >       If this atom is used in the output string, input lines are split\n> > @@ -314,7 +322,9 @@ newline. The available atoms are:\n> >       line) are output in place of the `%(rest)` atom.\n> >\n> >  If no format is specified, the default format is `%(objectname)\n> > -%(objecttype) %(objectsize)`.\n> > +%(objecttype) %(objectsize)`, except remote-object-info command who uses\n> > +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> > +When \"%(objecttype)\" is supported, default format should be unified.\n> >\n> >  If `--batch` is specified, or if `--batch-command` is used with the `contents`\n> >  command, the object information is followed by the object contents (consisting\n> > @@ -396,6 +406,10 @@ scripting purposes.\n> >  CAVEATS\n> >  -------\n> >\n> > +Note that since objecttype, objectsize:disk and deltabase are currently not supported by the\n> > +remote-object-info, git will error and exit when they are in the format string.\n> > +\n> > +\n> >  Note that the sizes of objects on disk are reported accurately, but care\n> >  should be taken in drawing conclusions about which refs or objects are\n> >  responsible for disk usage. The size of a packed non-delta object may be\n> > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > index 72a78cdc8c..34958a1747 100644\n> > --- a/builtin/cat-file.c\n> > +++ b/builtin/cat-file.c\n> > @@ -24,6 +24,9 @@\n> >  #include \"promisor-remote.h\"\n> >  #include \"mailmap.h\"\n> >  #include \"write-or-die.h\"\n> > +#include \"alias.h\"\n> > +#include \"remote.h\"\n> > +#include \"transport.h\"\n> >\n> >  enum batch_mode {\n> >       BATCH_MODE_CONTENTS,\n> > @@ -42,9 +45,14 @@ struct batch_options {\n> >       char input_delim;\n> >       char output_delim;\n> >       const char *format;\n> > +     int use_remote_info;\n> >  };\n> >\n> > +#define DEFAULT_FORMAT \"%(objectname) %(objecttype) %(objectsize)\"\n> > +\n> >  static const char *force_path;\n> > +static struct object_info *remote_object_info;\n> > +static struct oid_array object_info_oids = OID_ARRAY_INIT;\n> >\n> >  static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n> >  static int use_mailmap;\n> > @@ -508,7 +516,6 @@ static void batch_object_write(const char *obj_name,\n> >       }\n> >\n> >       batch_write(opt, scratch->buf, scratch->len);\n> > -\n>\n> Nit: why remove this?\n>\n\nAdding it back. Probably caused when resolving conflicts.\n\n> >       if (opt->batch_mode == BATCH_MODE_CONTENTS) {\n> >               print_object_or_die(opt, data);\n> >               batch_write(opt, &opt->output_delim, 1);\n> > @@ -526,51 +533,118 @@ static void batch_one_object(const char *obj_name,\n> >               (opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0);\n> >       enum get_oid_result result;\n> >\n> > -     result = get_oid_with_context(the_repository, obj_name,\n> > -                                   flags, &data->oid, &ctx);\n> > -     if (result != FOUND) {\n> > -             switch (result) {\n> > -             case MISSING_OBJECT:\n> > -                     printf(\"%s missing%c\", obj_name, opt->output_delim);\n> > -                     break;\n> > -             case SHORT_NAME_AMBIGUOUS:\n> > -                     printf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n> > -                     break;\n> > -             case DANGLING_SYMLINK:\n> > -                     printf(\"dangling %\"PRIuMAX\"%c%s%c\",\n> > -                            (uintmax_t)strlen(obj_name),\n> > -                            opt->output_delim, obj_name, opt->output_delim);\n> > -                     break;\n> > -             case SYMLINK_LOOP:\n> > -                     printf(\"loop %\"PRIuMAX\"%c%s%c\",\n> > -                            (uintmax_t)strlen(obj_name),\n> > -                            opt->output_delim, obj_name, opt->output_delim);\n> > -                     break;\n> > -             case NOT_DIR:\n> > -                     printf(\"notdir %\"PRIuMAX\"%c%s%c\",\n> > -                            (uintmax_t)strlen(obj_name),\n> > -                            opt->output_delim, obj_name, opt->output_delim);\n> > -                     break;\n> > -             default:\n> > -                     BUG(\"unknown get_sha1_with_context result %d\\n\",\n> > -                            result);\n> > -                     break;\n> > +     if (!opt->use_remote_info) {\n> > +             result = get_oid_with_context(the_repository, obj_name,\n> > +                                             flags, &data->oid, &ctx);\n> > +             if (result != FOUND) {\n> > +                     switch (result) {\n> > +                     case MISSING_OBJECT:\n> > +                             printf(\"%s missing%c\", obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case SHORT_NAME_AMBIGUOUS:\n> > +                             printf(\"%s ambiguous%c\", obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case DANGLING_SYMLINK:\n> > +                             printf(\"dangling %\"PRIuMAX\"%c%s%c\",\n> > +                                     (uintmax_t)strlen(obj_name),\n> > +                                     opt->output_delim, obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case SYMLINK_LOOP:\n> > +                             printf(\"loop %\"PRIuMAX\"%c%s%c\",\n> > +                                     (uintmax_t)strlen(obj_name),\n> > +                                     opt->output_delim, obj_name, opt->output_delim);\n> > +                             break;\n> > +                     case NOT_DIR:\n> > +                             printf(\"notdir %\"PRIuMAX\"%c%s%c\",\n> > +                                     (uintmax_t)strlen(obj_name),\n> > +                                     opt->output_delim, obj_name, opt->output_delim);\n> > +                             break;\n> > +                     default:\n> > +                             BUG(\"unknown get_sha1_with_context result %d\\n\",\n> > +                                     result);\n> > +                             break;\n> > +                     }\n> > +                     fflush(stdout);\n> > +                     return;\n> >               }\n> > -             fflush(stdout);\n> > -             return;\n> > -     }\n> >\n> > -     if (ctx.mode == 0) {\n> > -             printf(\"symlink %\"PRIuMAX\"%c%s%c\",\n> > -                    (uintmax_t)ctx.symlink_path.len,\n> > -                    opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n> > -             fflush(stdout);\n> > -             return;\n> > +             if (ctx.mode == 0) {\n> > +                     printf(\"symlink %\"PRIuMAX\"%c%s%c\",\n> > +                             (uintmax_t)ctx.symlink_path.len,\n> > +                             opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n> > +                     fflush(stdout);\n> > +                     return;\n> > +             }\n> >       }\n> >\n> >       batch_object_write(obj_name, scratch, opt, data, NULL, 0);\n> >  }\n> >\n> > +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> > +{\n> > +     int retval = 0;\n> > +     struct remote *remote = NULL;\n>\n> We need to call `remote_clear()` on this at the end.\n>\n\nThank you. I am not sure about this.\n\nIt seems the remote is cached in a hashmap see\nhttps://git.kernel.org/pub/scm/git/git.git/tree/remote.c#n136.\nWhen multiple commands are sent, the remote can be reused from the\nhashmap cache.\n\nThe life cycle of this hashmap cache seems managed by\n\"the_repository\", see\nhttps://git.kernel.org/pub/scm/git/git.git/tree/remote.c#n720 and\nhttps://git.kernel.org/pub/scm/git/git.git/tree/repository.c#n364.\n\n> > +     struct object_id oid;\n> > +     struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n>\n> This needs to be cleared.\n>\n\nThank you. Fixed in V2.\n\n> > +     static struct transport *gtransport;\n>\n> Shouldn't we call `transport_disconnect(transport);`?\n>\n\nThank you. transport_disconnect(transport) is added the end of\nget_remote_info() in V2.\n\n> > +     /*\n> > +      * Change the format to \"%(objectname) %(objectsize)\" when\n> > +      * remote-object-info command is used. Once we start supporting objecttype\n> > +      * the default format should change to DEFAULT_FORMAT\n> > +     */\n> > +     if (!opt->format) {\n> > +             opt->format = \"%(objectname) %(objectsize)\";\n> > +     }\n> > +\n> > +     remote = remote_get(argv[0]);\n> > +     if (!remote)\n> > +             die(_(\"must supply valid remote when using remote-object-info\"));\n> > +     oid_array_clear(&object_info_oids);\n> > +     for (size_t i = 1; i < argc; i++) {\n> > +             if (get_oid_hex(argv[i], &oid))\n> > +                     die(_(\"Not a valid object name %s\"), argv[i]);\n> > +             oid_array_append(&object_info_oids, &oid);\n> > +     }\n> > +\n> > +     gtransport = transport_get(remote, NULL);\n> > +     if (gtransport->smart_options) {\n> > +             int include_size = 0;\n> > +\n> > +             CALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> > +             gtransport->smart_options->object_info = 1;\n> > +             gtransport->smart_options->object_info_oids = &object_info_oids;\n> > +             /*\n> > +              * 'size' is the only option currently supported.\n> > +              * Other options that are passed in the format will exit with error.\n> > +              */\n> > +             if (strstr(opt->format, \"%(objectsize)\")) {\n> > +                     string_list_append(&object_info_options, \"size\");\n> > +                     include_size = 1;\n> > +             }\n> > +             if (strstr(opt->format, \"%(objecttype)\")) {\n> > +                     die(_(\"objecttype is currently not supported with remote-object-info\"));\n> > +             }\n> > +             if (strstr(opt->format, \"%(objectsize:disk)\"))\n> > +                     die(_(\"objectsize:disk is currently not supported with remote-object-info\"));\n> > +             if (strstr(opt->format, \"%(deltabase)\"))\n> > +                     die(_(\"deltabase is currently not supported with remote-object-info\"));\n> >\n>\n> This whole block could be replaced by an else..\n>\n> if (strstr(opt->format, \"%(objectsize)\")) {\n>         string_list_append(&object_info_options, \"size\");\n>         include_size = 1;\n> } else {\n>         die(_(\"%s is currently not supported with remote-object-info\", opt->format));\n> }\n>\n\nThank you. Revised in V2.\n\n> > +             if (object_info_options.nr > 0) {\n> > +                     gtransport->smart_options->object_info_options = &object_info_options;\n> > +                     for (size_t i = 0; i < object_info_oids.nr; i++) {\n> > +                             if (include_size)\n> > +                                     remote_object_info[i].sizep = xcalloc(1, sizeof(long));\n> > +                     }\n> > +                     gtransport->smart_options->object_info_data = &remote_object_info;\n> > +                     retval = transport_fetch_refs(gtransport, NULL);\n> > +             }\n> > +     } else {\n> > +             retval = -1;\n> > +     }\n> > +\n> > +     return retval;\n> > +}\n> > +\n> >  struct object_cb_data {\n> >       struct batch_options *opt;\n> >       struct expand_data *expand;\n> > @@ -642,6 +716,7 @@ typedef void (*parse_cmd_fn_t)(struct batch_options *, const char *,\n> >  struct queued_cmd {\n> >       parse_cmd_fn_t fn;\n> >       char *line;\n> > +     const char *name;\n> >  };\n> >\n> >  static void parse_cmd_contents(struct batch_options *opt,\n> > @@ -662,6 +737,55 @@ static void parse_cmd_info(struct batch_options *opt,\n> >       batch_one_object(line, output, opt, data);\n> >  }\n> >\n> > +static const struct parse_cmd {\n> > +     const char *name;\n> > +     parse_cmd_fn_t fn;\n> > +     unsigned takes_args;\n> > +} commands[] = {\n> > +     { \"contents\", parse_cmd_contents, 1 },\n> > +     { \"info\", parse_cmd_info, 1 },\n> > +     { \"remote-object-info\", parse_cmd_info, 1 },\n> > +     { \"flush\", NULL, 0 },\n> > +};\n> > +\n> > +static void parse_remote_info(struct batch_options *opt,\n> > +                        char *line,\n> > +                        struct strbuf *output,\n> > +                        struct expand_data *data,\n> > +                        const struct parse_cmd *p_cmd,\n> > +                        struct queued_cmd *q_cmd)\n> > +{\n> > +     int count;\n> > +     const char **argv;\n> > +\n> > +     count = split_cmdline(line, &argv);\n> > +     if (get_remote_info(opt, count, argv))\n> > +             goto cleanup;\n> > +     opt->use_remote_info = 1;\n> > +     data->skip_object_info = 1;\n> > +     data->mark_query = 0;\n> > +     for (size_t i = 0; i < object_info_oids.nr; i++) {\n> > +             if (remote_object_info[i].sizep)\n> > +                     data->size = *remote_object_info[i].sizep;\n> > +             if (remote_object_info[i].typep)\n> > +                     data->type = *remote_object_info[i].typep;\n> > +\n>\n> We don't even set the type, so this shouldn't ever be possible right?\n>\n\nThank you. Yes, that is right. Remove that in V2.\n\n> > +             data->oid = object_info_oids.oid[i];\n> > +             if (p_cmd)\n> > +                     p_cmd->fn(opt, argv[i+1], output, data);\n> > +             else\n> > +                     q_cmd->fn(opt, argv[i+1], output, data);\n> > +     }\n> > +     opt->use_remote_info = 0;\n> > +     data->skip_object_info = 0;\n> > +     data->mark_query = 1;\n> > +\n> > +cleanup:\n> > +     for (size_t i = 0; i < object_info_oids.nr; i++)\n> > +             free_object_info_contents(&remote_object_info[i]);\n> > +     free(remote_object_info);\n>\n> argv needs to free'd too\n>\nThank you. Added in V2.\n\n> > +}\n> > +\n> >  static void dispatch_calls(struct batch_options *opt,\n> >               struct strbuf *output,\n> >               struct expand_data *data,\n> > @@ -671,8 +795,12 @@ static void dispatch_calls(struct batch_options *opt,\n> >       if (!opt->buffer_output)\n> >               die(_(\"flush is only for --buffer mode\"));\n> >\n> > -     for (int i = 0; i < nr; i++)\n> > -             cmd[i].fn(opt, cmd[i].line, output, data);\n> > +     for (int i = 0; i < nr; i++) {\n> > +             if (!strcmp(cmd[i].name, \"remote-object-info\"))\n> > +                     parse_remote_info(opt, cmd[i].line, output, data, NULL, &cmd[i]);\n> > +             else\n> > +                     cmd[i].fn(opt, cmd[i].line, output, data);\n> > +     }\n> >\n> >       fflush(stdout);\n> >  }\n> > @@ -685,17 +813,6 @@ static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n> >       *nr = 0;\n> >  }\n> >\n> > -\n> > -static const struct parse_cmd {\n> > -     const char *name;\n> > -     parse_cmd_fn_t fn;\n> > -     unsigned takes_args;\n> > -} commands[] = {\n> > -     { \"contents\", parse_cmd_contents, 1},\n> > -     { \"info\", parse_cmd_info, 1},\n> > -     { \"flush\", NULL, 0},\n> > -};\n> > -\n> >  static void batch_objects_command(struct batch_options *opt,\n> >                                   struct strbuf *output,\n> >                                   struct expand_data *data)\n> > @@ -740,11 +857,17 @@ static void batch_objects_command(struct batch_options *opt,\n> >                       dispatch_calls(opt, output, data, queued_cmd, nr);\n> >                       free_cmds(queued_cmd, &nr);\n> >               } else if (!opt->buffer_output) {\n> > -                     cmd->fn(opt, p, output, data);\n> > +                     if (!strcmp(cmd->name, \"remote-object-info\")) {\n> > +                             char *line = xstrdup_or_null(p);\n>\n> This needs to be free'd.\n>\n\nThank you. This line is removed in V2, but free() is added in the new\ncode in `parse_cmd_remote_info()`\n\n> > +                             parse_remote_info(opt, line, output, data, cmd, NULL);\n>\n>\n>\n> > +                     } else {\n> > +                             cmd->fn(opt, p, output, data);\n> > +                     }\n> >               } else {\n> >                       ALLOC_GROW(queued_cmd, nr + 1, alloc);\n> >                       call.fn = cmd->fn;\n> >                       call.line = xstrdup_or_null(p);\n> > +                     call.name = cmd->name;\n> >                       queued_cmd[nr++] = call;\n> >               }\n> >       }\n> > @@ -761,8 +884,6 @@ static void batch_objects_command(struct batch_options *opt,\n> >       strbuf_release(&input);\n> >  }\n> >\n> > -#define DEFAULT_FORMAT \"%(objectname) %(objecttype) %(objectsize)\"\n> > -\n> >  static int batch_objects(struct batch_options *opt)\n> >  {\n> >       struct strbuf input = STRBUF_INIT;\n> > diff --git a/object-file.c b/object-file.c\n> > index d3cf4b8b2e..6aaa167942 100644\n> > --- a/object-file.c\n> > +++ b/object-file.c\n> > @@ -2988,3 +2988,14 @@ int read_loose_object(const char *path,\n> >               munmap(map, mapsize);\n> >       return ret;\n> >  }\n> > +\n> > +void free_object_info_contents(struct object_info *object_info)\n> > +{\n> > +     if (!object_info)\n> > +             return;\n> > +     free(object_info->typep);\n> > +     free(object_info->sizep);\n> > +     free(object_info->disk_sizep);\n> > +     free(object_info->delta_base_oid);\n> > +     free(object_info->type_name);\n> > +}\n> > diff --git a/object-store-ll.h b/object-store-ll.h\n> > index c5f2bb2fc2..333e19cd1e 100644\n> > --- a/object-store-ll.h\n> > +++ b/object-store-ll.h\n> > @@ -533,4 +533,7 @@ int for_each_object_in_pack(struct packed_git *p,\n> >  int for_each_packed_object(each_packed_object_fn, void *,\n> >                          enum for_each_object_flags flags);\n> >\n> > +/* Free pointers inside of object_info, but not object_info itself */\n> > +void free_object_info_contents(struct object_info *object_info);\n> > +\n> >  #endif /* OBJECT_STORE_LL_H */\n> > diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\n> > new file mode 100755\n> > index 0000000000..7a7bdfeb91\n> > --- /dev/null\n> > +++ b/t/t1017-cat-file-remote-object-info.sh\n> > @@ -0,0 +1,412 @@\n> > +#!/bin/sh\n> > +\n> > +test_description='git cat-file --batch-command with remote-object-info command'\n> > +\n> > +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> > +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> > +\n> > +. ./test-lib.sh\n> > +\n> > +echo_without_newline () {\n> > +    printf '%s' \"$*\"\n> > +}\n> > +\n> > +strlen () {\n> > +    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> > +}\n> > +\n> > +hello_content=\"Hello World\"\n> > +hello_size=$(strlen \"$hello_content\")\n> > +hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n> > +\n> > +tree_size=$(($(test_oid rawsz) + 13))\n> > +\n> > +commit_message=\"Initial commit\"\n> > +commit_size=$(($(test_oid hexsz) + 137))\n> >\n>\n> Why 13 and 137?\n>\n\nThank you. That is tricky. Originally I took them from\nt/t1006-cat-file.sh. I did some research though.\n\n13 = <file mode> + <a_space> + <file name> + <a_null>, where\nfile mode is 100644, which is 6 characters;\nfile name is hello, which is 5 characters\na space is 1 character and a null is 1 character\n\nFor commit message, here is the raw content\n\ntree 6241ab2a5314798183b5c4ee8a7b0ccd12c651e6\nauthor A U Thor <author@example.com> 1112354055 +0200\ncommitter C O Mitter <committer@example.com> 1112354055 +0200\n\nInitial commit\n\n137 = <tree header> + <a_space> + <a newline> +\n  <Author line> + <a newline> +\n  <Committer line> + <a newline> +\n  <a newline> +\n  <commit message length>\n\nAn easier way is this by `git cat-file commit <commit hash> | wc -c`,\nwhich gets 177, then it should be minus 40 hex away, and result in 137\n\nI put them in the comments to avoid confusion.\n\n> > +\n> > +tag_header_without_oid=\"type blob\n> > +tag hellotag\n> > +tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n> > +tag_header_without_timestamp=\"object $hello_oid\n> > +$tag_header_without_oid\"\n> > +tag_description=\"This is a tag\"\n> > +tag_content=\"$tag_header_without_timestamp 0 +0000\n> > +\n> > +$tag_description\"\n> > +\n> > +tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n> > +tag_size=$(strlen \"$tag_content\")\n> > +\n> > +# This section tests --batch-command with remote-object-info command\n> > +# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n> > +# the filters are set to \"%(objectname) %(objectsize)\".\n> > +# Tests with the default filter are used to test the fallback to 'fetch' command\n> > +\n> > +\n> > +# Test --batch-command remote-object-info with 'git://' transport\n> > +\n> > +. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n> > +start_git_daemon --export-all --enable=receive-pack\n> > +daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n> > +\n> > +test_expect_success 'create repo to be served by git-daemon' '\n> > +     git init \"$daemon_parent\" &&\n> > +\n> > +     echo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n> > +     git -C \"$daemon_parent\" update-index --add hello &&\n> > +     git -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n> > +'\n> > +\n> > +set_transport_variables () {\n> > +     hello_sha1=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n> > +     tree_sha1=$(git -C \"$1\" write-tree)\n> > +     commit_sha1=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_sha1)\n> > +     tag_sha1=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n> > +     tag_size=$(strlen \"$tag_content\")\n> > +}\n> > +\n> > +\n>\n> extra newline here\n>\n\nThank you. Fixed in V2.\n\n> > +test_expect_success 'batch-command remote-object-info git://' '\n> > +     (\n> > +             set_transport_variables \"$daemon_parent\" &&\n> > +             cd \"$daemon_parent\" &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +             git cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> > +             remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1\n> > +             remote-object-info \"$GIT_DAEMON_URL/parent\" $tree_sha1\n> > +             remote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1\n> > +             remote-object-info \"$GIT_DAEMON_URL/parent\" $tag_sha1\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n> > +     (\n> > +             set_transport_variables \"$daemon_parent\" &&\n> > +             cd \"$daemon_parent\" &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +             git cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> > +             remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'batch-command remote-object-info http:// default filter' '\n> > +     (\n> > +             set_transport_variables \"$daemon_parent\" &&\n> > +             cd \"$daemon_parent\" &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +             GIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n> > +             remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1\n> > +             remote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1 $tag_sha1\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'batch-command --buffer remote-object-info git://' '\n> > +     (\n> > +             set_transport_variables \"$daemon_parent\" &&\n> > +             cd \"$daemon_parent\" &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +             git cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n> > +             remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1\n> > +             remote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1 $tag_sha1\n> > +             flush\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +stop_git_daemon\n> > +\n> > +# Test --batch-command remote-object-info with 'http://' transport\n> > +\n> > +. \"$TEST_DIRECTORY\"/lib-httpd.sh\n> > +start_httpd\n> > +\n> > +test_expect_success 'create repo to be served by http:// transport' '\n> > +     git init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +     git -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n> > +     git -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n> > +     echo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n> > +     git -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello\n> > +'\n> > +\n> > +\n> > +test_expect_success 'batch-command remote-object-info http://' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +             git cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_sha1\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_sha1\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'batch-command remote-object-info http:// one line' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +             git cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'batch-command --buffer remote-object-info http://' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +\n> > +             git cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1 $tag_sha1\n> > +             flush\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'batch-command remote-object-info http:// default filter' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +\n> > +             git cat-file --batch-command >actual <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1 $tag_sha1\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +\n> > +             test_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n> > +             EOF\n> > +             test_grep \"objectsize:disk is currently not supported with remote-object-info\" err\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +\n> > +             test_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n> > +             EOF\n> > +             test_grep \"deltabase is currently not supported with remote-object-info\" err\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'remote-object-info fails on server with legacy protocol' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +\n> > +             test_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n> > +             EOF\n> > +             test_grep \"remote-object-info requires protocol v2\" err\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'remote-object-info fails on server with legacy protocol fallback' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +\n> > +             test_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n> > +             EOF\n> > +             test_grep \"remote-object-info requires protocol v2\" err\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'remote-object-info fails on malformed OID' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             malformed_object_id=\"this_id_is_not_valid\" &&\n> > +\n> > +             test_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n> > +             EOF\n> > +             test_grep \"Not a valid object name '$malformed_object_id'\" err\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'remote-object-info fails on malformed OID fallback' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             malformed_object_id=\"this_id_is_not_valid\" &&\n> > +\n> > +             test_must_fail git cat-file --batch-command 2>err <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n> > +             EOF\n> > +             test_grep \"Not a valid object name '$malformed_object_id'\" err\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'remote-object-info fails on missing OID' '\n> > +     set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +     git clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n> > +     test_commit -C missing_oid_repo message1 c.txt &&\n> > +     (\n> > +             cd missing_oid_repo &&\n> > +\n> > +             object_id=$(git rev-parse message1:c.txt) &&\n> > +             test_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n> > +             EOF\n> > +             test_grep \"object-info: not our ref $object_id\" err\n> > +     )\n> > +'\n> > +\n> > +# shellcheck disable=SC2016\n> > +test_expect_success 'remote-object-info fails on missing OID fallback' '\n> > +     (\n> > +             set_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n> > +             cd missing_oid_repo &&\n> > +             object_id=$(git rev-parse message1:c.txt) &&\n> > +             test_must_fail git cat-file --batch-command 2>err <<-EOF &&\n> > +             remote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n> > +             EOF\n> > +             test_grep \"fatal: object-info: not our ref $object_id\" err\n> > +     )\n> > +'\n> > +\n> > +# Test --batch-command remote-object-info with 'file://' transport\n> > +\n> > +# shellcheck disable=SC2016\n> > +test_expect_success 'create repo to be served by file:// transport' '\n> > +     git init server &&\n> > +     git -C server config protocol.version 2 &&\n> > +     git -C server config transfer.advertiseobjectinfo true &&\n> > +     echo_without_newline \"$hello_content\" > server/hello &&\n> > +     git -C server update-index --add hello\n> > +'\n> > +\n> > +\n> > +test_expect_success 'batch-command remote-object-info file://' '\n> > +     (\n> > +             set_transport_variables \"server\" &&\n> > +             cd server &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +             git cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> > +             remote-object-info \"file://$(pwd)\" $hello_sha1\n> > +             remote-object-info \"file://$(pwd)\" $tree_sha1\n> > +             remote-object-info \"file://$(pwd)\" $commit_sha1\n> > +             remote-object-info \"file://$(pwd)\" $tag_sha1\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n> > +     (\n> > +             set_transport_variables \"server\" &&\n> > +             cd server &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +             git cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n> > +             remote-object-info \"file://$(pwd)\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'batch-command --buffer remote-object-info file://' '\n> > +     (\n> > +             set_transport_variables \"server\" &&\n> > +             cd server &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +             git cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n> > +             remote-object-info \"file://$(pwd)\" $hello_sha1 $tree_sha1\n> > +             remote-object-info \"file://$(pwd)\" $commit_sha1 $tag_sha1\n> > +             flush\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_expect_success 'batch-command remote-object-info file:// default filter' '\n> > +     (\n> > +             set_transport_variables \"server\" &&\n> > +             cd server &&\n> > +\n> > +             echo \"$hello_sha1 $hello_size\" >expect &&\n> > +             echo \"$tree_sha1 $tree_size\" >>expect &&\n> > +             echo \"$commit_sha1 $commit_size\" >>expect &&\n> > +             echo \"$tag_sha1 $tag_size\" >>expect &&\n> > +\n> > +             git cat-file --batch-command >actual <<-EOF &&\n> > +             remote-object-info \"file://$(pwd)\" $hello_sha1 $tree_sha1\n> > +             remote-object-info \"file://$(pwd)\" $commit_sha1 $tag_sha1\n> > +             EOF\n> > +             test_cmp expect actual\n> > +     )\n> > +'\n> > +\n> > +test_done\n>\n> Some more tests I'd like to see\n> - Testing against the '-Z' option.\n> - Testing the fallback to fetch whole object when the server doesn't\n> support 'remote-object-info'.\n>\n\nThank you. More tests are added in V2 to cover those scenarios.\n\n> Thanks\n"},{"id":"498981","messageId":"20240720034337.57125-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH v2 0/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-20T03:43:31Z","receivedAt":"2024-07-20T03:43:53Z","isPatch":true,"body":"This is a continuation of Calvin Wan's (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info command at [1].\n\nSometimes it is useful to get information about an object without having to download\nit completely. The server logic for retrieving size has already been implemented and merged in\n\"a2ba162cda (object-info: support for retrieving object info, 2021-04-20)\"[2].\nThis patch series implement the client option for it.\n\nThis patch series add the `remote-object-info` command to `cat-file --batch-command`.\nThis command allows the client to make an object-info command request to a server\nthat supports protocol v2. If the server is v2, but does not have\nobject-info capability, the entire object is fetched and the\nrelevant object info is returned.\n\nA few questions open for discussions please:\n\n1. In the current implementation, if a user puts `remote-object-info` in protocol v1,\n   `cat-file --batch-command` will die. Which way do we prefer? \"error and exit (i.e. die)\"\n   or \"warn and wait for new command\".\n\n2. Right now, only the size is supported. If the batch command format\n   contains objectsize:disk or deltabase, it will die. The question\n   is about objecttype. In the current implementation, it will die too.\n   But dying on objecttype breaks the default format. We have changed the\n   default format to %(objectname) %(objectsize) when remote-object-info is used.\n   Any suggestions on this approach?\n\n[1] https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[2] https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n\nV1 of the patch series can be found here:\nhttps://lore.kernel.org/git/20240628190503.67389-1-eric.peijian@gmail.com/\n\n\nChanges since V1\n================\n\n- The function parse_remote_info() has been renamed to\n  parse_cmd_remote_object_info() and its signature has been modified to comply\n  with parse_cmd_fn_t. This function now serves as the mapped function for the\n  remote-object-info command.\n\n  This change simplifies the code by avoiding command name comparisons and\n  reusing logic that fits parse_cmd_fn_t.\n\n- Added more tests to cover fallbacks. When the server does not support the\n  object-info capability, remote-object-info will fetch the objects locally and\n  print out the information.\n\n- Fixed a logic issue that could lead to a potential heap-buffer-overflow error.\n  The alloc_ref function is now used to initialize a ref struct instead of xcalloc.\n\n- Refactored some logic for improved readability, such as how to initialize the\n  transport->remote_refs linked list.\n\nThank you.\n\nEric Ju\n\nCalvin Wan (5):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n  cat-file: add remote-object-info to batch-command\n\nEric Ju (1):\n  cat-file: add declaration of variable i inside its for loop\n\n Documentation/git-cat-file.txt         |  23 +-\n builtin/cat-file.c                     | 127 ++++-\n fetch-pack.c                           |  48 +-\n fetch-pack.h                           |  10 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/t1017-cat-file-remote-object-info.sh | 748 +++++++++++++++++++++++++\n transport-helper.c                     |   8 +-\n transport.c                            | 118 +++-\n transport.h                            |  11 +\n 11 files changed, 1075 insertions(+), 36 deletions(-)\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\nRange-diff against v1:\n1:  fdd44b16b5 ! 1:  97871cb75e fetch-pack: refactor packet writing\n    @@ Metadata\n      ## Commit message ##\n         fetch-pack: refactor packet writing\n     \n    -    A subsequent patch need to write capabilities for another command.\n    -    Refactor write_fetch_command_and_capabilities() to be used by both\n    -    fetch and future command.\n    +    A subsequent patch needs to write capabilities for another command.\n    +    Refactor write_fetch_command_and_capabilities() to be a more general\n    +    purpose function write_command_and_capabilities(), so that it can be\n    +    used by both fetch and future command.\n    +\n    +    Here \"command\" means the \"operations\" supported by Git’s wire protocol\n    +    https://git-scm.com/docs/protocol-v2. An example would be a\n    +    git's subcommand, such as git-fetch(1); or an operation supported by\n    +    the server side such as \"object-info\" implemented in \"a2ba162cda\n    +    (object-info: support for retrieving object info, 2021-04-20)\".\n     \n    -    Signed-off-by: Calvin Wan <calvinwan@google.com>\n    -    Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n         Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Helped-by: Christian Couder <chriscool@tuxfamily.org>\n    +    Signed-off-by: Calvin Wan <calvinwan@google.com>\n    +    Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n     \n      ## fetch-pack.c ##\n     @@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n2:  890219ce6a ! 2:  301047c574 fetch-pack: move fetch initialization\n    @@ Commit message\n         from the beginning of the first state to just before the execution of\n         the state machine.\n     \n    -    Signed-off-by: Calvin Wan <calvinwan@google.com>\n    -    Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n         Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Helped-by: Christian Couder <chriscool@tuxfamily.org>\n    +    Signed-off-by: Calvin Wan <calvinwan@google.com>\n    +    Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n     \n      ## fetch-pack.c ##\n     @@ fetch-pack.c: static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n3:  6844095b26 ! 3:  5d83c4f5b2 serve: advertise object-info feature\n    @@ Commit message\n         client to decide whether to query the server for object-info or fetch\n         as a fallback.\n     \n    -    Signed-off-by: Calvin Wan <calvinwan@google.com>\n    -    Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n         Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Helped-by: Christian Couder <chriscool@tuxfamily.org>\n    +    Signed-off-by: Calvin Wan <calvinwan@google.com>\n    +    Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n     \n      ## serve.c ##\n     @@ serve.c: static void session_id_receive(struct repository *r UNUSED,\n4:  c940cb1657 ! 4:  a7210b7169 transport: add client support for object-info\n    @@ Commit message\n     \n         Sometimes it is useful to get information about an object without having\n         to download it completely. The server logic has already been implemented\n    -    as “a2ba162cda (object-info: support for retrieving object info,\n    +    in “a2ba162cda (object-info: support for retrieving object info,\n         2021-04-20)”.\n     \n         Add client functions to communicate with the server.\n     \n         The client currently supports requesting a list of object ids with\n    -    features 'size' and 'type' from a v2 server. If a server does not\n    -    advertise either of the requested features, then the client falls back\n    +    feature 'size' from a v2 server. If a server does not\n    +    advertise the feature, then the client falls back\n         to making the request through 'fetch'.\n     \n    -    Signed-off-by: Calvin Wan <calvinwan@google.com>\n    -    Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n         Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Helped-by: Christian Couder <chriscool@tuxfamily.org>\n    +    Signed-off-by: Calvin Wan <calvinwan@google.com>\n    +    Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n     \n      ## fetch-pack.c ##\n     @@ fetch-pack.c: static void write_command_and_capabilities(struct strbuf *req_buf,\n    @@ fetch-pack.h: struct fetch_pack_args {\n      \tconst struct string_list *deepen_not;\n      \tstruct list_objects_filter_options filter_options;\n      \tconst struct string_list *server_options;\n    -+\tstruct object_info **object_info_data;\n    ++\tstruct object_info *object_info_data;\n      \n      \t/*\n      \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n    @@ transport.c: static struct ref *handshake(struct transport *transport, int for_p\n      \treturn refs;\n      }\n      \n    -+static int fetch_object_info(struct transport *transport, struct object_info **object_info_data)\n    ++static int fetch_object_info(struct transport *transport, struct object_info *object_info_data)\n     +{\n     +\tint size_index = -1;\n     +\tstruct git_transport_data *data = transport->data;\n    -+\tstruct object_info_args args;\n    ++\tstruct object_info_args args = { 0 };\n     +\tstruct packet_reader reader;\n     +\n    -+\tmemset(&args, 0, sizeof(args));\n     +\targs.server_options = transport->server_options;\n     +\targs.object_info_options = transport->smart_options->object_info_options;\n     +\targs.oids = transport->smart_options->object_info_oids;\n    @@ transport.c: static struct ref *handshake(struct transport *transport, int for_p\n     +\t\t\treturn -1;\n     +\t\t}\n     +\t\tif (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n    -+\t\t\tif (!strcmp(reader.line, \"size\"))\n    ++\t\t\tif (!strcmp(reader.line, \"size\")) {\n     +\t\t\t\tsize_index = i;\n    ++\t\t\t\tfor (size_t j = 0; j < args.oids->nr; j++) {\n    ++\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n    ++\t\t\t\t}\n    ++\t\t\t}\n     +\t\t\tcontinue;\n     +\t\t}\n     +\t\treturn -1;\n    @@ transport.c: static struct ref *handshake(struct transport *transport, int for_p\n     +\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n     +\t\t\t\tdie(\"object-info: not our ref %s\",\n     +\t\t\t\t\tobject_info_values.items[0].string);\n    -+\t\t\t*(*object_info_data)[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n    ++\n    ++\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n    ++\n     +\t\t}\n    ++\n    ++\t\tstring_list_clear(&object_info_values, 0);\n     +\t}\n     +\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n     +\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \tstruct ref *refs = NULL;\n      \tstruct fetch_pack_args args;\n      \tstruct ref *refs_tmp = NULL;\n    -+\tstruct ref *object_info_refs = xcalloc(1, sizeof (struct ref));\n    ++\tstruct ref *object_info_refs = NULL;\n      \n      \tmemset(&args, 0, sizeof(args));\n      \targs.uploadpack = data->options.uploadpack;\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \targs.server_options = transport->server_options;\n      \targs.negotiation_tips = data->options.negotiation_tips;\n      \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n    --\n    --\tif (!data->finished_handshake) {\n    --\t\tint i;\n     +\targs.object_info = transport->smart_options->object_info;\n     +\n    -+\tif (transport->smart_options && transport->smart_options->object_info) {\n    -+\t\tstruct ref *ref = object_info_refs;\n    ++\tif (transport->smart_options\n    ++\t\t&& transport->smart_options->object_info\n    ++\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n    ++\t\tstruct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n     +\n     +\t\tif (!fetch_object_info(transport, data->options.object_info_data))\n     +\t\t\tgoto cleanup;\n    ++\n     +\t\targs.object_info_data = data->options.object_info_data;\n     +\t\targs.quiet = 1;\n     +\t\targs.no_progress = 1;\n     +\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n    -+\t\t\tstruct ref *temp_ref = xcalloc(1, sizeof (struct ref));\n    -+\t\t\ttemp_ref->old_oid = *(transport->smart_options->object_info_oids->oid + i);\n    -+\t\t\ttemp_ref->exact_oid = 1;\n    -+\t\t\tref->next = temp_ref;\n    -+\t\t\tref = ref->next;\n    ++\t\t\tref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n    ++\t\t\tref_itr->exact_oid = 1;\n    ++\t\t\tif (i == transport->smart_options->object_info_oids->nr - 1)\n    ++\t\t\t\t/* last element, no need to allocat to next */\n    ++\t\t\t\tref_itr -> next = NULL;\n    ++\t\t\telse\n    ++\t\t\t\tref_itr->next = alloc_ref(\"\");\n    + \n    +-\tif (!data->finished_handshake) {\n    +-\t\tint i;\n    ++\t\t\tref_itr = ref_itr->next;\n     +\t\t}\n    -+\t\ttransport->remote_refs = object_info_refs->next;\n    ++\n    ++\t\ttransport->remote_refs = object_info_refs;\n    ++\n     +\t} else if (!data->finished_handshake) {\n      \t\tint must_list_refs = 0;\n     -\t\tfor (i = 0; i < nr_heads; i++) {\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \n      \tdata->finished_handshake = 0;\n     +\tif (args.object_info) {\n    -+\t\tstruct ref *ref_cpy_reader = object_info_refs->next;\n    ++\t\tstruct ref *ref_cpy_reader = object_info_refs;\n     +\t\tfor (int i = 0; ref_cpy_reader; i++) {\n    -+\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &(*args.object_info_data)[i], OBJECT_INFO_LOOKUP_REPLACE);\n    ++\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n     +\t\t\tref_cpy_reader = ref_cpy_reader->next;\n     +\t\t}\n     +\t}\n    ++\n      \tdata->options.self_contained_and_connected =\n      \t\targs.self_contained_and_connected;\n      \tdata->options.connectivity_checked = args.connectivity_checked;\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \t\tret = -1;\n      \tif (report_unmatched_refs(to_fetch, nr_heads))\n      \t\tret = -1;\n    -@@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n    + \n    + cleanup:\n    ++\tfree_refs(object_info_refs);\n    + \tclose(data->fd[0]);\n    + \tif (data->fd[1] >= 0)\n    + \t\tclose(data->fd[1]);\n    + \tif (finish_connect(data->conn))\n    + \t\tret = -1;\n    + \tdata->conn = NULL;\n    +-\n      \tfree_refs(refs_tmp);\n      \tfree_refs(refs);\n      \tlist_objects_filter_release(&args.filter_options);\n    -+\tfree_refs(object_info_refs);\n    - \treturn ret;\n    - }\n    - \n     \n      ## transport.h ##\n     @@\n    @@ transport.h: struct git_transport_options {\n      \tstruct oidset *acked_commits;\n     +\n     +\tstruct oid_array *object_info_oids;\n    -+\tstruct object_info **object_info_data;\n    ++\tstruct object_info *object_info_data;\n     +\tstruct string_list *object_info_options;\n      };\n      \n5:  6526e24aa4 ! 5:  2787327782 cat-file: add declaration of variable i inside its for loop\n    @@ Commit message\n     \n         Change the declaration of i to be inside the for loop for readability.\n     \n    -    Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n         Helped-by: Christian Couder <chriscool@tuxfamily.org>\n    +    Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n     \n      ## builtin/cat-file.c ##\n     @@ builtin/cat-file.c: static void dispatch_calls(struct batch_options *opt,\n    @@ builtin/cat-file.c: static void dispatch_calls(struct batch_options *opt,\n      \t\tdie(_(\"flush is only for --buffer mode\"));\n      \n     -\tfor (i = 0; i < nr; i++)\n    -+\tfor (int i = 0; i < nr; i++)\n    ++\tfor (size_t i = 0; i < nr; i++)\n      \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n      \n      \tfflush(stdout);\n    @@ builtin/cat-file.c: static void batch_objects_command(struct batch_options *opt,\n      \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n      \n     -\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n    -+\t\tfor (int i = 0; i < ARRAY_SIZE(commands); i++) {\n    ++\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n      \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n      \t\t\t\tcontinue;\n      \n6:  5cd1a1dbd2 < -:  ---------- cat-file: add remote-object-info to batch-command\n-:  ---------- > 6:  cb114765cf cat-file: add remote-object-info to batch-command\n-- \n2.45.2\n\n"},{"id":"498982","messageId":"20240720034337.57125-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240720034337.57125-1-eric.peijian@gmail.com","subject":"[PATCH v2 1/6] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-20T03:43:32Z","receivedAt":"2024-07-20T03:43:55Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nA subsequent patch needs to write capabilities for another command.\nRefactor write_fetch_command_and_capabilities() to be a more general\npurpose function write_command_and_capabilities(), so that it can be\nused by both fetch and future command.\n\nHere \"command\" means the \"operations\" supported by Git’s wire protocol\nhttps://git-scm.com/docs/protocol-v2. An example would be a\ngit's subcommand, such as git-fetch(1); or an operation supported by\nthe server side such as \"object-info\" implemented in \"a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20)\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 732511604b..9c8cda0f9e 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1312,13 +1312,13 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n+static void write_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t\t const struct string_list *server_options, const char* command)\n {\n \tconst char *hash_name;\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n@@ -1354,7 +1354,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, args->server_options, \"fetch\");\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2172,7 +2172,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, server_options, \"fetch\");\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.45.2\n\n"},{"id":"498983","messageId":"20240720034337.57125-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240720034337.57125-1-eric.peijian@gmail.com","subject":"[PATCH v2 2/6] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-20T03:43:33Z","receivedAt":"2024-07-20T03:43:55Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 9c8cda0f9e..a605b9a499 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1675,18 +1675,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.45.2\n\n"},{"id":"498984","messageId":"20240720034337.57125-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240720034337.57125-1-eric.peijian@gmail.com","subject":"[PATCH v2 3/6] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-20T03:43:34Z","receivedAt":"2024-07-20T03:43:56Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex 884cd84ca8..3aae03405b 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -70,7 +70,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -78,6 +78,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.45.2\n\n"},{"id":"498985","messageId":"20240720034337.57125-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240720034337.57125-1-eric.peijian@gmail.com","subject":"[PATCH v2 4/6] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-20T03:43:35Z","receivedAt":"2024-07-20T03:43:57Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes it is useful to get information about an object without having\nto download it completely. The server logic has already been implemented\nin “a2ba162cda (object-info: support for retrieving object info,\n2021-04-20)”.\n\nAdd client functions to communicate with the server.\n\nThe client currently supports requesting a list of object ids with\nfeature 'size' from a v2 server. If a server does not\nadvertise the feature, then the client falls back\nto making the request through 'fetch'.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c       |  24 +++++++++\n fetch-pack.h       |  10 ++++\n transport-helper.c |   8 ++-\n transport.c        | 118 +++++++++++++++++++++++++++++++++++++++++++--\n transport.h        |  11 +++++\n 5 files changed, 164 insertions(+), 7 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex a605b9a499..419450c8dd 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1344,6 +1344,27 @@ static void write_command_and_capabilities(struct strbuf *req_buf,\n \tpacket_buf_delim(req_buf);\n }\n \n+void send_object_info_request(int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, args->server_options, \"object-info\");\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids) {\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\t}\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1681,6 +1702,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex b5c579cdae..5a5211e355 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\n@@ -68,6 +70,12 @@ struct fetch_pack_args {\n \tunsigned connectivity_checked:1;\n };\n \n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n /*\n  * sought represents remote references that should be updated from.\n  * On return, the names that were found on the remote will have been\n@@ -106,4 +114,6 @@ int report_unmatched_refs(struct ref **sought, int nr_sought);\n  */\n int fetch_pack_fsck_objects(void);\n \n+void send_object_info_request(int fd_out, struct object_info_args *args);\n+\n #endif\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 09b3560ffd..841a32e80a 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -699,13 +699,17 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n+\t * require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n \t\treturn -1;\n \t}\n+\tif (transport->smart_options->object_info) {\n+\t\t// fail the command explicitly to avoid further commands input\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\t}\n \n \tif (!data->get_refs_list_called)\n \t\tget_refs_list_using_list(transport, 0);\ndiff --git a/transport.c b/transport.c\nindex 12cc5b4d96..8f990fcba6 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -366,6 +366,80 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \treturn refs;\n }\n \n+static int fetch_object_info(struct transport *transport, struct object_info *object_info_data)\n+{\n+\tint size_index = -1;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct object_info_args args = { 0 };\n+\tstruct packet_reader reader;\n+\n+\targs.server_options = transport->server_options;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\tdata->version = discover_version(&reader);\n+\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tswitch (data->version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\treturn -1;\n+\t\tif (unsorted_string_list_has_string(args.object_info_options, \"size\")\n+\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0)) {\n+\t\t\treturn -1;\n+\t\t}\n+\t\tsend_object_info_request(data->fd[1], &args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"wrong protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args.object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n+\t\t\tif (!strcmp(reader.line, \"size\")) {\n+\t\t\t\tsize_index = i;\n+\t\t\t\tfor (size_t j = 0; j < args.oids->nr; j++) {\n+\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(&reader) == PACKET_READ_NORMAL && i < args.oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader.line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\n+\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n+\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\n+\n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n \t\t\t\t\tstruct transport_ls_refs_options *options)\n {\n@@ -413,6 +487,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \tstruct ref *refs = NULL;\n \tstruct fetch_pack_args args;\n \tstruct ref *refs_tmp = NULL;\n+\tstruct ref *object_info_refs = NULL;\n \n \tmemset(&args, 0, sizeof(args));\n \targs.uploadpack = data->options.uploadpack;\n@@ -439,11 +514,36 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options\n+\t\t&& transport->smart_options->object_info\n+\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n+\n+\t\tif (!fetch_object_info(transport, data->options.object_info_data))\n+\t\t\tgoto cleanup;\n+\n+\t\targs.object_info_data = data->options.object_info_data;\n+\t\targs.quiet = 1;\n+\t\targs.no_progress = 1;\n+\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n+\t\t\tref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n+\t\t\tref_itr->exact_oid = 1;\n+\t\t\tif (i == transport->smart_options->object_info_oids->nr - 1)\n+\t\t\t\t/* last element, no need to allocat to next */\n+\t\t\t\tref_itr -> next = NULL;\n+\t\t\telse\n+\t\t\t\tref_itr->next = alloc_ref(\"\");\n \n-\tif (!data->finished_handshake) {\n-\t\tint i;\n+\t\t\tref_itr = ref_itr->next;\n+\t\t}\n+\n+\t\ttransport->remote_refs = object_info_refs;\n+\n+\t} else if (!data->finished_handshake) {\n \t\tint must_list_refs = 0;\n-\t\tfor (i = 0; i < nr_heads; i++) {\n+\t\tfor (int i = 0; i < nr_heads; i++) {\n \t\t\tif (!to_fetch[i]->exact_oid) {\n \t\t\t\tmust_list_refs = 1;\n \t\t\t\tbreak;\n@@ -481,23 +581,31 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t  &transport->pack_lockfiles, data->version);\n \n \tdata->finished_handshake = 0;\n+\tif (args.object_info) {\n+\t\tstruct ref *ref_cpy_reader = object_info_refs;\n+\t\tfor (int i = 0; ref_cpy_reader; i++) {\n+\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n+\t\t\tref_cpy_reader = ref_cpy_reader->next;\n+\t\t}\n+\t}\n+\n \tdata->options.self_contained_and_connected =\n \t\targs.self_contained_and_connected;\n \tdata->options.connectivity_checked = args.connectivity_checked;\n \n-\tif (!refs)\n+\tif (!refs && !args.object_info)\n \t\tret = -1;\n \tif (report_unmatched_refs(to_fetch, nr_heads))\n \t\tret = -1;\n \n cleanup:\n+\tfree_refs(object_info_refs);\n \tclose(data->fd[0]);\n \tif (data->fd[1] >= 0)\n \t\tclose(data->fd[1]);\n \tif (finish_connect(data->conn))\n \t\tret = -1;\n \tdata->conn = NULL;\n-\n \tfree_refs(refs_tmp);\n \tfree_refs(refs);\n \tlist_objects_filter_release(&args.filter_options);\ndiff --git a/transport.h b/transport.h\nindex 6393cd9823..50ea2b05cf 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to pull only object-info. Fallbacks\n+\t * to pulling entire object if object-info is not supported.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.45.2\n\n"},{"id":"498986","messageId":"20240720034337.57125-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240720034337.57125-1-eric.peijian@gmail.com","subject":"[PATCH v2 5/6] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-20T03:43:36Z","receivedAt":"2024-07-20T03:43:58Z","isPatch":true,"body":"Some code declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n 1 file changed, 3 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 18fe58d6b8..a5724667b1 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -673,12 +673,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -686,9 +684,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -714,7 +710,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -724,7 +719,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \n-- \n2.45.2\n\n"},{"id":"498987","messageId":"20240720034337.57125-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240720034337.57125-1-eric.peijian@gmail.com","subject":"[PATCH v2 6/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-07-20T03:43:37Z","receivedAt":"2024-07-20T03:44:00Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSince the `info` command in cat-file --batch-command prints object info\nfor a given object, it is natural to add another command in cat-file\n--batch-command to print object info for a given object from a remote.\nAdd `remote-object-info` to cat-file --batch-command.\n\nWhile `info` takes object ids one at a time, this creates overhead when\nmaking requests to a server so `remote-object-info` instead can take\nmultiple object ids at once.\n\ncat-file --batch-command is generally implemented in the following\nmanner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loop through the object info passed in, print the info.\n\nIn order for remote-object-info to avoid remote communication overhead\nin the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Documentation/git-cat-file.txt         |  23 +-\n builtin/cat-file.c                     | 116 +++-\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/t1017-cat-file-remote-object-info.sh | 748 +++++++++++++++++++++++++\n 5 files changed, 893 insertions(+), 8 deletions(-)\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex bd95a6c10a..98375dab46 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -149,6 +149,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified <remote> without\n+\tdownloading objects from remote. If the object-info capability is not\n+\tsupported by the server, the objects will be downloaded instead.\n+\tError when no object references is provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +297,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +306,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +323,9 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except remote-object-info command who uses\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+When \"%(objecttype)\" is supported, default format should be unified.\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +407,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since objecttype, objectsize:disk and deltabase are currently not supported by the\n+remote-object-info, git will error and exit when they are in the format string.\n+\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex a5724667b1..ca6e05e769 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -24,6 +24,9 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -42,9 +45,12 @@ struct batch_options {\n \tchar input_delim;\n \tchar output_delim;\n \tconst char *format;\n+\tint use_remote_info;\n };\n \n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -528,7 +534,7 @@ static void batch_one_object(const char *obj_name,\n \tenum get_oid_result result;\n \n \tresult = get_oid_with_context(the_repository, obj_name,\n-\t\t\t\t      flags, &data->oid, &ctx);\n+\t\t\t\t\t\t\t\t  flags, &data->oid, &ctx);\n \tif (result != FOUND) {\n \t\tswitch (result) {\n \t\tcase MISSING_OBJECT:\n@@ -576,6 +582,61 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT\n+\t*/\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\t\t/*\n+\t\t * 'size' is the only option currently supported.\n+\t\t * Other options that are passed in the format will exit with error.\n+\t\t */\n+\t\tif (strstr(opt->format, \"%(objectsize)\")) {\n+\t\t\tstring_list_append(&object_info_options, \"size\");\n+\t\t} else {\n+\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\t\t}\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -667,6 +728,52 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t   const char *line,\n+\t\t\t   struct strbuf *output,\n+\t\t\t   struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\n+\tchar *line_to_split = xstrdup_or_null(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\n+\topt->use_remote_info = 1;\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\n+\t\tdata->oid = object_info_oids.oid[i];\n+\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can't retrive\n+\t\t\t * infomation from server withoug downloading them, and the objects\n+\t\t\t * have been fetched to client already.\n+\t\t\t * Print the infomation using the logic for local objects.\n+\t\t\t */\n+\t\t\tdata->skip_object_info = 0;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\n+\t}\n+\topt->use_remote_info = 0;\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -696,9 +803,10 @@ static const struct parse_cmd {\n \tparse_cmd_fn_t fn;\n \tunsigned takes_args;\n } commands[] = {\n-\t{ \"contents\", parse_cmd_contents, 1},\n-\t{ \"info\", parse_cmd_info, 1},\n-\t{ \"flush\", NULL, 0},\n+\t{ \"contents\", parse_cmd_contents, 1 },\n+\t{ \"info\", parse_cmd_info, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n+\t{ \"flush\", NULL, 0 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex 065103be3e..34c702ece5 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2987,3 +2987,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex c5f2bb2fc2..333e19cd1e 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -533,4 +533,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(each_packed_object_fn, void *,\n \t\t\t   enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..64eb55bd9e\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,748 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+echo_without_newline () {\n+    printf '%s' \"$*\"\n+}\n+\n+echo_without_newline_nul () {\n+    echo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+strlen () {\n+    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_sha1=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_sha1=$(git -C \"$1\" write-tree)\n+\tcommit_sha1=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_sha1)\n+\ttag_sha1=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_sha1 missing\" >>expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_sha1\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_sha1 missing\" >>expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1 $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_sha1 missing\" >>expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1 $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_sha1 $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_sha1 $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_sha1 $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_sha1 missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_sha1 missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_sha1 missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_sha1 missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_sha1 $tree_sha1\n+remote-object-info $GIT_DAEMON_URL/parent $commit_sha1 $tag_sha1\n+info $hello_sha1\n+info $tree_sha1\n+info $commit_sha1\n+info $tag_sha1\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback git://: fetch objects to client' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_sha1 missing\" >expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_sha1 missing\" >>expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_sha1\n+\t\tremote-object-info \"file://${server_path}\" $tree_sha1\n+\t\tremote-object-info \"file://${server_path}\" $commit_sha1\n+\t\tremote-object-info \"file://${server_path}\" $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_sha1 missing\" >>expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_sha1 missing\" >>expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"file://${server_path}\" $commit_sha1 $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"file://${server_path}\" $commit_sha1 $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_sha1 $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_sha1 $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_sha1 $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_sha1 missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_sha1 missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_sha1 missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_sha1 missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_sha1 $tree_sha1\n+remote-object-info \\\"file://${server_path}\\\" $commit_sha1 $tag_sha1\n+info $hello_sha1\n+info $tree_sha1\n+info $commit_sha1\n+info $tag_sha1\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback file://: fetch objects to client' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_sha1 missing\" >expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tremote-object-info \"file://${server_path}\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_sha1 missing\" >>expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_sha1 missing\" >>expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_sha1 missing\" >>expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1 $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1 $tag_sha1\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_sha1 $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_sha1 $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_sha1 $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_sha1 $tree_sha1\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_sha1 $tag_sha1\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback http://: fetch objects to client' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_sha1 missing\" >expect &&\n+\t\techo \"$tree_sha1 missing\" >>expect &&\n+\t\techo \"$commit_sha1 missing\" >>expect &&\n+\t\techo \"$tag_sha1 missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n+\t\tinfo $hello_sha1\n+\t\tinfo $tree_sha1\n+\t\tinfo $commit_sha1\n+\t\tinfo $tag_sha1\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n-- \n2.45.2\n\n"},{"id":"501563","messageId":"CAN2LT1A7W+qp5H0R4Q1TfFJqSM5jKAz3NPEA9jxU0A00kgO3Ow@mail.gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"Re: [PATCH 0/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-08-22T21:24:34Z","receivedAt":"2024-08-22T21:24:46Z","isPatch":true,"body":"Dear Reviewers,\n\nThank you for your thorough review of v1. I have addressed all the\nissues identified in that version and have now prepared v2.\n\nCould you please take another look and provide your acknowledgment?\n\nThank you very much for your time and effort.\n\nBest regards,\nPeijian\n\nOn Fri, Jun 28, 2024 at 3:05 PM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> This is a continuation of Calvin Wan's (calvinwan@google.com)\n> patch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info command at [1].\n>\n> Sometimes it is useful to get information about an object without having to download\n> it completely. The server logic for retrieving size has already been implemented and merged in\n> \"a2ba162cda (object-info: support for retrieving object info, 2021-04-20)\"[2].\n> This patch series implement the client option for it.\n>\n> This patch series add the `remote-object-info` command to `cat-file --batch-command`. This command\n> allows the client to make an object-info command request to a server\n> that supports protocol v2. If the server is v2, but does not have\n> object-info capability, the entire object is fetched and the\n> relevant object info is returned.\n>\n> A few questions open for discussions please:\n>\n> 1. In the current implementation, if a user puts `remote-object-info` in protocol v1,\n>    `cat-file --batch-command` will die. Which way do we prefer? \"error and exit (i.e. die)\"\n>    or \"warn and wait for new command\".\n>\n> 2. Right now, only the size is supported. If the batch command format\n>    contains objectsize:disk or deltabase, it will die. The question\n>    is about objecttype. In the current implementation, it will die too.\n>    But dying on objecttype breaks the default format. We have changed the\n>    default format to %(objectname) %(objectsize) when remote-object-info is used.\n>    Any suggestions on this approach?\n>\n>\n> [1] https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n> [2] https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n>\n>\n> Calvin Wan (5):\n>   fetch-pack: refactor packet writing\n>   fetch-pack: move fetch initialization\n>   serve: advertise object-info feature\n>   transport: add client support for object-info\n>   cat-file: add remote-object-info to batch-command\n>\n> Eric Ju (1):\n>   cat-file: add declaration of variable i inside its for loop\n>\n>  Documentation/git-cat-file.txt         |  22 +-\n>  builtin/cat-file.c                     | 240 ++++++++++----\n>  fetch-pack.c                           |  48 ++-\n>  fetch-pack.h                           |  10 +\n>  object-file.c                          |  11 +\n>  object-store-ll.h                      |   3 +\n>  serve.c                                |   4 +-\n>  t/t1017-cat-file-remote-object-info.sh | 412 +++++++++++++++++++++++++\n>  transport-helper.c                     |   8 +-\n>  transport.c                            | 102 +++++-\n>  transport.h                            |  11 +\n>  11 files changed, 785 insertions(+), 86 deletions(-)\n>  create mode 100755 t/t1017-cat-file-remote-object-info.sh\n>\n> --\n> 2.45.2\n>\n"},{"id":"502618","messageId":"87ldzy9vxd.fsf@iotcl.com","threadId":"61695","inReplyTo":"20240720034337.57125-7-eric.peijian@gmail.com","subject":"Re: [PATCH v2 6/6] cat-file: add remote-object-info to batch-command","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2024-09-11T13:11:58Z","receivedAt":"2024-09-11T13:12:11Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n[snip]\n\n> diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\n> new file mode 100755\n> index 0000000000..64eb55bd9e\n> --- /dev/null\n> +++ b/t/t1017-cat-file-remote-object-info.sh\n> @@ -0,0 +1,748 @@\n> +#!/bin/sh\n> +\n> +test_description='git cat-file --batch-command with remote-object-info command'\n> +\n> +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> +\n> +. ./test-lib.sh\n> +\n> +echo_without_newline () {\n> +    printf '%s' \"$*\"\n> +}\n> +\n> +echo_without_newline_nul () {\n> +    echo_without_newline \"$@\" | tr '\\n' '\\0'\n> +}\n> +\n> +strlen () {\n> +    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> +}\n> +\n> +hello_content=\"Hello World\"\n> +hello_size=$(strlen \"$hello_content\")\n> +hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n> +\n> +# This is how we get 13:\n> +# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n> +# file mode is 100644, which is 6 characters;\n> +# file name is hello, which is 5 characters\n> +# a space is 1 character and a null is 1 character\n> +tree_size=$(($(test_oid rawsz) + 13))\n> +\n> +commit_message=\"Initial commit\"\n> +\n> +# This is how we get 137:\n> +# 137 = <tree header> + <a_space> + <a newline> +\n> +# <Author line> + <a newline> +\n> +# <Committer line> + <a newline> +\n> +# <a newline> +\n> +# <commit message length>\n> +# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n> +# to get 177, 2. then deduct 40 hex characters to get 137\n> +commit_size=$(($(test_oid hexsz) + 137))\n> +\n> +tag_header_without_oid=\"type blob\n> +tag hellotag\n> +tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n> +tag_header_without_timestamp=\"object $hello_oid\n> +$tag_header_without_oid\"\n> +tag_description=\"This is a tag\"\n> +tag_content=\"$tag_header_without_timestamp 0 +0000\n> +\n> +$tag_description\"\n> +\n> +tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n> +tag_size=$(strlen \"$tag_content\")\n> +\n> +set_transport_variables () {\n> +\thello_sha1=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n> +\ttree_sha1=$(git -C \"$1\" write-tree)\n> +\tcommit_sha1=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_sha1)\n> +\ttag_sha1=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n\nI see here and various other places in this file names with \"_sha1\". I\nthink it makes more sense to name them \"_oid\" because these works also\nfine with GIT_TEST_DEFAULT_HASH=sha256.\n\nOther than that I don't have any comments about this patch series.\n\n--\nToon\n"},{"id":"503345","messageId":"CAP8UFD1J4JdR-QP9em=_0q8CQZLf9nE7PLE2=kkvxY218fa-ig@mail.gmail.com","threadId":"61695","inReplyTo":"20240720034337.57125-2-eric.peijian@gmail.com","subject":"Re: [PATCH v2 1/6] fetch-pack: refactor packet writing","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-09-24T11:45:31Z","receivedAt":"2024-09-24T11:45:45Z","isPatch":true,"body":"On Sat, Jul 20, 2024 at 5:43 AM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> From: Calvin Wan <calvinwan@google.com>\n>\n> A subsequent patch needs to write capabilities for another command.\n> Refactor write_fetch_command_and_capabilities() to be a more general\n> purpose function write_command_and_capabilities(), so that it can be\n> used by both fetch and future command.\n>\n> Here \"command\" means the \"operations\" supported by Git’s wire protocol\n> https://git-scm.com/docs/protocol-v2. An example would be a\n> git's subcommand, such as git-fetch(1); or an operation supported by\n> the server side such as \"object-info\" implemented in \"a2ba162cda\n> (object-info: support for retrieving object info, 2021-04-20)\".\n\nI agree that reusing or refactoring the new\nwrite_command_and_capabilities() function for more commands can be\ndone in a separate series that could perhaps also move the new\nfunction to connect.c. Maybe this could be added to the commit message\nthough.\n\n[...]\n\n> -static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> -                                                const struct string_list *server_options)\n> +static void write_command_and_capabilities(struct strbuf *req_buf,\n> +                                                const struct string_list *server_options, const char* command)\n\nIn https://lore.kernel.org/git/xmqqfsn0qsi4.fsf@gitster.g/ Junio\nsuggested swaping the \"command\" and \"server_options\" arguments as well\nas sticking the \"*\" to \"command\" instead of \"char\", so:\n\nstatic void write_command_and_capabilities(struct strbuf *req_buf,\n\nconst char *command,\n\nconst struct string_list *server_options)\n\nThe rest of the patch looks good.\n"},{"id":"503346","messageId":"CAP8UFD0WVgTfgY5aBr8wUxFWi8ggTxL66jnRLLnVkTOniL0Wvg@mail.gmail.com","threadId":"61695","inReplyTo":"20240720034337.57125-5-eric.peijian@gmail.com","subject":"Re: [PATCH v2 4/6] transport: add client support for object-info","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-09-24T11:45:44Z","receivedAt":"2024-09-24T11:45:58Z","isPatch":true,"body":"On Sat, Jul 20, 2024 at 5:43 AM Eric Ju <eric.peijian@gmail.com> wrote:\n\n[...]\n\n>  fetch-pack.c       |  24 +++++++++\n>  fetch-pack.h       |  10 ++++\n>  transport-helper.c |   8 ++-\n>  transport.c        | 118 +++++++++++++++++++++++++++++++++++++++++++--\n>  transport.h        |  11 +++++\n>  5 files changed, 164 insertions(+), 7 deletions(-)\n\nKarthik suggested adding tests at this stage, but I see no tests here.\nMaybe the tests are added later, but I agree with Karthik that it\nwould be nice to add them early if possible.\n\n> diff --git a/transport-helper.c b/transport-helper.c\n> index 09b3560ffd..841a32e80a 100644\n> --- a/transport-helper.c\n> +++ b/transport-helper.c\n> @@ -699,13 +699,17 @@ static int fetch_refs(struct transport *transport,\n>\n>         /*\n>          * If we reach here, then the server, the client, and/or the transport\n> -        * helper does not support protocol v2. --negotiate-only requires\n> -        * protocol v2.\n> +        * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n> +        * require protocol v2.\n>          */\n>         if (data->transport_options.acked_commits) {\n>                 warning(_(\"--negotiate-only requires protocol v2\"));\n>                 return -1;\n>         }\n> +       if (transport->smart_options->object_info) {\n> +               // fail the command explicitly to avoid further commands input\n\nWe use \"/* stuff */\" for one line comments instead of \"// stuff\". Also\nthe comment could go before the if (...) above and the \"{\" and \"}\"\ncould be dropped.\n\n> +               die(_(\"remote-object-info requires protocol v2\"));\n> +       }\n\n[...]\n\n> +static int fetch_object_info(struct transport *transport, struct object_info *object_info_data)\n> +{\n> +       int size_index = -1;\n> +       struct git_transport_data *data = transport->data;\n> +       struct object_info_args args = { 0 };\n> +       struct packet_reader reader;\n> +\n> +       args.server_options = transport->server_options;\n> +       args.object_info_options = transport->smart_options->object_info_options;\n> +       args.oids = transport->smart_options->object_info_oids;\n> +\n> +       connect_setup(transport, 0);\n> +       packet_reader_init(&reader, data->fd[0], NULL, 0,\n> +                       PACKET_READ_CHOMP_NEWLINE |\n> +                       PACKET_READ_GENTLE_ON_EOF |\n> +                       PACKET_READ_DIE_ON_ERR_PACKET);\n> +       data->version = discover_version(&reader);\n> +\n> +       transport->hash_algo = reader.hash_algo;\n> +\n> +       switch (data->version) {\n> +       case protocol_v2:\n> +               if (!server_supports_v2(\"object-info\"))\n> +                       return -1;\n> +               if (unsorted_string_list_has_string(args.object_info_options, \"size\")\n> +                               && !server_supports_feature(\"object-info\", \"size\", 0)) {\n> +                       return -1;\n> +               }\n\nThe \"{\" and \"}\" can be dropped here too.\n\n> +               send_object_info_request(data->fd[1], &args);\n> +               break;\n> +       case protocol_v1:\n> +       case protocol_v0:\n> +               die(_(\"wrong protocol version. expected v2\"));\n> +       case protocol_unknown_version:\n> +               BUG(\"unknown protocol version\");\n> +       }\n> +\n> +       for (size_t i = 0; i < args.object_info_options->nr; i++) {\n> +               if (packet_reader_read(&reader) != PACKET_READ_NORMAL) {\n> +                       check_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n> +                       return -1;\n> +               }\n> +               if (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n> +                       if (!strcmp(reader.line, \"size\")) {\n> +                               size_index = i;\n> +                               for (size_t j = 0; j < args.oids->nr; j++) {\n> +                                       object_info_data[j].sizep = xcalloc(1, sizeof(long));\n> +                               }\n\nThe \"{\" and \"}\" can be dropped here too.\n\n> +                       }\n> +                       continue;\n> +               }\n> +               return -1;\n> +       }\n> +\n> +       for (size_t i = 0; packet_reader_read(&reader) == PACKET_READ_NORMAL && i < args.oids->nr; i++){\n> +               struct string_list object_info_values = STRING_LIST_INIT_DUP;\n> +\n> +               string_list_split(&object_info_values, reader.line, ' ', -1);\n> +               if (0 <= size_index) {\n> +                       if (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n> +                               die(\"object-info: not our ref %s\",\n> +                                       object_info_values.items[0].string);\n> +\n> +                       *object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n> +\n\nThis blank line can be removed.\n\n> +               }\n> +\n> +               string_list_clear(&object_info_values, 0);\n> +       }\n> +       check_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n> +\n> +       return 0;\n> +}\n> +\n>  static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n>                                         struct transport_ls_refs_options *options)\n>  {\n> @@ -413,6 +487,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n>         struct ref *refs = NULL;\n>         struct fetch_pack_args args;\n>         struct ref *refs_tmp = NULL;\n> +       struct ref *object_info_refs = NULL;\n>\n>         memset(&args, 0, sizeof(args));\n>         args.uploadpack = data->options.uploadpack;\n> @@ -439,11 +514,36 @@ static int fetch_refs_via_pack(struct transport *transport,\n>         args.server_options = transport->server_options;\n>         args.negotiation_tips = data->options.negotiation_tips;\n>         args.reject_shallow_remote = transport->smart_options->reject_shallow;\n> +       args.object_info = transport->smart_options->object_info;\n> +\n> +       if (transport->smart_options\n> +               && transport->smart_options->object_info\n> +               && transport->smart_options->object_info_oids->nr > 0) {\n> +               struct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n> +\n> +               if (!fetch_object_info(transport, data->options.object_info_data))\n> +                       goto cleanup;\n> +\n> +               args.object_info_data = data->options.object_info_data;\n> +               args.quiet = 1;\n> +               args.no_progress = 1;\n> +               for (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> +                       ref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n> +                       ref_itr->exact_oid = 1;\n> +                       if (i == transport->smart_options->object_info_oids->nr - 1)\n> +                               /* last element, no need to allocat to next */\n\ns/allocat/allocate/\n\n> +                               ref_itr -> next = NULL;\n> +                       else\n> +                               ref_itr->next = alloc_ref(\"\");\n>\n> -       if (!data->finished_handshake) {\n> -               int i;\n> +                       ref_itr = ref_itr->next;\n> +               }\n> +\n> +               transport->remote_refs = object_info_refs;\n> +\n> +       } else if (!data->finished_handshake) {\n>                 int must_list_refs = 0;\n> -               for (i = 0; i < nr_heads; i++) {\n> +               for (int i = 0; i < nr_heads; i++) {\n>                         if (!to_fetch[i]->exact_oid) {\n>                                 must_list_refs = 1;\n>                                 break;\n> @@ -481,23 +581,31 @@ static int fetch_refs_via_pack(struct transport *transport,\n>                           &transport->pack_lockfiles, data->version);\n>\n>         data->finished_handshake = 0;\n> +       if (args.object_info) {\n> +               struct ref *ref_cpy_reader = object_info_refs;\n> +               for (int i = 0; ref_cpy_reader; i++) {\n> +                       oid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n\nThis line might want to be folded.\n\n\n> +                       ref_cpy_reader = ref_cpy_reader->next;\n> +               }\n> +       }\n> +\n"},{"id":"503348","messageId":"CAP8UFD0DWWi19HesaBEHtWXrcqj0184xKRAehwKCUJ-bhR5saQ@mail.gmail.com","threadId":"61695","inReplyTo":"20240720034337.57125-7-eric.peijian@gmail.com","subject":"Re: [PATCH v2 6/6] cat-file: add remote-object-info to batch-command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-09-24T12:13:00Z","receivedAt":"2024-09-24T12:13:14Z","isPatch":true,"body":"On Sat, Jul 20, 2024 at 5:44 AM Eric Ju <eric.peijian@gmail.com> wrote:\n\n> +remote-object-info <remote> <object>...::\n> +       Print object info for object references `<object>` at specified <remote> without\n> +       downloading objects from remote. If the object-info capability is not\n> +       supported by the server, the objects will be downloaded instead.\n> +       Error when no object references is provided.\n\nMaybe s/is provided/are provided/\n\n> +       This command may be combined with `--buffer`.\n\n>  `deltabase`::\n>         If the object is stored as a delta on-disk, this expands to the\n>         full hex representation of the delta base object name.\n>         Otherwise, expands to the null OID (all zeroes). See `CAVEATS`\n> -       below.\n> +       below. Not supported by `remote-object-info`.\n>\n>  `rest`::\n>         If this atom is used in the output string, input lines are split\n> @@ -314,7 +323,9 @@ newline. The available atoms are:\n>         line) are output in place of the `%(rest)` atom.\n>\n>  If no format is specified, the default format is `%(objectname)\n> -%(objecttype) %(objectsize)`.\n> +%(objecttype) %(objectsize)`, except remote-object-info command who uses\n\ns/except remote-object-info command who uses/except for\n`remote-object-info` commands which use/\n\n> +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> +When \"%(objecttype)\" is supported, default format should be unified.\n>\n>  If `--batch` is specified, or if `--batch-command` is used with the `contents`\n>  command, the object information is followed by the object contents (consisting\n> @@ -396,6 +407,10 @@ scripting purposes.\n>  CAVEATS\n>  -------\n>\n> +Note that since objecttype, objectsize:disk and deltabase are currently not supported by the\n\ns/objecttype, objectsize:disk and deltabase/%(objecttype),\n%(objectsize:disk) and %(deltabase)/\n\n> +remote-object-info, git will error and exit when they are in the format string.\n\ns//remote-object-info, git /`remote-object-info` command, we/\n\n> +\n> +\n\nMaybe a single blank line is enough.\n\n>  Note that the sizes of objects on disk are reported accurately, but care\n>  should be taken in drawing conclusions about which refs or objects are\n>  responsible for disk usage. The size of a packed non-delta object may be\n\n[...]\n\n> +       gtransport = transport_get(remote, NULL);\n> +       if (gtransport->smart_options) {\n> +               CALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> +               gtransport->smart_options->object_info = 1;\n> +               gtransport->smart_options->object_info_oids = &object_info_oids;\n> +               /*\n> +                * 'size' is the only option currently supported.\n> +                * Other options that are passed in the format will exit with error.\n> +                */\n> +               if (strstr(opt->format, \"%(objectsize)\")) {\n> +                       string_list_append(&object_info_options, \"size\");\n> +               } else {\n> +                       die(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n> +               }\n\nSomething like the following might be a bit shorter and simpler:\n\n  /* 'objectsize' is the only option currently supported */\n  if (!strstr(opt->format, \"%(objectsize)\"))\n          die(_(\"%s is currently not supported with\nremote-object-info\"), opt->format);\n\n  string_list_append(&object_info_options, \"size\");\n\n> +               if (object_info_options.nr > 0) {\n> +                       gtransport->smart_options->object_info_options = &object_info_options;\n> +                       gtransport->smart_options->object_info_data = remote_object_info;\n> +                       retval = transport_fetch_refs(gtransport, NULL);\n> +               }\n> +       } else {\n> +               retval = -1;\n> +       }\n\n[...]\n\n> +       opt->use_remote_info = 1;\n> +       data->skip_object_info = 1;\n> +       for (size_t i = 0; i < object_info_oids.nr; i++) {\n> +\n> +               data->oid = object_info_oids.oid[i];\n> +\n> +               if (remote_object_info[i].sizep) {\n> +                       data->size = *remote_object_info[i].sizep;\n> +               } else {\n> +                       /*\n> +                        * When reaching here, it means remote-object-info can't retrive\n\ns/retrive/retrieve/\n\n> +                        * infomation from server withoug downloading them, and the objects\n\ns/infomation from server withoug/information from server without/\n\n> +                        * have been fetched to client already.\n> +                        * Print the infomation using the logic for local objects.\n\ns/infomation/information/\n\n> +                        */\n> +                       data->skip_object_info = 0;\n> +               }\n> +\n> +               opt->batch_mode = BATCH_MODE_INFO;\n> +               batch_object_write(argv[i+1], output, opt, data, NULL, 0);\n> +\n> +       }\n> +       opt->use_remote_info = 0;\n> +       data->skip_object_info = 0;\n> +\n> +cleanup:\n> +       for (size_t i = 0; i < object_info_oids.nr; i++)\n> +               free_object_info_contents(&remote_object_info[i]);\n> +       free(line_to_split);\n> +       free(argv);\n> +       free(remote_object_info);\n> +}\n> +\n>  static void dispatch_calls(struct batch_options *opt,\n>                 struct strbuf *output,\n>                 struct expand_data *data,\n> @@ -696,9 +803,10 @@ static const struct parse_cmd {\n>         parse_cmd_fn_t fn;\n>         unsigned takes_args;\n>  } commands[] = {\n> -       { \"contents\", parse_cmd_contents, 1},\n> -       { \"info\", parse_cmd_info, 1},\n> -       { \"flush\", NULL, 0},\n> +       { \"contents\", parse_cmd_contents, 1 },\n> +       { \"info\", parse_cmd_info, 1 },\n> +       { \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n> +       { \"flush\", NULL, 0 },\n\nI am not sure it's a good thing to add a space before \"}\".\n\n>  };\n"},{"id":"503373","messageId":"xmqq1q19ufho.fsf@gitster.g","threadId":"61695","inReplyTo":"CAP8UFD0WVgTfgY5aBr8wUxFWi8ggTxL66jnRLLnVkTOniL0Wvg@mail.gmail.com","subject":"Re: [PATCH v2 4/6] transport: add client support for object-info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-09-24T17:29:55Z","receivedAt":"2024-09-24T17:29:58Z","isPatch":true,"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Sat, Jul 20, 2024 at 5:43 AM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> [...]\n>\n>>  fetch-pack.c       |  24 +++++++++\n>>  fetch-pack.h       |  10 ++++\n>>  transport-helper.c |   8 ++-\n>>  transport.c        | 118 +++++++++++++++++++++++++++++++++++++++++++--\n>>  transport.h        |  11 +++++\n>>  5 files changed, 164 insertions(+), 7 deletions(-)\n>\n> Karthik suggested adding tests at this stage, but I see no tests here.\n> Maybe the tests are added later, but I agree with Karthik that it\n> would be nice to add them early if possible.\n> ...\n>> +       if (args.object_info) {\n>> +               struct ref *ref_cpy_reader = object_info_refs;\n>> +               for (int i = 0; ref_cpy_reader; i++) {\n>> +                       oid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n>\n> This line might want to be folded.\n\nThanks for a review on this long patch.\n"},{"id":"503488","messageId":"CAN2LT1AtwfbfbPQH3d=DvQ=VHezNKmHhfOix2DizL9+u5D8U3w@mail.gmail.com","threadId":"61695","inReplyTo":"CAP8UFD0DWWi19HesaBEHtWXrcqj0184xKRAehwKCUJ-bhR5saQ@mail.gmail.com","subject":"Re: [PATCH v2 6/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-25T18:12:47Z","receivedAt":"2024-09-25T18:12:59Z","isPatch":true,"body":"On Tue, Sep 24, 2024 at 8:13 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Sat, Jul 20, 2024 at 5:44 AM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> > +remote-object-info <remote> <object>...::\n> > +       Print object info for object references `<object>` at specified <remote> without\n> > +       downloading objects from remote. If the object-info capability is not\n> > +       supported by the server, the objects will be downloaded instead.\n> > +       Error when no object references is provided.\n>\n> Maybe s/is provided/are provided/\n\nThank you. Fixed in V3.\n\n>\n>\n> > +       This command may be combined with `--buffer`.\n>\n> >  `deltabase`::\n> >         If the object is stored as a delta on-disk, this expands to the\n> >         full hex representation of the delta base object name.\n> >         Otherwise, expands to the null OID (all zeroes). See `CAVEATS`\n> > -       below.\n> > +       below. Not supported by `remote-object-info`.\n> >\n> >  `rest`::\n> >         If this atom is used in the output string, input lines are split\n> > @@ -314,7 +323,9 @@ newline. The available atoms are:\n> >         line) are output in place of the `%(rest)` atom.\n> >\n> >  If no format is specified, the default format is `%(objectname)\n> > -%(objecttype) %(objectsize)`.\n> > +%(objecttype) %(objectsize)`, except remote-object-info command who uses\n>\n> s/except remote-object-info command who uses/except for\n> `remote-object-info` commands which use/\n\nThank you. Fixed in V3.\n\n>\n>\n> > +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> > +When \"%(objecttype)\" is supported, default format should be unified.\n> >\n> >  If `--batch` is specified, or if `--batch-command` is used with the `contents`\n> >  command, the object information is followed by the object contents (consisting\n> > @@ -396,6 +407,10 @@ scripting purposes.\n> >  CAVEATS\n> >  -------\n> >\n> > +Note that since objecttype, objectsize:disk and deltabase are currently not supported by the\n>\n> s/objecttype, objectsize:disk and deltabase/%(objecttype),\n> %(objectsize:disk) and %(deltabase)/\n>\n\nThank you.   Fixed in V3.\n>\n> > +remote-object-info, git will error and exit when they are in the format string.\n>\n> s//remote-object-info, git /`remote-object-info` command, we/\n>\n\n Thank you. Fixed in V3.\n\n>\n> > +\n> > +\n>\n> Maybe a single blank line is enough.\n\nThank you. Fixed in V3.\n\n>\n>\n> >  Note that the sizes of objects on disk are reported accurately, but care\n> >  should be taken in drawing conclusions about which refs or objects are\n> >  responsible for disk usage. The size of a packed non-delta object may be\n>\n> [...]\n>\n> > +       gtransport = transport_get(remote, NULL);\n> > +       if (gtransport->smart_options) {\n> > +               CALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> > +               gtransport->smart_options->object_info = 1;\n> > +               gtransport->smart_options->object_info_oids = &object_info_oids;\n> > +               /*\n> > +                * 'size' is the only option currently supported.\n> > +                * Other options that are passed in the format will exit with error.\n> > +                */\n> > +               if (strstr(opt->format, \"%(objectsize)\")) {\n> > +                       string_list_append(&object_info_options, \"size\");\n> > +               } else {\n> > +                       die(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n> > +               }\n>\n> Something like the following might be a bit shorter and simpler:\n>\n>   /* 'objectsize' is the only option currently supported */\n>   if (!strstr(opt->format, \"%(objectsize)\"))\n>           die(_(\"%s is currently not supported with\n> remote-object-info\"), opt->format);\n>\n>   string_list_append(&object_info_options, \"size\");\n\nThank you. Revised in V3.\n\n>\n>\n> > +               if (object_info_options.nr > 0) {\n> > +                       gtransport->smart_options->object_info_options = &object_info_options;\n> > +                       gtransport->smart_options->object_info_data = remote_object_info;\n> > +                       retval = transport_fetch_refs(gtransport, NULL);\n> > +               }\n> > +       } else {\n> > +               retval = -1;\n> > +       }\n>\n> [...]\n>\n> > +       opt->use_remote_info = 1;\n> > +       data->skip_object_info = 1;\n> > +       for (size_t i = 0; i < object_info_oids.nr; i++) {\n> > +\n> > +               data->oid = object_info_oids.oid[i];\n> > +\n> > +               if (remote_object_info[i].sizep) {\n> > +                       data->size = *remote_object_info[i].sizep;\n> > +               } else {\n> > +                       /*\n> > +                        * When reaching here, it means remote-object-info can't retrive\n>\n> s/retrive/retrieve/\n>\n\nThank you. Fixed in V3.\n\n>\n> > +                        * infomation from server withoug downloading them, and the objects\n>\n> s/infomation from server withoug/information from server without/\n>\n\nThank you. Fixed in V3.\n\n> > +                        * have been fetched to client already.\n> > +                        * Print the infomation using the logic for local objects.\n>\n> s/infomation/information/\n\nThank you. Fixed in V3.\n\n>\n>\n> > +                        */\n> > +                       data->skip_object_info = 0;\n> > +               }\n> > +\n> > +               opt->batch_mode = BATCH_MODE_INFO;\n> > +               batch_object_write(argv[i+1], output, opt, data, NULL, 0);\n> > +\n> > +       }\n> > +       opt->use_remote_info = 0;\n> > +       data->skip_object_info = 0;\n> > +\n> > +cleanup:\n> > +       for (size_t i = 0; i < object_info_oids.nr; i++)\n> > +               free_object_info_contents(&remote_object_info[i]);\n> > +       free(line_to_split);\n> > +       free(argv);\n> > +       free(remote_object_info);\n> > +}\n> > +\n> >  static void dispatch_calls(struct batch_options *opt,\n> >                 struct strbuf *output,\n> >                 struct expand_data *data,\n> > @@ -696,9 +803,10 @@ static const struct parse_cmd {\n> >         parse_cmd_fn_t fn;\n> >         unsigned takes_args;\n> >  } commands[] = {\n> > -       { \"contents\", parse_cmd_contents, 1},\n> > -       { \"info\", parse_cmd_info, 1},\n> > -       { \"flush\", NULL, 0},\n> > +       { \"contents\", parse_cmd_contents, 1 },\n> > +       { \"info\", parse_cmd_info, 1 },\n> > +       { \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n> > +       { \"flush\", NULL, 0 },\n>\n> I am not sure it's a good thing to add a space before \"}\".\n>\n\nThank you. Fixed in V3.\n>\n> >  };\n"},{"id":"503489","messageId":"CAN2LT1AabjD8sX9LqFgcEn2cFK+J9fAhEhbZTbQeUHEmW_+cQQ@mail.gmail.com","threadId":"61695","inReplyTo":"87ldzy9vxd.fsf@iotcl.com","subject":"Re: [PATCH v2 6/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-25T18:18:46Z","receivedAt":"2024-09-25T18:18:58Z","isPatch":true,"body":"On Wed, Sep 11, 2024 at 9:12 AM Toon Claes <toon@iotcl.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> [snip]\n>\n> > diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\n> > new file mode 100755\n> > index 0000000000..64eb55bd9e\n> > --- /dev/null\n> > +++ b/t/t1017-cat-file-remote-object-info.sh\n> > @@ -0,0 +1,748 @@\n> > +#!/bin/sh\n> > +\n> > +test_description='git cat-file --batch-command with remote-object-info command'\n> > +\n> > +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> > +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> > +\n> > +. ./test-lib.sh\n> > +\n> > +echo_without_newline () {\n> > +    printf '%s' \"$*\"\n> > +}\n> > +\n> > +echo_without_newline_nul () {\n> > +    echo_without_newline \"$@\" | tr '\\n' '\\0'\n> > +}\n> > +\n> > +strlen () {\n> > +    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> > +}\n> > +\n> > +hello_content=\"Hello World\"\n> > +hello_size=$(strlen \"$hello_content\")\n> > +hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n> > +\n> > +# This is how we get 13:\n> > +# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n> > +# file mode is 100644, which is 6 characters;\n> > +# file name is hello, which is 5 characters\n> > +# a space is 1 character and a null is 1 character\n> > +tree_size=$(($(test_oid rawsz) + 13))\n> > +\n> > +commit_message=\"Initial commit\"\n> > +\n> > +# This is how we get 137:\n> > +# 137 = <tree header> + <a_space> + <a newline> +\n> > +# <Author line> + <a newline> +\n> > +# <Committer line> + <a newline> +\n> > +# <a newline> +\n> > +# <commit message length>\n> > +# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n> > +# to get 177, 2. then deduct 40 hex characters to get 137\n> > +commit_size=$(($(test_oid hexsz) + 137))\n> > +\n> > +tag_header_without_oid=\"type blob\n> > +tag hellotag\n> > +tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n> > +tag_header_without_timestamp=\"object $hello_oid\n> > +$tag_header_without_oid\"\n> > +tag_description=\"This is a tag\"\n> > +tag_content=\"$tag_header_without_timestamp 0 +0000\n> > +\n> > +$tag_description\"\n> > +\n> > +tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n> > +tag_size=$(strlen \"$tag_content\")\n> > +\n> > +set_transport_variables () {\n> > +     hello_sha1=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n> > +     tree_sha1=$(git -C \"$1\" write-tree)\n> > +     commit_sha1=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_sha1)\n> > +     tag_sha1=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n>\n> I see here and various other places in this file names with \"_sha1\". I\n> think it makes more sense to name them \"_oid\" because these works also\n> fine with GIT_TEST_DEFAULT_HASH=sha256.\n>\n> Other than that I don't have any comments about this patch series.\n>\n> --\n> Toon\n\nThank you.  In V3, all the variables end with  \"_sha1\" are changed to\nend with \"_oid\"\n"},{"id":"503490","messageId":"CAN2LT1BaeLHYJRdGiuuTKeaBFJU9WRHxGiWY=fQVOCMufwiwrA@mail.gmail.com","threadId":"61695","inReplyTo":"CAP8UFD0WVgTfgY5aBr8wUxFWi8ggTxL66jnRLLnVkTOniL0Wvg@mail.gmail.com","subject":"Re: [PATCH v2 4/6] transport: add client support for object-info","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-25T18:29:06Z","receivedAt":"2024-09-25T18:29:18Z","isPatch":true,"body":"On Tue, Sep 24, 2024 at 7:45 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Sat, Jul 20, 2024 at 5:43 AM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> [...]\n>\n> >  fetch-pack.c       |  24 +++++++++\n> >  fetch-pack.h       |  10 ++++\n> >  transport-helper.c |   8 ++-\n> >  transport.c        | 118 +++++++++++++++++++++++++++++++++++++++++++--\n> >  transport.h        |  11 +++++\n> >  5 files changed, 164 insertions(+), 7 deletions(-)\n>\n> Karthik suggested adding tests at this stage, but I see no tests here.\n> Maybe the tests are added later, but I agree with Karthik that it\n> would be nice to add them early if possible.\n>\n\nThank you. I’m not sure if there’s an easy way to directly add unit\ntests for the changes in fetch-pack.c, transport-helper.c, and\ntransport.c, as the relevant functions are deeply nested within the\nclient’s call stack. Therefore, I’m attempting to test them indirectly\nthrough git cat-file in t/t1017-cat-file-remote-object-info.sh in the\nnext commit.\n\nSpecifically, in t/t1017-cat-file-remote-object-info.sh:\n\n- The code in transport-helper.c is tested in the cases\n“remote-object-info fails on server with legacy protocol” and\n“remote-object-info fails on server with legacy protocol fallback”.\n\n- The code in transport.c and fetch-pack.c is tested in the cases\nwhere transfer.advertiseobjectinfo is set to true, such as in\n“batch-command remote-object-info http://”, “batch-command\nremote-object-info file://”, and “batch-command remote-object-info\ngit://”. In these tests, we verify that remote-object-info\nsuccessfully retrieves the size from the remote without downloading\nthe objects locally.\n\n\n> > diff --git a/transport-helper.c b/transport-helper.c\n> > index 09b3560ffd..841a32e80a 100644\n> > --- a/transport-helper.c\n> > +++ b/transport-helper.c\n> > @@ -699,13 +699,17 @@ static int fetch_refs(struct transport *transport,\n> >\n> >         /*\n> >          * If we reach here, then the server, the client, and/or the transport\n> > -        * helper does not support protocol v2. --negotiate-only requires\n> > -        * protocol v2.\n> > +        * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n> > +        * require protocol v2.\n> >          */\n> >         if (data->transport_options.acked_commits) {\n> >                 warning(_(\"--negotiate-only requires protocol v2\"));\n> >                 return -1;\n> >         }\n> > +       if (transport->smart_options->object_info) {\n> > +               // fail the command explicitly to avoid further commands input\n>\n> We use \"/* stuff */\" for one line comments instead of \"// stuff\". Also\n> the comment could go before the if (...) above and the \"{\" and \"}\"\n> could be dropped.\n>\n\nThank you. Revised in V3.\n\n> > +               die(_(\"remote-object-info requires protocol v2\"));\n> > +       }\n>\n> [...]\n>\n> > +static int fetch_object_info(struct transport *transport, struct object_info *object_info_data)\n> > +{\n> > +       int size_index = -1;\n> > +       struct git_transport_data *data = transport->data;\n> > +       struct object_info_args args = { 0 };\n> > +       struct packet_reader reader;\n> > +\n> > +       args.server_options = transport->server_options;\n> > +       args.object_info_options = transport->smart_options->object_info_options;\n> > +       args.oids = transport->smart_options->object_info_oids;\n> > +\n> > +       connect_setup(transport, 0);\n> > +       packet_reader_init(&reader, data->fd[0], NULL, 0,\n> > +                       PACKET_READ_CHOMP_NEWLINE |\n> > +                       PACKET_READ_GENTLE_ON_EOF |\n> > +                       PACKET_READ_DIE_ON_ERR_PACKET);\n> > +       data->version = discover_version(&reader);\n> > +\n> > +       transport->hash_algo = reader.hash_algo;\n> > +\n> > +       switch (data->version) {\n> > +       case protocol_v2:\n> > +               if (!server_supports_v2(\"object-info\"))\n> > +                       return -1;\n> > +               if (unsorted_string_list_has_string(args.object_info_options, \"size\")\n> > +                               && !server_supports_feature(\"object-info\", \"size\", 0)) {\n> > +                       return -1;\n> > +               }\n>\n> The \"{\" and \"}\" can be dropped here too.\n>\n\nThank you. Fixed in V3.\n\n> > +               send_object_info_request(data->fd[1], &args);\n> > +               break;\n> > +       case protocol_v1:\n> > +       case protocol_v0:\n> > +               die(_(\"wrong protocol version. expected v2\"));\n> > +       case protocol_unknown_version:\n> > +               BUG(\"unknown protocol version\");\n> > +       }\n> > +\n> > +       for (size_t i = 0; i < args.object_info_options->nr; i++) {\n> > +               if (packet_reader_read(&reader) != PACKET_READ_NORMAL) {\n> > +                       check_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n> > +                       return -1;\n> > +               }\n> > +               if (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n> > +                       if (!strcmp(reader.line, \"size\")) {\n> > +                               size_index = i;\n> > +                               for (size_t j = 0; j < args.oids->nr; j++) {\n> > +                                       object_info_data[j].sizep = xcalloc(1, sizeof(long));\n> > +                               }\n>\n> The \"{\" and \"}\" can be dropped here too.\n>\n\nThank you. Fixed in V3.\n\n> > +                       }\n> > +                       continue;\n> > +               }\n> > +               return -1;\n> > +       }\n> > +\n> > +       for (size_t i = 0; packet_reader_read(&reader) == PACKET_READ_NORMAL && i < args.oids->nr; i++){\n> > +               struct string_list object_info_values = STRING_LIST_INIT_DUP;\n> > +\n> > +               string_list_split(&object_info_values, reader.line, ' ', -1);\n> > +               if (0 <= size_index) {\n> > +                       if (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n> > +                               die(\"object-info: not our ref %s\",\n> > +                                       object_info_values.items[0].string);\n> > +\n> > +                       *object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n> > +\n>\n> This blank line can be removed.\n>\n\nThank you. Fixed in V3.\n\n> > +               }\n> > +\n> > +               string_list_clear(&object_info_values, 0);\n> > +       }\n> > +       check_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n> > +\n> > +       return 0;\n> > +}\n> > +\n> >  static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n> >                                         struct transport_ls_refs_options *options)\n> >  {\n> > @@ -413,6 +487,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >         struct ref *refs = NULL;\n> >         struct fetch_pack_args args;\n> >         struct ref *refs_tmp = NULL;\n> > +       struct ref *object_info_refs = NULL;\n> >\n> >         memset(&args, 0, sizeof(args));\n> >         args.uploadpack = data->options.uploadpack;\n> > @@ -439,11 +514,36 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >         args.server_options = transport->server_options;\n> >         args.negotiation_tips = data->options.negotiation_tips;\n> >         args.reject_shallow_remote = transport->smart_options->reject_shallow;\n> > +       args.object_info = transport->smart_options->object_info;\n> > +\n> > +       if (transport->smart_options\n> > +               && transport->smart_options->object_info\n> > +               && transport->smart_options->object_info_oids->nr > 0) {\n> > +               struct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n> > +\n> > +               if (!fetch_object_info(transport, data->options.object_info_data))\n> > +                       goto cleanup;\n> > +\n> > +               args.object_info_data = data->options.object_info_data;\n> > +               args.quiet = 1;\n> > +               args.no_progress = 1;\n> > +               for (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> > +                       ref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n> > +                       ref_itr->exact_oid = 1;\n> > +                       if (i == transport->smart_options->object_info_oids->nr - 1)\n> > +                               /* last element, no need to allocat to next */\n>\n> s/allocat/allocate/\n>\n\nThank you. Fixed in V3.\n\n> > +                               ref_itr -> next = NULL;\n> > +                       else\n> > +                               ref_itr->next = alloc_ref(\"\");\n> >\n> > -       if (!data->finished_handshake) {\n> > -               int i;\n> > +                       ref_itr = ref_itr->next;\n> > +               }\n> > +\n> > +               transport->remote_refs = object_info_refs;\n> > +\n> > +       } else if (!data->finished_handshake) {\n> >                 int must_list_refs = 0;\n> > -               for (i = 0; i < nr_heads; i++) {\n> > +               for (int i = 0; i < nr_heads; i++) {\n> >                         if (!to_fetch[i]->exact_oid) {\n> >                                 must_list_refs = 1;\n> >                                 break;\n> > @@ -481,23 +581,31 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >                           &transport->pack_lockfiles, data->version);\n> >\n> >         data->finished_handshake = 0;\n> > +       if (args.object_info) {\n> > +               struct ref *ref_cpy_reader = object_info_refs;\n> > +               for (int i = 0; ref_cpy_reader; i++) {\n> > +                       oid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n>\n> This line might want to be folded.\n>\n\nThank you. Fixed in V3.\n\n\n\n>\n> > +                       ref_cpy_reader = ref_cpy_reader->next;\n> > +               }\n> > +       }\n> > +\n"},{"id":"503495","messageId":"CAN2LT1DvrXwAZsjwgEh1waBAVgTNf3UHztsKgY0aGz6Lt3Dfhw@mail.gmail.com","threadId":"61695","inReplyTo":"CAP8UFD1J4JdR-QP9em=_0q8CQZLf9nE7PLE2=kkvxY218fa-ig@mail.gmail.com","subject":"Re: [PATCH v2 1/6] fetch-pack: refactor packet writing","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-25T20:42:57Z","receivedAt":"2024-09-25T20:43:09Z","isPatch":true,"body":"On Tue, Sep 24, 2024 at 7:45 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Sat, Jul 20, 2024 at 5:43 AM Eric Ju <eric.peijian@gmail.com> wrote:\n> >\n> > From: Calvin Wan <calvinwan@google.com>\n> >\n> > A subsequent patch needs to write capabilities for another command.\n> > Refactor write_fetch_command_and_capabilities() to be a more general\n> > purpose function write_command_and_capabilities(), so that it can be\n> > used by both fetch and future command.\n> >\n> > Here \"command\" means the \"operations\" supported by Git’s wire protocol\n> > https://git-scm.com/docs/protocol-v2. An example would be a\n> > git's subcommand, such as git-fetch(1); or an operation supported by\n> > the server side such as \"object-info\" implemented in \"a2ba162cda\n> > (object-info: support for retrieving object info, 2021-04-20)\".\n>\n> I agree that reusing or refactoring the new\n> write_command_and_capabilities() function for more commands can be\n> done in a separate series that could perhaps also move the new\n> function to  Maybe this could be added to the commit message\n> though.\n>\n\nThank you, I am adding this to the commit message,\n\"In a future separate series, we can move\nwrite_command_and_capabilities() to a higher-level file, such as\nconnect.c, so that it becomes accessible to other commands.\"\n\n> [...]\n>\n> > -static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> > -                                                const struct string_list *server_options)\n> > +static void write_command_and_capabilities(struct strbuf *req_buf,\n> > +                                                const struct string_list *server_options, const char* command)\n>\n> In https://lore.kernel.org/git/xmqqfsn0qsi4.fsf@gitster.g/ Junio\n> suggested swaping the \"command\" and \"server_options\" arguments as well\n> as sticking the \"*\" to \"command\" instead of \"char\", so:\n>\n> static void write_command_and_capabilities(struct strbuf *req_buf,\n>\n> const char *command,\n>\n> const struct string_list *server_options)\n>\n> The rest of the patch looks good.\n\nThank you. The format is changed in V3.\n"},{"id":"503503","messageId":"20240926013856.35527-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH v3 0/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-26T01:38:50Z","receivedAt":"2024-09-26T01:39:19Z","isPatch":true,"body":"This is a continuation of Calvin Wan's (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info command at [1].\n\nSometimes it is useful to get information about an object without having to download\nit completely. The server logic for retrieving size has already been implemented and merged in\n\"a2ba162cda (object-info: support for retrieving object info, 2021-04-20)\"[2].\nThis patch series implement the client option for it.\n\nThis patch series add the `remote-object-info` command to `cat-file --batch-command`.\nThis command allows the client to make an object-info command request to a server\nthat supports protocol v2. If the server is v2, but does not have\nobject-info capability, the entire object is fetched and the\nrelevant object info is returned.\n\nA few questions open for discussions please:\n\n1. In the current implementation, if a user puts `remote-object-info` in protocol v1,\n   `cat-file --batch-command` will die. Which way do we prefer? \"error and exit (i.e. die)\"\n   or \"warn and wait for new command\".\n\n2. Right now, only the size is supported. If the batch command format\n   contains objectsize:disk or deltabase, it will die. The question\n   is about objecttype. In the current implementation, it will die too.\n   But dying on objecttype breaks the default format. We have changed the\n   default format to %(objectname) %(objectsize) when remote-object-info is used.\n   Any suggestions on this approach?\n\n[1] https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[2] https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n\nV1 of the patch series can be found here:\nhttps://lore.kernel.org/git/20240628190503.67389-1-eric.peijian@gmail.com/\n\nv2 of the patch series can be found here:\nhttps://lore.kernel.org/git/20240720034337.57125-1-eric.peijian@gmail.com/\n\nChanges since V2\n================\n\n- Fix typos and formatting errors\n\n- Add more information in commit messages\n\n- Confirm that new logics in transport.c, fetch-pack.c and transport-helper.c\n  are covered in the new test file t1017-cat-file-remote-object-info.sh\n\nThank you.\n\nEric Ju\n\nCalvin Wan (5):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n  cat-file: add remote-object-info to batch-command\n\nEric Ju (1):\n  cat-file: add declaration of variable i inside its for loop\n\n Documentation/git-cat-file.txt         |  22 +-\n builtin/cat-file.c                     | 119 +++-\n fetch-pack.c                           |  49 +-\n fetch-pack.h                           |  10 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/t1017-cat-file-remote-object-info.sh | 750 +++++++++++++++++++++++++\n transport-helper.c                     |   8 +-\n transport.c                            | 116 +++-\n transport.h                            |  11 +\n 11 files changed, 1070 insertions(+), 33 deletions(-)\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\nRange-diff against v2:\n1:  f90a74cbb2 ! 1:  b570dee186 fetch-pack: refactor packet writing\n    @@ Metadata\n      ## Commit message ##\n         fetch-pack: refactor packet writing\n     \n    -    A subsequent patch needs to write capabilities for another command.\n         Refactor write_fetch_command_and_capabilities() to be a more general\n         purpose function write_command_and_capabilities(), so that it can be\n         used by both fetch and future command.\n    @@ Commit message\n         the server side such as \"object-info\" implemented in \"a2ba162cda\n         (object-info: support for retrieving object info, 2021-04-20)\".\n     \n    +    In a future separate series, we can move\n    +    write_command_and_capabilities() to a higher-level file, such as\n    +    connect.c, so that it becomes accessible to other commands.\n    +\n         Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Helped-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Calvin Wan <calvinwan@google.com>\n    @@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n     -static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n     -\t\t\t\t\t\t const struct string_list *server_options)\n     +static void write_command_and_capabilities(struct strbuf *req_buf,\n    -+\t\t\t\t\t\t const struct string_list *server_options, const char* command)\n    ++\t\t\t\t\t\t\t\t\t\t   const char *command,\n    ++\t\t\t\t\t\t\t\t\t\t   const struct string_list *server_options)\n      {\n      \tconst char *hash_name;\n      \n    @@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n      \tif (server_supports_v2(\"agent\"))\n      \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n      \tif (advertise_sid && server_supports_v2(\"session-id\"))\n    +@@ fetch-pack.c: static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n    + \tpacket_buf_delim(req_buf);\n    + }\n    + \n    ++\n    ++void send_object_info_request(int fd_out, struct object_info_args *args)\n    ++{\n    ++\tstruct strbuf req_buf = STRBUF_INIT;\n    ++\n    ++\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n    ++\n    ++\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n    ++\t\tpacket_buf_write(&req_buf, \"size\");\n    ++\n    ++\tif (args->oids) {\n    ++\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n    ++\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n    ++\t}\n    ++\n    ++\tpacket_buf_flush(&req_buf);\n    ++\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n    ++\t\tdie_errno(_(\"unable to write request to remote\"));\n    ++\n    ++\tstrbuf_release(&req_buf);\n    ++}\n    ++\n    + static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n    + \t\t\t      struct fetch_pack_args *args,\n    + \t\t\t      const struct ref *wants, struct oidset *common,\n     @@ fetch-pack.c: static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n      \tint done_sent = 0;\n      \tstruct strbuf req_buf = STRBUF_INIT;\n      \n     -\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n    -+\twrite_command_and_capabilities(&req_buf, args->server_options, \"fetch\");\n    ++\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n      \n      \tif (args->use_thin_pack)\n      \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n    @@ fetch-pack.c: void negotiate_using_fetch(const struct oid_array *negotiation_tip\n      \t\t\t\t\t   negotiation_round);\n      \t\tstrbuf_reset(&req_buf);\n     -\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n    -+\t\twrite_command_and_capabilities(&req_buf, server_options, \"fetch\");\n    ++\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n      \n      \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n      \n2:  64ec1ab9f9 = 2:  e8777e8776 fetch-pack: move fetch initialization\n3:  68c35ab6c1 = 3:  d00d19cf2c serve: advertise object-info feature\n4:  3e5a65ab46 ! 4:  3e1773910c transport: add client support for object-info\n    @@ fetch-pack.c: static void write_command_and_capabilities(struct strbuf *req_buf,\n      \tpacket_buf_delim(req_buf);\n      }\n      \n    -+void send_object_info_request(int fd_out, struct object_info_args *args)\n    -+{\n    -+\tstruct strbuf req_buf = STRBUF_INIT;\n    -+\n    -+\twrite_command_and_capabilities(&req_buf, args->server_options, \"object-info\");\n    -+\n    -+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n    -+\t\tpacket_buf_write(&req_buf, \"size\");\n    -+\n    -+\tif (args->oids) {\n    -+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n    -+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n    -+\t}\n    -+\n    -+\tpacket_buf_flush(&req_buf);\n    -+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n    -+\t\tdie_errno(_(\"unable to write request to remote\"));\n    -+\n    -+\tstrbuf_release(&req_buf);\n    -+}\n    -+\n    - static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n    - \t\t\t      struct fetch_pack_args *args,\n    - \t\t\t      const struct ref *wants, struct oidset *common,\n    +-\n    + void send_object_info_request(int fd_out, struct object_info_args *args)\n    + {\n    + \tstruct strbuf req_buf = STRBUF_INIT;\n     @@ fetch-pack.c: static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n      \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n      \t\targs->deepen = 1;\n    @@ transport-helper.c: static int fetch_refs(struct transport *transport,\n      \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n      \t\treturn -1;\n      \t}\n    -+\tif (transport->smart_options->object_info) {\n    -+\t\t// fail the command explicitly to avoid further commands input\n    -+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n    -+\t}\n      \n    ++\t/* fail the command explicitly to avoid further commands input. */\n    ++\tif (transport->smart_options->object_info)\n    ++\t\tdie(_(\"remote-object-info requires protocol v2\"));\n    ++\n      \tif (!data->get_refs_list_called)\n      \t\tget_refs_list_using_list(transport, 0);\n    + \n     \n      ## transport.c ##\n     @@ transport.c: static struct ref *handshake(struct transport *transport, int for_push,\n    @@ transport.c: static struct ref *handshake(struct transport *transport, int for_p\n     +\t\tif (!server_supports_v2(\"object-info\"))\n     +\t\t\treturn -1;\n     +\t\tif (unsorted_string_list_has_string(args.object_info_options, \"size\")\n    -+\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0)) {\n    ++\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0))\n     +\t\t\treturn -1;\n    -+\t\t}\n     +\t\tsend_object_info_request(data->fd[1], &args);\n     +\t\tbreak;\n     +\tcase protocol_v1:\n    @@ transport.c: static struct ref *handshake(struct transport *transport, int for_p\n     +\t\tif (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n     +\t\t\tif (!strcmp(reader.line, \"size\")) {\n     +\t\t\t\tsize_index = i;\n    -+\t\t\t\tfor (size_t j = 0; j < args.oids->nr; j++) {\n    ++\t\t\t\tfor (size_t j = 0; j < args.oids->nr; j++)\n     +\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n    -+\t\t\t\t}\n     +\t\t\t}\n     +\t\t\tcontinue;\n     +\t\t}\n    @@ transport.c: static struct ref *handshake(struct transport *transport, int for_p\n     +\t\t\t\t\tobject_info_values.items[0].string);\n     +\n     +\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n    -+\n     +\t\t}\n     +\n     +\t\tstring_list_clear(&object_info_values, 0);\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n     +\t\t\tref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n     +\t\t\tref_itr->exact_oid = 1;\n     +\t\t\tif (i == transport->smart_options->object_info_oids->nr - 1)\n    -+\t\t\t\t/* last element, no need to allocat to next */\n    ++\t\t\t\t/* last element, no need to allocate to next */\n     +\t\t\t\tref_itr -> next = NULL;\n     +\t\t\telse\n     +\t\t\t\tref_itr->next = alloc_ref(\"\");\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n     +\tif (args.object_info) {\n     +\t\tstruct ref *ref_cpy_reader = object_info_refs;\n     +\t\tfor (int i = 0; ref_cpy_reader; i++) {\n    -+\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid, &args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n    ++\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid,\n    ++\t\t\t\t&args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n     +\t\t\tref_cpy_reader = ref_cpy_reader->next;\n     +\t\t}\n     +\t}\n5:  eb1c87f3fd = 5:  bb110fbc93 cat-file: add declaration of variable i inside its for loop\n6:  e33c1f93bc ! 6:  6dd143c164 cat-file: add remote-object-info to batch-command\n    @@ Documentation/git-cat-file.txt: info <object>::\n     +\tPrint object info for object references `<object>` at specified <remote> without\n     +\tdownloading objects from remote. If the object-info capability is not\n     +\tsupported by the server, the objects will be downloaded instead.\n    -+\tError when no object references is provided.\n    ++\tError when no object references are provided.\n     +\tThis command may be combined with `--buffer`.\n     +\n      flush::\n    @@ Documentation/git-cat-file.txt: newline. The available atoms are:\n      \n      If no format is specified, the default format is `%(objectname)\n     -%(objecttype) %(objectsize)`.\n    -+%(objecttype) %(objectsize)`, except remote-object-info command who uses\n    ++%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n     +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n     +When \"%(objecttype)\" is supported, default format should be unified.\n      \n    @@ Documentation/git-cat-file.txt: scripting purposes.\n      CAVEATS\n      -------\n      \n    -+Note that since objecttype, objectsize:disk and deltabase are currently not supported by the\n    -+remote-object-info, git will error and exit when they are in the format string.\n    -+\n    ++Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are currently not supported by the\n    ++`remote-object-info` command, we will error and exit when they are in the format string.\n     +\n      Note that the sizes of objects on disk are reported accurately, but care\n      should be taken in drawing conclusions about which refs or objects are\n    @@ builtin/cat-file.c: static void batch_one_object(const char *obj_name,\n     +\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n     +\t\tgtransport->smart_options->object_info = 1;\n     +\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n    -+\t\t/*\n    -+\t\t * 'size' is the only option currently supported.\n    -+\t\t * Other options that are passed in the format will exit with error.\n    -+\t\t */\n    -+\t\tif (strstr(opt->format, \"%(objectsize)\")) {\n    -+\t\t\tstring_list_append(&object_info_options, \"size\");\n    -+\t\t} else {\n    ++\n    ++\t\t/* 'objectsize' is the only option currently supported */\n    ++\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n     +\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n    -+\t\t}\n    ++\n    ++\t\tstring_list_append(&object_info_options, \"size\");\n    ++\n     +\t\tif (object_info_options.nr > 0) {\n     +\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n     +\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n    @@ builtin/cat-file.c: static void parse_cmd_info(struct batch_options *opt,\n     +\t\t\tdata->size = *remote_object_info[i].sizep;\n     +\t\t} else {\n     +\t\t\t/*\n    -+\t\t\t * When reaching here, it means remote-object-info can't retrive\n    -+\t\t\t * infomation from server withoug downloading them, and the objects\n    ++\t\t\t * When reaching here, it means remote-object-info can't retrieve\n    ++\t\t\t * information from server without downloading them, and the objects\n     +\t\t\t * have been fetched to client already.\n    -+\t\t\t * Print the infomation using the logic for local objects.\n    ++\t\t\t * Print the information using the logic for local objects.\n     +\t\t\t */\n     +\t\t\tdata->skip_object_info = 0;\n     +\t\t}\n    @@ builtin/cat-file.c: static void parse_cmd_info(struct batch_options *opt,\n      \t\tstruct strbuf *output,\n      \t\tstruct expand_data *data,\n     @@ builtin/cat-file.c: static const struct parse_cmd {\n    - \tparse_cmd_fn_t fn;\n    - \tunsigned takes_args;\n      } commands[] = {\n    --\t{ \"contents\", parse_cmd_contents, 1},\n    --\t{ \"info\", parse_cmd_info, 1},\n    --\t{ \"flush\", NULL, 0},\n    -+\t{ \"contents\", parse_cmd_contents, 1 },\n    -+\t{ \"info\", parse_cmd_info, 1 },\n    -+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n    -+\t{ \"flush\", NULL, 0 },\n    + \t{ \"contents\", parse_cmd_contents, 1},\n    + \t{ \"info\", parse_cmd_info, 1},\n    ++\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1},\n    + \t{ \"flush\", NULL, 0},\n      };\n      \n    - static void batch_objects_command(struct batch_options *opt,\n     \n      ## object-file.c ##\n     @@ object-file.c: int read_loose_object(const char *path,\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +tag_size=$(strlen \"$tag_content\")\n     +\n     +set_transport_variables () {\n    -+\thello_sha1=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n    -+\ttree_sha1=$(git -C \"$1\" write-tree)\n    -+\tcommit_sha1=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_sha1)\n    -+\ttag_sha1=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n    ++\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n    ++\ttree_oid=$(git -C \"$1\" write-tree)\n    ++\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n    ++\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n     +\ttag_size=$(strlen \"$tag_content\")\n     +}\n     +\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n     +# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n     +\n    -+# Test --batch-command remote-object-info with 'git://' transport\n    ++# Test --batch-command remote-object-info with 'git://' transport with\n    ++# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n     +. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n     +start_git_daemon --export-all --enable=receive-pack\n     +daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n     +\n     +\t\t# These results prove remote-object-info can get object info from the remote\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info did not download objects from the remote\n    -+\t\techo \"$hello_sha1 missing\" >>expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >>expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n    -+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1\n    -+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_sha1\n    -+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1\n    -+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n    ++\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n    ++\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n    ++\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n     +\n     +\t\t# These results prove remote-object-info can get object info from the remote\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info did not download objects from the remote\n    -+\t\techo \"$hello_sha1 missing\" >>expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >>expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n    -+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tset_transport_variables \"$daemon_parent\" &&\n     +\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n     +\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n    -+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1\n    -+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1 $tag_sha1\n    ++\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n    ++\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n     +\n     +\t\t# These results prove remote-object-info can get object info from the remote\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info did not download objects from the remote\n    -+\t\techo \"$hello_sha1 missing\" >>expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >>expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n    -+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_sha1 $tree_sha1\n    -+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_sha1 $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n    ++\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tflush\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tset_transport_variables \"$daemon_parent\" &&\n     +\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n     +\n    -+\t\tprintf \"%s\\0\" \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\tprintf \"%s\\0\" \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$tag_sha1 $tag_size\" >>expect &&\n    -+\n    -+\t\tprintf \"%s\\0\" \"$hello_sha1 missing\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$tree_sha1 missing\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$commit_sha1 missing\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$tag_sha1 missing\" >>expect &&\n    -+\n    -+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_sha1 $tree_sha1\n    -+remote-object-info $GIT_DAEMON_URL/parent $commit_sha1 $tag_sha1\n    -+info $hello_sha1\n    -+info $tree_sha1\n    -+info $commit_sha1\n    -+info $tag_sha1\n    ++\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n    ++\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n    ++\n    ++\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n    ++\n    ++\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n    ++remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n    ++info $hello_oid\n    ++info $tree_oid\n    ++info $commit_oid\n    ++info $tag_oid\n     +\" &&\n     +\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n     +\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n     +\n     +\t\t# Prove object is not on the client\n    -+\t\techo \"$hello_sha1 missing\" >expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info can retrieve object info\n    -+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >>expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results are for the info command\n     +\t\t# They prove objects are downloaded\n    -+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >>expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    -+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n    ++\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tEOF\n     +\n     +\t\t# revert server state back\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\n     +stop_git_daemon\n     +\n    -+# Test --batch-command remote-object-info with 'file://' transport\n    ++# Test --batch-command remote-object-info with 'file://' transport with\n    ++# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n     +# shellcheck disable=SC2016\n     +test_expect_success 'create repo to be served by file:// transport' '\n     +\tgit init server &&\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd file_client_empty &&\n     +\n     +\t\t# These results prove remote-object-info can get object info from the remote\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info did not download objects from the remote\n    -+\t\techo \"$hello_sha1 missing\" >>expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >>expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n    -+\t\tremote-object-info \"file://${server_path}\" $hello_sha1\n    -+\t\tremote-object-info \"file://${server_path}\" $tree_sha1\n    -+\t\tremote-object-info \"file://${server_path}\" $commit_sha1\n    -+\t\tremote-object-info \"file://${server_path}\" $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tremote-object-info \"file://${server_path}\" $hello_oid\n    ++\t\tremote-object-info \"file://${server_path}\" $tree_oid\n    ++\t\tremote-object-info \"file://${server_path}\" $commit_oid\n    ++\t\tremote-object-info \"file://${server_path}\" $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd file_client_empty &&\n     +\n     +\t\t# These results prove remote-object-info can get object info from the remote\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info did not download objects from the remote\n    -+\t\techo \"$hello_sha1 missing\" >>expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >>expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n    -+\t\tremote-object-info \"file://${server_path}\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd file_client_empty &&\n     +\n     +\t\t# These results prove remote-object-info can get object info from the remote\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info did not download objects from the remote\n    -+\t\techo \"$hello_sha1 missing\" >>expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >>expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n    -+\t\tremote-object-info \"file://${server_path}\" $hello_sha1 $tree_sha1\n    -+\t\tremote-object-info \"file://${server_path}\" $commit_sha1 $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n    ++\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tflush\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tserver_path=\"$(pwd)/server\" &&\n     +\t\tcd file_client_empty &&\n     +\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command >actual <<-EOF &&\n    -+\t\tremote-object-info \"file://${server_path}\" $hello_sha1 $tree_sha1\n    -+\t\tremote-object-info \"file://${server_path}\" $commit_sha1 $tag_sha1\n    ++\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n    ++\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tserver_path=\"$(pwd)/server\" &&\n     +\t\tcd file_client_empty &&\n     +\n    -+\t\tprintf \"%s\\0\" \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\tprintf \"%s\\0\" \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$tag_sha1 $tag_size\" >>expect &&\n    -+\n    -+\t\tprintf \"%s\\0\" \"$hello_sha1 missing\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$tree_sha1 missing\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$commit_sha1 missing\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$tag_sha1 missing\" >>expect &&\n    -+\n    -+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_sha1 $tree_sha1\n    -+remote-object-info \\\"file://${server_path}\\\" $commit_sha1 $tag_sha1\n    -+info $hello_sha1\n    -+info $tree_sha1\n    -+info $commit_sha1\n    -+info $tag_sha1\n    ++\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n    ++\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n    ++\n    ++\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n    ++\n    ++\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n    ++remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n    ++info $hello_oid\n    ++info $tree_oid\n    ++info $commit_oid\n    ++info $tag_oid\n     +\" &&\n     +\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n     +\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd file_client_empty &&\n     +\n     +\t\t# Prove object is not on the client\n    -+\t\techo \"$hello_sha1 missing\" >expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info can retrieve object info\n    -+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >>expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results are for the info command\n     +\t\t# They prove objects are downloaded\n    -+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >>expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    -+\t\tremote-object-info \"file://${server_path}\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n    ++\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tEOF\n     +\n     +\t\t# revert server state back\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n     +\n     +\t\t# These results prove remote-object-info can get object info from the remote\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info did not download objects from the remote\n    -+\t\techo \"$hello_sha1 missing\" >>expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >>expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_sha1\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n     +\n     +\t\t# These results prove remote-object-info can get object info from the remote\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info did not download objects from the remote\n    -+\t\techo \"$hello_sha1 missing\" >>expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >>expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n     +\n     +\t\t# These results prove remote-object-info can get object info from the remote\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info did not download objects from the remote\n    -+\t\techo \"$hello_sha1 missing\" >>expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >>expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1 $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tflush\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n     +\n    -+\t\techo \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command >actual <<-EOF &&\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_sha1 $tag_sha1\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n     +\t\tEOF\n     +\t\ttest_cmp expect actual\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n     +\n    -+\t\tprintf \"%s\\0\" \"$hello_sha1 $hello_size\" >expect &&\n    -+\t\tprintf \"%s\\0\" \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\tprintf \"%s\\0\" \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n    ++\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n    ++\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n     +\n    -+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_sha1 $tree_sha1\n    -+remote-object-info $HTTPD_URL/smart/http_parent $commit_sha1 $tag_sha1\n    ++\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n    ++remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n     +\" &&\n     +\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n     +\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n     +\n     +\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n     +\t\tEOF\n     +\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n     +\n     +\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n     +\t\tEOF\n     +\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n     +\n     +\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n     +\t\tEOF\n     +\t\ttest_grep \"remote-object-info requires protocol v2\" err\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n     +\n     +\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n     +\t\tEOF\n     +\t\ttest_grep \"remote-object-info requires protocol v2\" err\n     +\t)\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n     +\n     +\t\t# Prove object is not on the client\n    -+\t\techo \"$hello_sha1 missing\" >expect &&\n    -+\t\techo \"$tree_sha1 missing\" >>expect &&\n    -+\t\techo \"$commit_sha1 missing\" >>expect &&\n    -+\t\techo \"$tag_sha1 missing\" >>expect &&\n    ++\t\techo \"$hello_oid missing\" >expect &&\n    ++\t\techo \"$tree_oid missing\" >>expect &&\n    ++\t\techo \"$commit_oid missing\" >>expect &&\n    ++\t\techo \"$tag_oid missing\" >>expect &&\n     +\n     +\t\t# These results prove remote-object-info can retrieve object info\n    -+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >>expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\t# These results are for the info command\n     +\t\t# They prove objects are downloaded\n    -+\t\techo \"$hello_sha1 $hello_size\" >>expect &&\n    -+\t\techo \"$tree_sha1 $tree_size\" >>expect &&\n    -+\t\techo \"$commit_sha1 $commit_size\" >>expect &&\n    -+\t\techo \"$tag_sha1 $tag_size\" >>expect &&\n    ++\t\techo \"$hello_oid $hello_size\" >>expect &&\n    ++\t\techo \"$tree_oid $tree_size\" >>expect &&\n    ++\t\techo \"$commit_oid $commit_size\" >>expect &&\n    ++\t\techo \"$tag_oid $tag_size\" >>expect &&\n     +\n     +\t\tgit cat-file --batch-command >actual <<-EOF &&\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    -+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_sha1 $tree_sha1 $commit_sha1 $tag_sha1\n    -+\t\tinfo $hello_sha1\n    -+\t\tinfo $tree_sha1\n    -+\t\tinfo $commit_sha1\n    -+\t\tinfo $tag_sha1\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n    ++\t\tinfo $hello_oid\n    ++\t\tinfo $tree_oid\n    ++\t\tinfo $commit_oid\n    ++\t\tinfo $tag_oid\n     +\t\tEOF\n     +\n     +\t\t# revert server state back\n-- \n2.46.0\n\n"},{"id":"503504","messageId":"20240926013856.35527-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240926013856.35527-1-eric.peijian@gmail.com","subject":"[PATCH v3 1/6] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-26T01:38:51Z","receivedAt":"2024-09-26T01:39:20Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nRefactor write_fetch_command_and_capabilities() to be a more general\npurpose function write_command_and_capabilities(), so that it can be\nused by both fetch and future command.\n\nHere \"command\" means the \"operations\" supported by Git’s wire protocol\nhttps://git-scm.com/docs/protocol-v2. An example would be a\ngit's subcommand, such as git-fetch(1); or an operation supported by\nthe server side such as \"object-info\" implemented in \"a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20)\".\n\nIn a future separate series, we can move\nwrite_command_and_capabilities() to a higher-level file, such as\nconnect.c, so that it becomes accessible to other commands.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 35 +++++++++++++++++++++++++++++------\n 1 file changed, 29 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f752da93a8..756fb83f89 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1314,13 +1314,14 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n+static void write_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t\t\t\t\t\t   const char *command,\n+\t\t\t\t\t\t\t\t\t\t   const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n@@ -1346,6 +1347,28 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tpacket_buf_delim(req_buf);\n }\n \n+\n+void send_object_info_request(int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids) {\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\t}\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1356,7 +1379,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2174,7 +2197,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.46.0\n\n"},{"id":"503505","messageId":"20240926013856.35527-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240926013856.35527-1-eric.peijian@gmail.com","subject":"[PATCH v3 2/6] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-26T01:38:52Z","receivedAt":"2024-09-26T01:39:21Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 756fb83f89..800505f25f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1700,18 +1700,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.46.0\n\n"},{"id":"503506","messageId":"20240926013856.35527-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240926013856.35527-1-eric.peijian@gmail.com","subject":"[PATCH v3 3/6] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-26T01:38:53Z","receivedAt":"2024-09-26T01:39:22Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex d674764a25..c3d8098642 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -70,7 +70,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -78,6 +78,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.46.0\n\n"},{"id":"503507","messageId":"20240926013856.35527-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240926013856.35527-1-eric.peijian@gmail.com","subject":"[PATCH v3 4/6] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-26T01:38:54Z","receivedAt":"2024-09-26T01:39:23Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes it is useful to get information about an object without having\nto download it completely. The server logic has already been implemented\nin “a2ba162cda (object-info: support for retrieving object info,\n2021-04-20)”.\n\nAdd client functions to communicate with the server.\n\nThe client currently supports requesting a list of object ids with\nfeature 'size' from a v2 server. If a server does not\nadvertise the feature, then the client falls back\nto making the request through 'fetch'.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c       |   4 +-\n fetch-pack.h       |  10 ++++\n transport-helper.c |   8 +++-\n transport.c        | 116 +++++++++++++++++++++++++++++++++++++++++++--\n transport.h        |  11 +++++\n 5 files changed, 141 insertions(+), 8 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 800505f25f..1a9facc1c0 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1347,7 +1347,6 @@ static void write_command_and_capabilities(struct strbuf *req_buf,\n \tpacket_buf_delim(req_buf);\n }\n \n-\n void send_object_info_request(int fd_out, struct object_info_args *args)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n@@ -1706,6 +1705,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex b5c579cdae..5a5211e355 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\n@@ -68,6 +70,12 @@ struct fetch_pack_args {\n \tunsigned connectivity_checked:1;\n };\n \n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n /*\n  * sought represents remote references that should be updated from.\n  * On return, the names that were found on the remote will have been\n@@ -106,4 +114,6 @@ int report_unmatched_refs(struct ref **sought, int nr_sought);\n  */\n int fetch_pack_fsck_objects(void);\n \n+void send_object_info_request(int fd_out, struct object_info_args *args);\n+\n #endif\ndiff --git a/transport-helper.c b/transport-helper.c\nindex c688967b8c..7db74b4ad7 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -709,14 +709,18 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n+\t * require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n \t\treturn -1;\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n \tif (!data->get_refs_list_called)\n \t\tget_refs_list_using_list(transport, 0);\n \ndiff --git a/transport.c b/transport.c\nindex 3c4714581f..8266e347b3 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -368,6 +368,77 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \treturn refs;\n }\n \n+static int fetch_object_info(struct transport *transport, struct object_info *object_info_data)\n+{\n+\tint size_index = -1;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct object_info_args args = { 0 };\n+\tstruct packet_reader reader;\n+\n+\targs.server_options = transport->server_options;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\tdata->version = discover_version(&reader);\n+\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tswitch (data->version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\treturn -1;\n+\t\tif (unsorted_string_list_has_string(args.object_info_options, \"size\")\n+\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0))\n+\t\t\treturn -1;\n+\t\tsend_object_info_request(data->fd[1], &args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"wrong protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args.object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n+\t\t\tif (!strcmp(reader.line, \"size\")) {\n+\t\t\t\tsize_index = i;\n+\t\t\t\tfor (size_t j = 0; j < args.oids->nr; j++)\n+\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(&reader) == PACKET_READ_NORMAL && i < args.oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader.line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\n+\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\n+\n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n \t\t\t\t\tstruct transport_ls_refs_options *options)\n {\n@@ -415,6 +486,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \tstruct ref *refs = NULL;\n \tstruct fetch_pack_args args;\n \tstruct ref *refs_tmp = NULL;\n+\tstruct ref *object_info_refs = NULL;\n \n \tmemset(&args, 0, sizeof(args));\n \targs.uploadpack = data->options.uploadpack;\n@@ -441,11 +513,36 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options\n+\t\t&& transport->smart_options->object_info\n+\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n+\n+\t\tif (!fetch_object_info(transport, data->options.object_info_data))\n+\t\t\tgoto cleanup;\n+\n+\t\targs.object_info_data = data->options.object_info_data;\n+\t\targs.quiet = 1;\n+\t\targs.no_progress = 1;\n+\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n+\t\t\tref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n+\t\t\tref_itr->exact_oid = 1;\n+\t\t\tif (i == transport->smart_options->object_info_oids->nr - 1)\n+\t\t\t\t/* last element, no need to allocate to next */\n+\t\t\t\tref_itr -> next = NULL;\n+\t\t\telse\n+\t\t\t\tref_itr->next = alloc_ref(\"\");\n \n-\tif (!data->finished_handshake) {\n-\t\tint i;\n+\t\t\tref_itr = ref_itr->next;\n+\t\t}\n+\n+\t\ttransport->remote_refs = object_info_refs;\n+\n+\t} else if (!data->finished_handshake) {\n \t\tint must_list_refs = 0;\n-\t\tfor (i = 0; i < nr_heads; i++) {\n+\t\tfor (int i = 0; i < nr_heads; i++) {\n \t\t\tif (!to_fetch[i]->exact_oid) {\n \t\t\t\tmust_list_refs = 1;\n \t\t\t\tbreak;\n@@ -483,23 +580,32 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t  &transport->pack_lockfiles, data->version);\n \n \tdata->finished_handshake = 0;\n+\tif (args.object_info) {\n+\t\tstruct ref *ref_cpy_reader = object_info_refs;\n+\t\tfor (int i = 0; ref_cpy_reader; i++) {\n+\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid,\n+\t\t\t\t&args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n+\t\t\tref_cpy_reader = ref_cpy_reader->next;\n+\t\t}\n+\t}\n+\n \tdata->options.self_contained_and_connected =\n \t\targs.self_contained_and_connected;\n \tdata->options.connectivity_checked = args.connectivity_checked;\n \n-\tif (!refs)\n+\tif (!refs && !args.object_info)\n \t\tret = -1;\n \tif (report_unmatched_refs(to_fetch, nr_heads))\n \t\tret = -1;\n \n cleanup:\n+\tfree_refs(object_info_refs);\n \tclose(data->fd[0]);\n \tif (data->fd[1] >= 0)\n \t\tclose(data->fd[1]);\n \tif (finish_connect(data->conn))\n \t\tret = -1;\n \tdata->conn = NULL;\n-\n \tfree_refs(refs_tmp);\n \tfree_refs(refs);\n \tlist_objects_filter_release(&args.filter_options);\ndiff --git a/transport.h b/transport.h\nindex 6393cd9823..50ea2b05cf 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to pull only object-info. Fallbacks\n+\t * to pulling entire object if object-info is not supported.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.46.0\n\n"},{"id":"503508","messageId":"20240926013856.35527-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240926013856.35527-1-eric.peijian@gmail.com","subject":"[PATCH v3 5/6] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-26T01:38:55Z","receivedAt":"2024-09-26T01:39:24Z","isPatch":true,"body":"Some code declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n 1 file changed, 3 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex bfdfb51c7c..5db55fabc4 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -673,12 +673,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -686,9 +684,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -714,7 +710,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -724,7 +719,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \n-- \n2.46.0\n\n"},{"id":"503509","messageId":"20240926013856.35527-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240926013856.35527-1-eric.peijian@gmail.com","subject":"[PATCH v3 6/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-09-26T01:38:56Z","receivedAt":"2024-09-26T01:39:26Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSince the `info` command in cat-file --batch-command prints object info\nfor a given object, it is natural to add another command in cat-file\n--batch-command to print object info for a given object from a remote.\nAdd `remote-object-info` to cat-file --batch-command.\n\nWhile `info` takes object ids one at a time, this creates overhead when\nmaking requests to a server so `remote-object-info` instead can take\nmultiple object ids at once.\n\ncat-file --batch-command is generally implemented in the following\nmanner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loop through the object info passed in, print the info.\n\nIn order for remote-object-info to avoid remote communication overhead\nin the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Documentation/git-cat-file.txt         |  22 +-\n builtin/cat-file.c                     | 108 +++-\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/t1017-cat-file-remote-object-info.sh | 750 +++++++++++++++++++++++++\n 5 files changed, 889 insertions(+), 5 deletions(-)\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex d5890ae368..5c81af80c9 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -149,6 +149,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified <remote> without\n+\tdownloading objects from remote. If the object-info capability is not\n+\tsupported by the server, the objects will be downloaded instead.\n+\tError when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +297,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +306,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +323,9 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+When \"%(objecttype)\" is supported, default format should be unified.\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +407,9 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are currently not supported by the\n+`remote-object-info` command, we will error and exit when they are in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 5db55fabc4..714c182f39 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -24,6 +24,9 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -42,9 +45,12 @@ struct batch_options {\n \tchar input_delim;\n \tchar output_delim;\n \tconst char *format;\n+\tint use_remote_info;\n };\n \n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -528,7 +534,7 @@ static void batch_one_object(const char *obj_name,\n \tenum get_oid_result result;\n \n \tresult = get_oid_with_context(the_repository, obj_name,\n-\t\t\t\t      flags, &data->oid, &ctx);\n+\t\t\t\t\t\t\t\t  flags, &data->oid, &ctx);\n \tif (result != FOUND) {\n \t\tswitch (result) {\n \t\tcase MISSING_OBJECT:\n@@ -576,6 +582,59 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT\n+\t*/\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\n+\t\t/* 'objectsize' is the only option currently supported */\n+\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -667,6 +726,52 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t   const char *line,\n+\t\t\t   struct strbuf *output,\n+\t\t\t   struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\n+\tchar *line_to_split = xstrdup_or_null(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\n+\topt->use_remote_info = 1;\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\n+\t\tdata->oid = object_info_oids.oid[i];\n+\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can't retrieve\n+\t\t\t * information from server without downloading them, and the objects\n+\t\t\t * have been fetched to client already.\n+\t\t\t * Print the information using the logic for local objects.\n+\t\t\t */\n+\t\t\tdata->skip_object_info = 0;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\n+\t}\n+\topt->use_remote_info = 0;\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -698,6 +803,7 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1},\n \t{ \"info\", parse_cmd_info, 1},\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1},\n \t{ \"flush\", NULL, 0},\n };\n \ndiff --git a/object-file.c b/object-file.c\nindex 7ac9533ab1..485aa51dc6 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -3020,3 +3020,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex 53b8e693b1..611e2ca708 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -548,4 +548,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(each_packed_object_fn, void *,\n \t\t\t   enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..6826ff7a59\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,750 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+echo_without_newline () {\n+    printf '%s' \"$*\"\n+}\n+\n+echo_without_newline_nul () {\n+    echo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+strlen () {\n+    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback git://: fetch objects to client' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback file://: fetch objects to client' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback http://: fetch objects to client' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n-- \n2.46.0\n\n"},{"id":"505908","messageId":"CAP8UFD0-EPZkdWd8xbg-Yt+aSGfJQE1cFz62Oxwjm-8N1sQ2PQ@mail.gmail.com","threadId":"61695","inReplyTo":"20240926013856.35527-5-eric.peijian@gmail.com","subject":"Re: [PATCH v3 4/6] transport: add client support for object-info","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-10-23T09:48:37Z","receivedAt":"2024-10-23T09:48:52Z","isPatch":true,"body":"On Thu, Sep 26, 2024 at 3:39 AM Eric Ju <eric.peijian@gmail.com> wrote:\n\n> +       if (transport->smart_options\n> +               && transport->smart_options->object_info\n> +               && transport->smart_options->object_info_oids->nr > 0) {\n> +               struct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n> +\n> +               if (!fetch_object_info(transport, data->options.object_info_data))\n> +                       goto cleanup;\n> +\n> +               args.object_info_data = data->options.object_info_data;\n> +               args.quiet = 1;\n> +               args.no_progress = 1;\n> +               for (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> +                       ref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n> +                       ref_itr->exact_oid = 1;\n> +                       if (i == transport->smart_options->object_info_oids->nr - 1)\n> +                               /* last element, no need to allocate to next */\n> +                               ref_itr -> next = NULL;\n\nSpace characters should be removed around \"->\".\n\n> +                       else\n> +                               ref_itr->next = alloc_ref(\"\");\n\nHere for example there are no spaces around \"->\".\n"},{"id":"505909","messageId":"CAP8UFD24jDT1yFNTp-T==-avccd4QjrwRtfJ_-wPx78H31VSiw@mail.gmail.com","threadId":"61695","inReplyTo":"20240926013856.35527-7-eric.peijian@gmail.com","subject":"Re: [PATCH v3 6/6] cat-file: add remote-object-info to batch-command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-10-23T09:49:44Z","receivedAt":"2024-10-23T09:49:57Z","isPatch":true,"body":"On Thu, Sep 26, 2024 at 3:39 AM Eric Ju <eric.peijian@gmail.com> wrote:\n\n> And finally for --buffer mode `remote-object-info`:\n>  - Receive and parse input from user\n>  - Store respective function attached to command in a queue\n>  - After flush, loop through commands in queue:\n>     If command is `remote-object-info`:\n>         - Get object info from remote\n>         - Loop through and print each object info\n>     Else:\n>         - Call respective function attached to command\n>         - Get object info, print object info\n>\n> To summarize, `remote-object-info` gets object info from the remote and\n> then loop through the object info passed in, print the info.\n\nMaybe: s/print the info/printing the info/\n\n> In order for remote-object-info to avoid remote communication overhead\n> in the non-buffer mode, the objects are passed in as such:\n>\n> remote-object-info <remote> <oid> <oid> ... <oid>\n>\n> rather than\n>\n> remote-object-info <remote> <oid>\n> remote-object-info <remote> <oid>\n> ...\n> remote-object-info <remote> <oid>\n\n[...]\n\n>  If no format is specified, the default format is `%(objectname)\n> -%(objecttype) %(objectsize)`.\n> +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> +When \"%(objecttype)\" is supported, default format should be unified.\n\nI think we should warn more clearly and strongly that users should\ntake into account that the default format will change. So they should\nbetter not rely on the current format in their code.\n\nMaybe something like:\n\n`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not\nsupported yet.\nWARNING: When \"%(objecttype)\" is supported, default format WILL be unified, so\nDO NOT RELY on the current default format to stay the same!!!\n\n>  If `--batch` is specified, or if `--batch-command` is used with the `contents`\n>  command, the object information is followed by the object contents (consisting\n\n[...]\n\n> diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\n> new file mode 100755\n> index 0000000000..6826ff7a59\n> --- /dev/null\n> +++ b/t/t1017-cat-file-remote-object-info.sh\n> @@ -0,0 +1,750 @@\n> +#!/bin/sh\n> +\n> +test_description='git cat-file --batch-command with remote-object-info command'\n> +\n> +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> +\n> +. ./test-lib.sh\n> +\n> +echo_without_newline () {\n> +    printf '%s' \"$*\"\n> +}\n> +\n> +echo_without_newline_nul () {\n> +    echo_without_newline \"$@\" | tr '\\n' '\\0'\n> +}\n> +\n> +strlen () {\n> +    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> +}\n\nThe above functions have been copied verbatim from t1006-cat-file.sh.\nI think this is worth a comment or a TODO before these functions\nsaying that common code might want to be unified in the future.\n\nMaybe something like:\n\n# TODO: refactor these functions which were copied from\nt1006-cat-file.sh into a new common file, maybe \"lib-cat-file.sh\"\n\nExcept the above nits and another one I found in patch 4/6, the rest\nof this patch series looks good to me.\n\nThanks!\n"},{"id":"505975","messageId":"ZxlbsizeDUz5yp11@nand.local","threadId":"61695","inReplyTo":"CAP8UFD24jDT1yFNTp-T==-avccd4QjrwRtfJ_-wPx78H31VSiw@mail.gmail.com","subject":"Re: [PATCH v3 6/6] cat-file: add remote-object-info to batch-command","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-10-23T20:25:22Z","receivedAt":"2024-10-23T20:25:24Z","isPatch":true,"body":"On Wed, Oct 23, 2024 at 11:49:44AM +0200, Christian Couder wrote:\n> Except the above nits and another one I found in patch 4/6, the rest\n> of this patch series looks good to me.\n\nThanks for reviewing. Sounds like we are expecting another round here In\nthe meantime, do other reviewers have any feedback on this series?\n\nThanks,\nTaylor\n"},{"id":"506064","messageId":"CAN2LT1D2h-D38F6ELv5Opb1-TdQiFXz88aqA8oxOOfmXw-dTeg@mail.gmail.com","threadId":"61695","inReplyTo":"CAP8UFD0-EPZkdWd8xbg-Yt+aSGfJQE1cFz62Oxwjm-8N1sQ2PQ@mail.gmail.com","subject":"Re: [PATCH v3 4/6] transport: add client support for object-info","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-24T20:23:54Z","receivedAt":"2024-10-24T20:24:06Z","isPatch":true,"body":"On Wed, Oct 23, 2024 at 5:48 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Thu, Sep 26, 2024 at 3:39 AM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> > +       if (transport->smart_options\n> > +               && transport->smart_options->object_info\n> > +               && transport->smart_options->object_info_oids->nr > 0) {\n> > +               struct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n> > +\n> > +               if (!fetch_object_info(transport, data->options.object_info_data))\n> > +                       goto cleanup;\n> > +\n> > +               args.object_info_data = data->options.object_info_data;\n> > +               args.quiet = 1;\n> > +               args.no_progress = 1;\n> > +               for (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> > +                       ref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n> > +                       ref_itr->exact_oid = 1;\n> > +                       if (i == transport->smart_options->object_info_oids->nr - 1)\n> > +                               /* last element, no need to allocate to next */\n> > +                               ref_itr -> next = NULL;\n>\n> Space characters should be removed around \"->\".\n\nThank you. It is fixed in v4.\n\n\n\n>\n> > +                       else\n> > +                               ref_itr->next = alloc_ref(\"\");\n>\n> Here for example there are no spaces around \"->\".\n"},{"id":"506065","messageId":"CAN2LT1Dk7A-y9ffNTjf+UP6VKZZgFOephFSAk8eDUi06UjteYg@mail.gmail.com","threadId":"61695","inReplyTo":"CAP8UFD24jDT1yFNTp-T==-avccd4QjrwRtfJ_-wPx78H31VSiw@mail.gmail.com","subject":"Re: [PATCH v3 6/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-24T20:28:01Z","receivedAt":"2024-10-24T20:28:13Z","isPatch":true,"body":"On Wed, Oct 23, 2024 at 5:49 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Thu, Sep 26, 2024 at 3:39 AM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> > And finally for --buffer mode `remote-object-info`:\n> >  - Receive and parse input from user\n> >  - Store respective function attached to command in a queue\n> >  - After flush, loop through commands in queue:\n> >     If command is `remote-object-info`:\n> >         - Get object info from remote\n> >         - Loop through and print each object info\n> >     Else:\n> >         - Call respective function attached to command\n> >         - Get object info, print object info\n> >\n> > To summarize, `remote-object-info` gets object info from the remote and\n> > then loop through the object info passed in, print the info.\n>\n> Maybe: s/print the info/printing the info/\n\nThank you. Fixed in V4.\n\n>\n> > In order for remote-object-info to avoid remote communication overhead\n> > in the non-buffer mode, the objects are passed in as such:\n> >\n> > remote-object-info <remote> <oid> <oid> ... <oid>\n> >\n> > rather than\n> >\n> > remote-object-info <remote> <oid>\n> > remote-object-info <remote> <oid>\n> > ...\n> > remote-object-info <remote> <oid>\n>\n> [...]\n>\n> >  If no format is specified, the default format is `%(objectname)\n> > -%(objecttype) %(objectsize)`.\n> > +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> > +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> > +When \"%(objecttype)\" is supported, default format should be unified.\n>\n> I think we should warn more clearly and strongly that users should\n> take into account that the default format will change. So they should\n> better not rely on the current format in their code.\n>\n> Maybe something like:\n>\n> `%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not\n> supported yet.\n> WARNING: When \"%(objecttype)\" is supported, default format WILL be unified, so\n> DO NOT RELY on the current default format to stay the same!!!\n>\n\nThank you. The warning is added to v4.\n\n> >  If `--batch` is specified, or if `--batch-command` is used with the `contents`\n> >  command, the object information is followed by the object contents (consisting\n>\n> [...]\n>\n> > diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\n> > new file mode 100755\n> > index 0000000000..6826ff7a59\n> > --- /dev/null\n> > +++ b/t/t1017-cat-file-remote-object-info.sh\n> > @@ -0,0 +1,750 @@\n> > +#!/bin/sh\n> > +\n> > +test_description='git cat-file --batch-command with remote-object-info command'\n> > +\n> > +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> > +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> > +\n> > +. ./test-lib.sh\n> > +\n> > +echo_without_newline () {\n> > +    printf '%s' \"$*\"\n> > +}\n> > +\n> > +echo_without_newline_nul () {\n> > +    echo_without_newline \"$@\" | tr '\\n' '\\0'\n> > +}\n> > +\n> > +strlen () {\n> > +    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> > +}\n>\n> The above functions have been copied verbatim from t1006-cat-file.sh.\n> I think this is worth a comment or a TODO before these functions\n> saying that common code might want to be unified in the future.\n>\n> Maybe something like:\n>\n> # TODO: refactor these functions which were copied from\n> t1006-cat-file.sh into a new common file, maybe \"lib-cat-file.sh\"\n>\n\nThank you. I added \"lib-cat-file.sh\" in v4, and let both tests\nt1006-cat-file.sh\nand t1017-cat-file-remote-object-info.sh refer to it.\n\n\n> Except the above nits and another one I found in patch 4/6, the rest\n> of this patch series looks good to me.\n>\n> Thanks!\n"},{"id":"506066","messageId":"CAN2LT1AJwuocyvww71E8H7s3SU=rXjigLviWgCfUMac7zzH+VQ@mail.gmail.com","threadId":"61695","inReplyTo":"ZxlbsizeDUz5yp11@nand.local","subject":"Re: [PATCH v3 6/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-24T20:28:06Z","receivedAt":"2024-10-24T20:28:19Z","isPatch":true,"body":"On Wed, Oct 23, 2024 at 4:25 PM Taylor Blau <me@ttaylorr.com> wrote:\n>\n> On Wed, Oct 23, 2024 at 11:49:44AM +0200, Christian Couder wrote:\n> > Except the above nits and another one I found in patch 4/6, the rest\n> > of this patch series looks good to me.\n>\n> Thanks for reviewing. Sounds like we are expecting another round here In\n> the meantime, do other reviewers have any feedback on this series?\n>\n> Thanks,\n> Taylor\n\nThank you Tayor, no other reviewers' feedback yet.\n"},{"id":"506067","messageId":"20241024205359.16376-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH v4 0/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-24T20:53:53Z","receivedAt":"2024-10-24T20:54:40Z","isPatch":true,"body":"This is a continuation of Calvin Wan's (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info command at [1].\n\nSometimes it is useful to get information about an object without having to download\nit completely. The server logic for retrieving size has already been implemented and merged in\n\"a2ba162cda (object-info: support for retrieving object info, 2021-04-20)\"[2].\nThis patch series implement the client option for it.\n\nThis patch series add the `remote-object-info` command to `cat-file --batch-command`.\nThis command allows the client to make an object-info command request to a server\nthat supports protocol v2. If the server is v2, but does not have\nobject-info capability, the entire object is fetched and the\nrelevant object info is returned.\n\nA few questions open for discussions please:\n\n1. In the current implementation, if a user puts `remote-object-info` in protocol v1,\n   `cat-file --batch-command` will die. Which way do we prefer? \"error and exit (i.e. die)\"\n   or \"warn and wait for new command\".\n\n2. Right now, only the size is supported. If the batch command format\n   contains objectsize:disk or deltabase, it will die. The question\n   is about objecttype. In the current implementation, it will die too.\n   But dying on objecttype breaks the default format. We have changed the\n   default format to %(objectname) %(objectsize) when remote-object-info is used.\n   Any suggestions on this approach?\n\n[1] https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[2] https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n\nV1 of the patch series can be found here:\nhttps://lore.kernel.org/git/20240628190503.67389-1-eric.peijian@gmail.com/\n\nv2 of the patch series can be found here:\nhttps://lore.kernel.org/git/20240720034337.57125-1-eric.peijian@gmail.com/\n\nChanges since V3\n================\n\n- Fix typos and formatting errors\n\n- Add warning in the git-cat-file doc about the default format\n\n- Add a new test lib file, lib-cat-file.sh. And put the shared code of\nt1017-cat-file-remote-object-info.sh and  t1006-cat-file.sh in it.\n\nThank you.\n\nEric Ju\n\nCalvin Wan (5):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n  cat-file: add remote-object-info to batch-command\n\nEric Ju (1):\n  cat-file: add declaration of variable i inside its for loop\n\n Documentation/git-cat-file.txt         |  24 +-\n builtin/cat-file.c                     | 119 +++-\n fetch-pack.c                           |  49 +-\n fetch-pack.h                           |  10 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 739 +++++++++++++++++++++++++\n transport-helper.c                     |  11 +-\n transport.c                            | 115 +++-\n transport.h                            |  11 +\n 13 files changed, 1081 insertions(+), 44 deletions(-)\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\nRange-diff against v3:\n1:  b570dee186 = 1:  41898fe23e fetch-pack: refactor packet writing\n2:  e8777e8776 = 2:  b3a1bee551 fetch-pack: move fetch initialization\n3:  d00d19cf2c = 3:  d363b0f768 serve: advertise object-info feature\n4:  3e1773910c ! 4:  3118061b21 transport: add client support for object-info\n    @@ transport-helper.c: static int fetch_refs(struct transport *transport,\n      \t */\n      \tif (data->transport_options.acked_commits) {\n      \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n    - \t\treturn -1;\n    +@@ transport-helper.c: static int fetch_refs(struct transport *transport,\n    + \t\tfree_refs(dummy);\n      \t}\n      \n     +\t/* fail the command explicitly to avoid further commands input. */\n     +\tif (transport->smart_options->object_info)\n     +\t\tdie(_(\"remote-object-info requires protocol v2\"));\n     +\n    - \tif (!data->get_refs_list_called)\n    - \t\tget_refs_list_using_list(transport, 0);\n    - \n    ++\tif (!data->get_refs_list_called)\n    ++\t\tget_refs_list_using_list(transport, 0);\n    ++\n    + \tcount = 0;\n    + \tfor (i = 0; i < nr_heads; i++)\n    + \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\n     \n      ## transport.c ##\n     @@ transport.c: static struct ref *handshake(struct transport *transport, int for_push,\n    @@ transport.c: static struct ref *handshake(struct transport *transport, int for_p\n     @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \tstruct ref *refs = NULL;\n      \tstruct fetch_pack_args args;\n    - \tstruct ref *refs_tmp = NULL;\n    + \tstruct ref *refs_tmp = NULL, **to_fetch_dup = NULL;\n     +\tstruct ref *object_info_refs = NULL;\n      \n      \tmemset(&args, 0, sizeof(args));\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n     +\t\t\tref_itr->exact_oid = 1;\n     +\t\t\tif (i == transport->smart_options->object_info_oids->nr - 1)\n     +\t\t\t\t/* last element, no need to allocate to next */\n    -+\t\t\t\tref_itr -> next = NULL;\n    ++\t\t\t\tref_itr->next = NULL;\n     +\t\t\telse\n     +\t\t\t\tref_itr->next = alloc_ref(\"\");\n      \n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \tclose(data->fd[0]);\n      \tif (data->fd[1] >= 0)\n      \t\tclose(data->fd[1]);\n    - \tif (finish_connect(data->conn))\n    - \t\tret = -1;\n    - \tdata->conn = NULL;\n    --\n    - \tfree_refs(refs_tmp);\n    - \tfree_refs(refs);\n    - \tlist_objects_filter_release(&args.filter_options);\n     \n      ## transport.h ##\n     @@\n5:  bb110fbc93 = 5:  2ae81acf2a cat-file: add declaration of variable i inside its for loop\n6:  6dd143c164 ! 6:  b5aa6c1888 cat-file: add remote-object-info to batch-command\n    @@ Commit message\n                 - Get object info, print object info\n     \n         To summarize, `remote-object-info` gets object info from the remote and\n    -    then loop through the object info passed in, print the info.\n    +    then loop through the object info passed in, printing the info.\n     \n         In order for remote-object-info to avoid remote communication overhead\n         in the non-buffer mode, the objects are passed in as such:\n    @@ Documentation/git-cat-file.txt: newline. The available atoms are:\n     -%(objecttype) %(objectsize)`.\n     +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n     +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n    -+When \"%(objecttype)\" is supported, default format should be unified.\n    ++WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n    ++DO NOT RELY on the current the default format to stay the same!!!\n      \n      If `--batch` is specified, or if `--batch-command` is used with the `contents`\n      command, the object information is followed by the object contents (consisting\n    @@ Documentation/git-cat-file.txt: scripting purposes.\n      CAVEATS\n      -------\n      \n    -+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are currently not supported by the\n    -+`remote-object-info` command, we will error and exit when they are in the format string.\n    ++Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n    ++currently not supported by the `remote-object-info` command, we will error\n    ++and exit when they are in the format string.\n     +\n      Note that the sizes of objects on disk are reported accurately, but care\n      should be taken in drawing conclusions about which refs or objects are\n    @@ object-store-ll.h: int for_each_object_in_pack(struct packed_git *p,\n     +\n      #endif /* OBJECT_STORE_LL_H */\n     \n    - ## t/t1017-cat-file-remote-object-info.sh (new) ##\n    + ## t/lib-cat-file.sh (new) ##\n     @@\n    -+#!/bin/sh\n    -+\n    -+test_description='git cat-file --batch-command with remote-object-info command'\n    -+\n    -+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n    -+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n    -+\n    -+. ./test-lib.sh\n    ++# Library of git-cat-file related functions.\n     +\n    ++# Print a string without a trailing newline\n     +echo_without_newline () {\n    -+    printf '%s' \"$*\"\n    ++\tprintf '%s' \"$*\"\n     +}\n     +\n    ++# Print a string without newlines and replaces them with a NULL character (\\0).\n     +echo_without_newline_nul () {\n    -+    echo_without_newline \"$@\" | tr '\\n' '\\0'\n    ++\techo_without_newline \"$@\" | tr '\\n' '\\0'\n     +}\n     +\n    ++# Calculate the length of a string removing any leading spaces.\n     +strlen () {\n    -+    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n    ++\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n     +}\n    +\n    + ## t/t1006-cat-file.sh ##\n    +@@ t/t1006-cat-file.sh: test_description='git cat-file'\n    + \n    + TEST_PASSES_SANITIZE_LEAK=true\n    + . ./test-lib.sh\n    ++. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n    + \n    + test_cmdmode_usage () {\n    + \ttest_expect_code 129 \"$@\" 2>err &&\n    +@@ t/t1006-cat-file.sh: do\n    + \t'\n    + done\n    + \n    +-echo_without_newline () {\n    +-    printf '%s' \"$*\"\n    +-}\n    +-\n    +-echo_without_newline_nul () {\n    +-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n    +-}\n    +-\n    +-strlen () {\n    +-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n    +-}\n    +-\n    + run_tests () {\n    +     type=$1\n    +     oid=$2\n    +\n    + ## t/t1017-cat-file-remote-object-info.sh (new) ##\n    +@@\n    ++#!/bin/sh\n    ++\n    ++test_description='git cat-file --batch-command with remote-object-info command'\n    ++\n    ++GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n    ++export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n    ++\n    ++. ./test-lib.sh\n    ++. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n     +\n     +hello_content=\"Hello World\"\n     +hello_size=$(strlen \"$hello_content\")\n-- \n2.47.0\n\n"},{"id":"506068","messageId":"20241024205359.16376-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241024205359.16376-1-eric.peijian@gmail.com","subject":"[PATCH v4 1/6] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-24T20:53:54Z","receivedAt":"2024-10-24T20:54:41Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nRefactor write_fetch_command_and_capabilities() to be a more general\npurpose function write_command_and_capabilities(), so that it can be\nused by both fetch and future command.\n\nHere \"command\" means the \"operations\" supported by Git’s wire protocol\nhttps://git-scm.com/docs/protocol-v2. An example would be a\ngit's subcommand, such as git-fetch(1); or an operation supported by\nthe server side such as \"object-info\" implemented in \"a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20)\".\n\nIn a future separate series, we can move\nwrite_command_and_capabilities() to a higher-level file, such as\nconnect.c, so that it becomes accessible to other commands.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 35 +++++++++++++++++++++++++++++------\n 1 file changed, 29 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f752da93a8..756fb83f89 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1314,13 +1314,14 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n+static void write_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t\t\t\t\t\t   const char *command,\n+\t\t\t\t\t\t\t\t\t\t   const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n@@ -1346,6 +1347,28 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tpacket_buf_delim(req_buf);\n }\n \n+\n+void send_object_info_request(int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids) {\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\t}\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1356,7 +1379,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2174,7 +2197,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.47.0\n\n"},{"id":"506069","messageId":"20241024205359.16376-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241024205359.16376-1-eric.peijian@gmail.com","subject":"[PATCH v4 2/6] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-24T20:53:55Z","receivedAt":"2024-10-24T20:54:42Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 756fb83f89..800505f25f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1700,18 +1700,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.47.0\n\n"},{"id":"506070","messageId":"20241024205359.16376-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241024205359.16376-1-eric.peijian@gmail.com","subject":"[PATCH v4 3/6] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-24T20:53:56Z","receivedAt":"2024-10-24T20:54:43Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex d674764a25..c3d8098642 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -70,7 +70,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -78,6 +78,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.47.0\n\n"},{"id":"506071","messageId":"20241024205359.16376-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241024205359.16376-1-eric.peijian@gmail.com","subject":"[PATCH v4 4/6] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-24T20:53:57Z","receivedAt":"2024-10-24T20:54:44Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes it is useful to get information about an object without having\nto download it completely. The server logic has already been implemented\nin “a2ba162cda (object-info: support for retrieving object info,\n2021-04-20)”.\n\nAdd client functions to communicate with the server.\n\nThe client currently supports requesting a list of object ids with\nfeature 'size' from a v2 server. If a server does not\nadvertise the feature, then the client falls back\nto making the request through 'fetch'.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c       |   4 +-\n fetch-pack.h       |  10 ++++\n transport-helper.c |  11 ++++-\n transport.c        | 115 +++++++++++++++++++++++++++++++++++++++++++--\n transport.h        |  11 +++++\n 5 files changed, 144 insertions(+), 7 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 800505f25f..1a9facc1c0 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1347,7 +1347,6 @@ static void write_command_and_capabilities(struct strbuf *req_buf,\n \tpacket_buf_delim(req_buf);\n }\n \n-\n void send_object_info_request(int fd_out, struct object_info_args *args)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n@@ -1706,6 +1705,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex b5c579cdae..5a5211e355 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\n@@ -68,6 +70,12 @@ struct fetch_pack_args {\n \tunsigned connectivity_checked:1;\n };\n \n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n /*\n  * sought represents remote references that should be updated from.\n  * On return, the names that were found on the remote will have been\n@@ -106,4 +114,6 @@ int report_unmatched_refs(struct ref **sought, int nr_sought);\n  */\n int fetch_pack_fsck_objects(void);\n \n+void send_object_info_request(int fd_out, struct object_info_args *args);\n+\n #endif\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 013ec79dc9..2ff9675984 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -709,8 +709,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n+\t * require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -726,6 +726,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 47fda6a773..64b49c083b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -371,6 +371,77 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \treturn refs;\n }\n \n+static int fetch_object_info(struct transport *transport, struct object_info *object_info_data)\n+{\n+\tint size_index = -1;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct object_info_args args = { 0 };\n+\tstruct packet_reader reader;\n+\n+\targs.server_options = transport->server_options;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\tdata->version = discover_version(&reader);\n+\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tswitch (data->version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\treturn -1;\n+\t\tif (unsorted_string_list_has_string(args.object_info_options, \"size\")\n+\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0))\n+\t\t\treturn -1;\n+\t\tsend_object_info_request(data->fd[1], &args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"wrong protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args.object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n+\t\t\tif (!strcmp(reader.line, \"size\")) {\n+\t\t\t\tsize_index = i;\n+\t\t\t\tfor (size_t j = 0; j < args.oids->nr; j++)\n+\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(&reader) == PACKET_READ_NORMAL && i < args.oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader.line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\n+\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\n+\n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n \t\t\t\t\tstruct transport_ls_refs_options *options)\n {\n@@ -418,6 +489,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \tstruct ref *refs = NULL;\n \tstruct fetch_pack_args args;\n \tstruct ref *refs_tmp = NULL, **to_fetch_dup = NULL;\n+\tstruct ref *object_info_refs = NULL;\n \n \tmemset(&args, 0, sizeof(args));\n \targs.uploadpack = data->options.uploadpack;\n@@ -444,11 +516,36 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options\n+\t\t&& transport->smart_options->object_info\n+\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n+\n+\t\tif (!fetch_object_info(transport, data->options.object_info_data))\n+\t\t\tgoto cleanup;\n+\n+\t\targs.object_info_data = data->options.object_info_data;\n+\t\targs.quiet = 1;\n+\t\targs.no_progress = 1;\n+\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n+\t\t\tref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n+\t\t\tref_itr->exact_oid = 1;\n+\t\t\tif (i == transport->smart_options->object_info_oids->nr - 1)\n+\t\t\t\t/* last element, no need to allocate to next */\n+\t\t\t\tref_itr->next = NULL;\n+\t\t\telse\n+\t\t\t\tref_itr->next = alloc_ref(\"\");\n \n-\tif (!data->finished_handshake) {\n-\t\tint i;\n+\t\t\tref_itr = ref_itr->next;\n+\t\t}\n+\n+\t\ttransport->remote_refs = object_info_refs;\n+\n+\t} else if (!data->finished_handshake) {\n \t\tint must_list_refs = 0;\n-\t\tfor (i = 0; i < nr_heads; i++) {\n+\t\tfor (int i = 0; i < nr_heads; i++) {\n \t\t\tif (!to_fetch[i]->exact_oid) {\n \t\t\t\tmust_list_refs = 1;\n \t\t\t\tbreak;\n@@ -494,16 +591,26 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t  &transport->pack_lockfiles, data->version);\n \n \tdata->finished_handshake = 0;\n+\tif (args.object_info) {\n+\t\tstruct ref *ref_cpy_reader = object_info_refs;\n+\t\tfor (int i = 0; ref_cpy_reader; i++) {\n+\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid,\n+\t\t\t\t&args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n+\t\t\tref_cpy_reader = ref_cpy_reader->next;\n+\t\t}\n+\t}\n+\n \tdata->options.self_contained_and_connected =\n \t\targs.self_contained_and_connected;\n \tdata->options.connectivity_checked = args.connectivity_checked;\n \n-\tif (!refs)\n+\tif (!refs && !args.object_info)\n \t\tret = -1;\n \tif (report_unmatched_refs(to_fetch, nr_heads))\n \t\tret = -1;\n \n cleanup:\n+\tfree_refs(object_info_refs);\n \tclose(data->fd[0]);\n \tif (data->fd[1] >= 0)\n \t\tclose(data->fd[1]);\ndiff --git a/transport.h b/transport.h\nindex 44100fa9b7..42b8ee1251 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to pull only object-info. Fallbacks\n+\t * to pulling entire object if object-info is not supported.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.47.0\n\n"},{"id":"506072","messageId":"20241024205359.16376-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241024205359.16376-1-eric.peijian@gmail.com","subject":"[PATCH v4 5/6] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-24T20:53:58Z","receivedAt":"2024-10-24T20:54:44Z","isPatch":true,"body":"Some code declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n 1 file changed, 3 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex bfdfb51c7c..5db55fabc4 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -673,12 +673,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -686,9 +684,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -714,7 +710,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -724,7 +719,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \n-- \n2.47.0\n\n"},{"id":"506073","messageId":"20241024205359.16376-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241024205359.16376-1-eric.peijian@gmail.com","subject":"[PATCH v4 6/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-24T20:53:59Z","receivedAt":"2024-10-24T20:54:46Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSince the `info` command in cat-file --batch-command prints object info\nfor a given object, it is natural to add another command in cat-file\n--batch-command to print object info for a given object from a remote.\nAdd `remote-object-info` to cat-file --batch-command.\n\nWhile `info` takes object ids one at a time, this creates overhead when\nmaking requests to a server so `remote-object-info` instead can take\nmultiple object ids at once.\n\ncat-file --batch-command is generally implemented in the following\nmanner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loop through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication overhead\nin the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Documentation/git-cat-file.txt         |  24 +-\n builtin/cat-file.c                     | 108 +++-\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 739 +++++++++++++++++++++++++\n 7 files changed, 897 insertions(+), 17 deletions(-)\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex d5890ae368..f2be00b599 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -149,6 +149,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified <remote> without\n+\tdownloading objects from remote. If the object-info capability is not\n+\tsupported by the server, the objects will be downloaded instead.\n+\tError when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +297,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +306,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +323,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current the default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +408,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will error\n+and exit when they are in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 5db55fabc4..714c182f39 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -24,6 +24,9 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -42,9 +45,12 @@ struct batch_options {\n \tchar input_delim;\n \tchar output_delim;\n \tconst char *format;\n+\tint use_remote_info;\n };\n \n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -528,7 +534,7 @@ static void batch_one_object(const char *obj_name,\n \tenum get_oid_result result;\n \n \tresult = get_oid_with_context(the_repository, obj_name,\n-\t\t\t\t      flags, &data->oid, &ctx);\n+\t\t\t\t\t\t\t\t  flags, &data->oid, &ctx);\n \tif (result != FOUND) {\n \t\tswitch (result) {\n \t\tcase MISSING_OBJECT:\n@@ -576,6 +582,59 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT\n+\t*/\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\n+\t\t/* 'objectsize' is the only option currently supported */\n+\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -667,6 +726,52 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t   const char *line,\n+\t\t\t   struct strbuf *output,\n+\t\t\t   struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\n+\tchar *line_to_split = xstrdup_or_null(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\n+\topt->use_remote_info = 1;\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\n+\t\tdata->oid = object_info_oids.oid[i];\n+\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can't retrieve\n+\t\t\t * information from server without downloading them, and the objects\n+\t\t\t * have been fetched to client already.\n+\t\t\t * Print the information using the logic for local objects.\n+\t\t\t */\n+\t\t\tdata->skip_object_info = 0;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\n+\t}\n+\topt->use_remote_info = 0;\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -698,6 +803,7 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1},\n \t{ \"info\", parse_cmd_info, 1},\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1},\n \t{ \"flush\", NULL, 0},\n };\n \ndiff --git a/object-file.c b/object-file.c\nindex b1a3463852..181cde98e1 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -3132,3 +3132,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex 53b8e693b1..611e2ca708 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -548,4 +548,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(each_packed_object_fn, void *,\n \t\t\t   enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..929d32da76\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related functions.\n+\n+# Print a string without a trailing newline\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replaces them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string removing any leading spaces.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex d36cd7c086..d8a851c427 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     oid=$2\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..f4bff07311\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,739 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback git://: fetch objects to client' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback file://: fetch objects to client' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback http://: fetch objects to client' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n-- \n2.47.0\n\n"},{"id":"506097","messageId":"CAOLa=ZSb6p_z8jJHSWCfqD6bdTNNXQw-Dq6DsQWt+o+cyQ3x9Q@mail.gmail.com","threadId":"61695","inReplyTo":"20241024205359.16376-2-eric.peijian@gmail.com","subject":"Re: [PATCH v4 1/6] fetch-pack: refactor packet writing","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-10-25T09:52:12Z","receivedAt":"2024-10-25T09:52:14Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n[snip]\n\n> +\n> +void send_object_info_request(int fd_out, struct object_info_args *args)\n> +{\n> +\tstruct strbuf req_buf = STRBUF_INIT;\n> +\n> +\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n> +\n> +\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n> +\t\tpacket_buf_write(&req_buf, \"size\");\n> +\n> +\tif (args->oids) {\n> +\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n> +\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n> +\t}\n> +\n> +\tpacket_buf_flush(&req_buf);\n> +\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n> +\t\tdie_errno(_(\"unable to write request to remote\"));\n> +\n> +\tstrbuf_release(&req_buf);\n> +}\n> +\n\nWas this function meant to be added here? I mean, there is no reference\nto it in the commit message or anywhere else.\n\n[snip]\n"},{"id":"506099","messageId":"CAOLa=ZS4XLNBeXewQ8O7tpobQfF9C9LKP--MVYghVR52hcqgWA@mail.gmail.com","threadId":"61695","inReplyTo":"20241024205359.16376-5-eric.peijian@gmail.com","subject":"Re: [PATCH v4 4/6] transport: add client support for object-info","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-10-25T10:12:59Z","receivedAt":"2024-10-25T10:13:03Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n[snip]\n\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index 800505f25f..1a9facc1c0 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1347,7 +1347,6 @@ static void write_command_and_capabilities(struct strbuf *req_buf,\n>  \tpacket_buf_delim(req_buf);\n>  }\n>\n> -\n\nSeems like this was introduced in Patch 1/6, including the function\nbelow which is not used in that patch.\n\n>  void send_object_info_request(int fd_out, struct object_info_args *args)\n>  {\n>  \tstruct strbuf req_buf = STRBUF_INIT;\n> @@ -1706,6 +1705,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n>  \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n>  \t\targs->deepen = 1;\n>\n> +\tif (args->object_info)\n> +\t\tstate = FETCH_SEND_REQUEST;\n> +\n>  \twhile (state != FETCH_DONE) {\n>  \t\tswitch (state) {\n>  \t\tcase FETCH_CHECK_LOCAL:\n\n[snip]\n\n>  /*\n>   * sought represents remote references that should be updated from.\n>   * On return, the names that were found on the remote will have been\n> @@ -106,4 +114,6 @@ int report_unmatched_refs(struct ref **sought, int nr_sought);\n>   */\n>  int fetch_pack_fsck_objects(void);\n>\n> +void send_object_info_request(int fd_out, struct object_info_args *args);\n> +\n>\n\nNit: Would be nice to have a comment here explaining what the function does.\n\n>  #endif\n> diff --git a/transport-helper.c b/transport-helper.c\n> index 013ec79dc9..2ff9675984 100644\n> --- a/transport-helper.c\n> +++ b/transport-helper.c\n> @@ -709,8 +709,8 @@ static int fetch_refs(struct transport *transport,\n>\n>  \t/*\n>  \t * If we reach here, then the server, the client, and/or the transport\n> -\t * helper does not support protocol v2. --negotiate-only requires\n> -\t * protocol v2.\n> +\t * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n\nNit: could we wrap this comment?\n\n> +\t * require protocol v2.\n>  \t */\n>  \tif (data->transport_options.acked_commits) {\n>  \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n\n[snip]\n\n>  static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n>  \t\t\t\t\tstruct transport_ls_refs_options *options)\n>  {\n> @@ -418,6 +489,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \tstruct ref *refs = NULL;\n>  \tstruct fetch_pack_args args;\n>  \tstruct ref *refs_tmp = NULL, **to_fetch_dup = NULL;\n> +\tstruct ref *object_info_refs = NULL;\n>\n>  \tmemset(&args, 0, sizeof(args));\n>  \targs.uploadpack = data->options.uploadpack;\n> @@ -444,11 +516,36 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \targs.server_options = transport->server_options;\n>  \targs.negotiation_tips = data->options.negotiation_tips;\n>  \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n> +\targs.object_info = transport->smart_options->object_info;\n> +\n> +\tif (transport->smart_options\n> +\t\t&& transport->smart_options->object_info\n> +\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n> +\t\tstruct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n> +\n> +\t\tif (!fetch_object_info(transport, data->options.object_info_data))\n> +\t\t\tgoto cleanup;\n\nSo if we were successful, we skip to the cleanup. Okay.\n\n> +\t\targs.object_info_data = data->options.object_info_data;\n> +\t\targs.quiet = 1;\n> +\t\targs.no_progress = 1;\n\nNot sure why we set quiet and no_progress here.\n\n> +\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> +\t\t\tref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n> +\t\t\tref_itr->exact_oid = 1;\n> +\t\t\tif (i == transport->smart_options->object_info_oids->nr - 1)\n> +\t\t\t\t/* last element, no need to allocate to next */\n> +\t\t\t\tref_itr->next = NULL;\n> +\t\t\telse\n> +\t\t\t\tref_itr->next = alloc_ref(\"\");\n>\n> -\tif (!data->finished_handshake) {\n> -\t\tint i;\n> +\t\t\tref_itr = ref_itr->next;\n> +\t\t}\n> +\n> +\t\ttransport->remote_refs = object_info_refs;\n> +\n> +\t} else if (!data->finished_handshake) {\n>  \t\tint must_list_refs = 0;\n> -\t\tfor (i = 0; i < nr_heads; i++) {\n> +\t\tfor (int i = 0; i < nr_heads; i++) {\n>  \t\t\tif (!to_fetch[i]->exact_oid) {\n>  \t\t\t\tmust_list_refs = 1;\n>  \t\t\t\tbreak;\n> @@ -494,16 +591,26 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \t\t\t  &transport->pack_lockfiles, data->version);\n>\n>  \tdata->finished_handshake = 0;\n> +\tif (args.object_info) {\n> +\t\tstruct ref *ref_cpy_reader = object_info_refs;\n> +\t\tfor (int i = 0; ref_cpy_reader; i++) {\n> +\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid,\n> +\t\t\t\t&args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n> +\t\t\tref_cpy_reader = ref_cpy_reader->next;\n> +\t\t}\n> +\t}\n> +\n>  \tdata->options.self_contained_and_connected =\n>  \t\targs.self_contained_and_connected;\n>  \tdata->options.connectivity_checked = args.connectivity_checked;\n>\n> -\tif (!refs)\n> +\tif (!refs && !args.object_info)\n>  \t\tret = -1;\n\nThis is because, now we don't necessary always fetch the refs, since\nsometimes we're just happy fetching the object info. Would be nice to\nhave a comment here.\n\n>  \tif (report_unmatched_refs(to_fetch, nr_heads))\n>  \t\tret = -1;\n>\n>  cleanup:\n> +\tfree_refs(object_info_refs);\n>  \tclose(data->fd[0]);\n>  \tif (data->fd[1] >= 0)\n>  \t\tclose(data->fd[1]);\n\n\n[snip]\n"},{"id":"506100","messageId":"CAOLa=ZSVxOoS0iQ0Ku1LF2jwSw2uK8DMjFJYL77Bto98_sKMUg@mail.gmail.com","threadId":"61695","inReplyTo":"20241024205359.16376-7-eric.peijian@gmail.com","subject":"Re: [PATCH v4 6/6] cat-file: add remote-object-info to batch-command","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-10-25T10:53:20Z","receivedAt":"2024-10-25T10:53:23Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n[snip]\n\n> @@ -314,7 +323,10 @@ newline. The available atoms are:\n>  \tline) are output in place of the `%(rest)` atom.\n>\n>  If no format is specified, the default format is `%(objectname)\n> -%(objecttype) %(objectsize)`.\n> +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> +WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n> +DO NOT RELY on the current the default format to stay the same!!!\n>\n\nThis seems like a planned breakage, wouldn't it make more sense to\nimplement %(objecttype) first?\n\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 5db55fabc4..714c182f39 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -24,6 +24,9 @@\n>  #include \"promisor-remote.h\"\n>  #include \"mailmap.h\"\n>  #include \"write-or-die.h\"\n> +#include \"alias.h\"\n> +#include \"remote.h\"\n> +#include \"transport.h\"\n>\n>  enum batch_mode {\n>  \tBATCH_MODE_CONTENTS,\n> @@ -42,9 +45,12 @@ struct batch_options {\n>  \tchar input_delim;\n>  \tchar output_delim;\n>  \tconst char *format;\n> +\tint use_remote_info;\n>  };\n>\n>  static const char *force_path;\n> +static struct object_info *remote_object_info;\n> +static struct oid_array object_info_oids = OID_ARRAY_INIT;\n>\n>  static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n>  static int use_mailmap;\n> @@ -528,7 +534,7 @@ static void batch_one_object(const char *obj_name,\n>  \tenum get_oid_result result;\n>\n>  \tresult = get_oid_with_context(the_repository, obj_name,\n> -\t\t\t\t      flags, &data->oid, &ctx);\n> +\t\t\t\t\t\t\t\t  flags, &data->oid, &ctx);\n\nNit: we usually don't fix parts of code, which we're not explicitly\nmodifying.\n\n[snip]\n"},{"id":"506103","messageId":"CAP8UFD3cAyVZN21_2BGWCfqKhEMhnhqEiU-TL3rLvDOXzp2Hxw@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZSVxOoS0iQ0Ku1LF2jwSw2uK8DMjFJYL77Bto98_sKMUg@mail.gmail.com","subject":"Re: [PATCH v4 6/6] cat-file: add remote-object-info to batch-command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-10-25T13:55:32Z","receivedAt":"2024-10-25T13:55:45Z","isPatch":true,"body":"Hi Karthik,\n\nOn Fri, Oct 25, 2024 at 12:53 PM karthik nayak <karthik.188@gmail.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> [snip]\n>\n> > @@ -314,7 +323,10 @@ newline. The available atoms are:\n> >       line) are output in place of the `%(rest)` atom.\n> >\n> >  If no format is specified, the default format is `%(objectname)\n> > -%(objecttype) %(objectsize)`.\n> > +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> > +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> > +WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n> > +DO NOT RELY on the current the default format to stay the same!!!\n>\n> This seems like a planned breakage, wouldn't it make more sense to\n> implement %(objecttype) first?\n\nI don't think it's fair to say it's a planned breakage. For example if\na default specifies what is displayed on the command line and if\nwhat's displayed has an informational purpose there, then changing the\ndefault to add more information is not really a breakage. Here we make\nit clear that a possible breakage (in case the feature wasn't used for\ninformational purposes only for example) could easily be avoided by\nnot relying on the default, but instead specifying exactly the desired\noutput format.\n\nAnd yeah ideally both %(objecttype) and %(objectsize) should be\nimplemented first, but on the other hand sending short patch series\nand growing some features step by step is a nicer approach in some\nways than sending big patch series that are hard to review. So I think\nit's fair here to start with just %(objectsize) and leave\n%(objecttype) for a future patch series.\n\nExcept for this I agree with the comments in your review of the v4.\n\nThanks!\n"},{"id":"506105","messageId":"CAN2LT1CEPdTAxCEpKtd+8-5zKYSnh0PMqEXgAZ++TTMPPKrD1g@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZSb6p_z8jJHSWCfqD6bdTNNXQw-Dq6DsQWt+o+cyQ3x9Q@mail.gmail.com","subject":"Re: [PATCH v4 1/6] fetch-pack: refactor packet writing","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-25T16:06:42Z","receivedAt":"2024-10-25T16:06:54Z","isPatch":true,"body":"On Fri, Oct 25, 2024 at 5:52 AM karthik nayak <karthik.188@gmail.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> [snip]\n>\n> > +\n> > +void send_object_info_request(int fd_out, struct object_info_args *args)\n> > +{\n> > +     struct strbuf req_buf = STRBUF_INIT;\n> > +\n> > +     write_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n> > +\n> > +     if (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n> > +             packet_buf_write(&req_buf, \"size\");\n> > +\n> > +     if (args->oids) {\n> > +             for (size_t i = 0; i < args->oids->nr; i++)\n> > +                     packet_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n> > +     }\n> > +\n> > +     packet_buf_flush(&req_buf);\n> > +     if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n> > +             die_errno(_(\"unable to write request to remote\"));\n> > +\n> > +     strbuf_release(&req_buf);\n> > +}\n> > +\n>\n> Was this function meant to be added here? I mean, there is no reference\n> to it in the commit message or anywhere else.\n>\n\nThank you.\n\nThe `send_object_info_request` function is used in `transport.c`\n`fetch_object_info()` in patch 4/6. Its functionality is similar to\n`send_fetch_request()`:  sending the object-info command along with\nsub-command (e.g. size) and arguments (e.g. oids) to the remote.\n\nI guess Clavin put it here because\n1. it has similar functionality as `send_fetch_request()`\n2. `write_command_and_capabilities()` is only visible within `fecth-pack.c`.\n\nHowever, I believe your comment is valid. Adding everything to\n`fetch-pack.c` makes the file overly bloated with functionality\nunrelated to fetch-pack. For v5, I plan to address this by:\n\nI will:\n1. move `write_command_and_capabilities()` a level up to connect.c\n2. add a new file f`ecth-object-info.c` at the same level of\n`fetch-pack.c`. This new file contains the logic related to\nobject-info command, i.e. `send_object_info_request()` and\n`fetch_object_info()`\n3. move `fetch_object_info()` away from `transport.c`\n\nThe dependency WAS like this, as I pointed out in a previous reply at\nhttps://lore.kernel.org/git/CAN2LT1AM5rYpwjZ+rhYerxDkL6mbxr7iDc=wvuhvNKS8VVXQ8w@mail.gmail.com/#t\n\n`transport.c`  -> `fetch-pack.c` -> `connect.c`,   where \"->\" means\n\"depends on\".\n\nIn v5, it would be like this:\n\n`transport.c`  ->       `fetch-pack.c`         -> `connect.c`\n                      |                                      /\n                       -> fecth-object-info.c  ->\n\nLet me know if that makes sense.\n\n> [snip]\n"},{"id":"506112","messageId":"ZxwGAhWYm0tASMI3@nand.local","threadId":"61695","inReplyTo":"20241024205359.16376-1-eric.peijian@gmail.com","subject":"Re: [PATCH v4 0/6] cat-file: add remote-object-info to batch-command","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-10-25T20:56:34Z","receivedAt":"2024-10-25T20:56:36Z","isPatch":true,"body":"On Thu, Oct 24, 2024 at 04:53:53PM -0400, Eric Ju wrote:\n> Calvin Wan (5):\n>   fetch-pack: refactor packet writing\n>   fetch-pack: move fetch initialization\n>   serve: advertise object-info feature\n>   transport: add client support for object-info\n>   cat-file: add remote-object-info to batch-command\n>\n> Eric Ju (1):\n>   cat-file: add declaration of variable i inside its for loop\n\nThanks. I just want to make sure that I have the right base here... this\nwas previously based on 3857aae53f (Git 2.47-rc0, 2024-09-25), but\napplying the new round did not cleanly apply on top of that commit as\nits merge base.\n\nI applied the new round on top of the current tip of 'master', which is\n6a11438f43 (The fifth batch, 2024-10-25) at the time of writing.\n\nPlease let me know if that was the right choice to make ;-).\n\nThanks,\nTaylor\n"},{"id":"506143","messageId":"CAN2LT1CTi+7GWsDOYKDF+PEg6H3BFTfCVSwPvK5u5KnHi_AbZA@mail.gmail.com","threadId":"61695","inReplyTo":"ZxwGAhWYm0tASMI3@nand.local","subject":"Re: [PATCH v4 0/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-27T03:54:03Z","receivedAt":"2024-10-27T03:54:15Z","isPatch":true,"body":"On Fri, Oct 25, 2024 at 4:56 PM Taylor Blau <me@ttaylorr.com> wrote:\n>\n> On Thu, Oct 24, 2024 at 04:53:53PM -0400, Eric Ju wrote:\n> > Calvin Wan (5):\n> >   fetch-pack: refactor packet writing\n> >   fetch-pack: move fetch initialization\n> >   serve: advertise object-info feature\n> >   transport: add client support for object-info\n> >   cat-file: add remote-object-info to batch-command\n> >\n> > Eric Ju (1):\n> >   cat-file: add declaration of variable i inside its for loop\n>\n> Thanks. I just want to make sure that I have the right base here... this\n> was previously based on 3857aae53f (Git 2.47-rc0, 2024-09-25), but\n> applying the new round did not cleanly apply on top of that commit as\n> its merge base.\n>\n> I applied the new round on top of the current tip of 'master', which is\n> 6a11438f43 (The fifth batch, 2024-10-25) at the time of writing.\n>\n> Please let me know if that was the right choice to make ;-).\n>\n> Thanks,\n> Taylor\n\nHi Taylor,\n\nI probably rebase on the wrong master tip. I am working on a new v5 now.\nWould you like to resend v4 or can we skip v4 and use v5?\n\nThank you.\n\nEric\n"},{"id":"506179","messageId":"Zx7URU2twSW1G6VN@nand.local","threadId":"61695","inReplyTo":"CAN2LT1CTi+7GWsDOYKDF+PEg6H3BFTfCVSwPvK5u5KnHi_AbZA@mail.gmail.com","subject":"Re: [PATCH v4 0/6] cat-file: add remote-object-info to batch-command","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-10-28T00:01:09Z","receivedAt":"2024-10-28T00:01:12Z","isPatch":true,"body":"On Sat, Oct 26, 2024 at 11:54:03PM -0400, Peijian Ju wrote:\n> I probably rebase on the wrong master tip. I am working on a new v5 now.\n> Would you like to resend v4 or can we skip v4 and use v5?\n\nLet's skip resending this round since I found a suitable base for it.\nOnce you send v5, I can apply it onto any base that you specify as\nappropriate.\n\nThanks,\nTaylor\n"},{"id":"506194","messageId":"CAN2LT1C1dh4oSO-3q71TX-BCy-JMoDXNXBSHA3P66_EjQ+0EKg@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZS4XLNBeXewQ8O7tpobQfF9C9LKP--MVYghVR52hcqgWA@mail.gmail.com","subject":"Re: [PATCH v4 4/6] transport: add client support for object-info","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-28T05:39:35Z","receivedAt":"2024-10-28T05:39:46Z","isPatch":true,"body":"On Fri, Oct 25, 2024 at 6:13 AM karthik nayak <karthik.188@gmail.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> [snip]\n>\n> > diff --git a/fetch-pack.c b/fetch-pack.c\n> > index 800505f25f..1a9facc1c0 100644\n> > --- a/fetch-pack.c\n> > +++ b/fetch-pack.c\n> > @@ -1347,7 +1347,6 @@ static void write_command_and_capabilities(struct strbuf *req_buf,\n> >       packet_buf_delim(req_buf);\n> >  }\n> >\n> > -\n>\n> Seems like this was introduced in Patch 1/6, including the function\n> below which is not used in that patch.\n>\n\nThank you. As explained at\nhttps://lore.kernel.org/git/CAN2LT1CEPdTAxCEpKtd+8-5zKYSnh0PMqEXgAZ++TTMPPKrD1g@mail.gmail.com/.\n\nIn Patch 1/6, I am moving `write_command_and_capabilities()` to connect.c.\nAnd I am moving `send_object_info_request()` to a new file\nfetch-object-info.c in patch 4/6 where it is used.\n\n\n> >  void send_object_info_request(int fd_out, struct object_info_args *args)\n> >  {\n> >       struct strbuf req_buf = STRBUF_INIT;\n> > @@ -1706,6 +1705,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n> >       if (args->depth > 0 || args->deepen_since || args->deepen_not)\n> >               args->deepen = 1;\n> >\n> > +     if (args->object_info)\n> > +             state = FETCH_SEND_REQUEST;\n> > +\n> >       while (state != FETCH_DONE) {\n> >               switch (state) {\n> >               case FETCH_CHECK_LOCAL:\n>\n> [snip]\n>\n> >  /*\n> >   * sought represents remote references that should be updated from.\n> >   * On return, the names that were found on the remote will have been\n> > @@ -106,4 +114,6 @@ int report_unmatched_refs(struct ref **sought, int nr_sought);\n> >   */\n> >  int fetch_pack_fsck_objects(void);\n> >\n> > +void send_object_info_request(int fd_out, struct object_info_args *args);\n> > +\n> >\n>\n> Nit: Would be nice to have a comment here explaining what the function does.\n>\n\nThank you. Added in v5.\n\n> >  #endif\n> > diff --git a/transport-helper.c b/transport-helper.c\n> > index 013ec79dc9..2ff9675984 100644\n> > --- a/transport-helper.c\n> > +++ b/transport-helper.c\n> > @@ -709,8 +709,8 @@ static int fetch_refs(struct transport *transport,\n> >\n> >       /*\n> >        * If we reach here, then the server, the client, and/or the transport\n> > -      * helper does not support protocol v2. --negotiate-only requires\n> > -      * protocol v2.\n> > +      * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n>\n> Nit: could we wrap this comment?\n>\n\nThank you, Fixed in v5.\n\n> > +      * require protocol v2.\n> >        */\n> >       if (data->transport_options.acked_commits) {\n> >               warning(_(\"--negotiate-only requires protocol v2\"));\n>\n> [snip]\n>\n> >  static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n> >                                       struct transport_ls_refs_options *options)\n> >  {\n> > @@ -418,6 +489,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >       struct ref *refs = NULL;\n> >       struct fetch_pack_args args;\n> >       struct ref *refs_tmp = NULL, **to_fetch_dup = NULL;\n> > +     struct ref *object_info_refs = NULL;\n> >\n> >       memset(&args, 0, sizeof(args));\n> >       args.uploadpack = data->options.uploadpack;\n> > @@ -444,11 +516,36 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >       args.server_options = transport->server_options;\n> >       args.negotiation_tips = data->options.negotiation_tips;\n> >       args.reject_shallow_remote = transport->smart_options->reject_shallow;\n> > +     args.object_info = transport->smart_options->object_info;\n> > +\n> > +     if (transport->smart_options\n> > +             && transport->smart_options->object_info\n> > +             && transport->smart_options->object_info_oids->nr > 0) {\n> > +             struct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n> > +\n> > +             if (!fetch_object_info(transport, data->options.object_info_data))\n> > +                     goto cleanup;\n>\n> So if we were successful, we skip to the cleanup. Okay.\n>\n\nYes, that is right.\n\n> > +             args.object_info_data = data->options.object_info_data;\n> > +             args.quiet = 1;\n> > +             args.no_progress = 1;\n>\n> Not sure why we set quiet and no_progress here.\n>\n\nThank you.  If the code reaches here, it means we fall back to\ndownloading the pack file with fetch_pack().\nSetting quiet and no_progress just wants to make fetch_pack less\nverbose and do its job quietly in the background.\nIt is like calling `git fetch-pack -q ...`. I see setting quiet and\nno_progress is necessary here because:\n1. If the call git fetch-pack is from an internal command, we would\nbetter keep the call lean and efficient.\n2. If the user wants to do a verbose call, they have the choice to\ncall git fetch-pack directly from the client.\n\nI add a comment in v5 to explain it, like this:\n\" we can't retrieve object info in packets, so we will fall back to\ndownland pack files. We set quiet and no_progress to be true, so that\nthe internal call of fetch-pack is less verbose.\"\n\n\n\n> > +             for (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n> > +                     ref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n> > +                     ref_itr->exact_oid = 1;\n> > +                     if (i == transport->smart_options->object_info_oids->nr - 1)\n> > +                             /* last element, no need to allocate to next */\n> > +                             ref_itr->next = NULL;\n> > +                     else\n> > +                             ref_itr->next = alloc_ref(\"\");\n> >\n> > -     if (!data->finished_handshake) {\n> > -             int i;\n> > +                     ref_itr = ref_itr->next;\n> > +             }\n> > +\n> > +             transport->remote_refs = object_info_refs;\n> > +\n> > +     } else if (!data->finished_handshake) {\n> >               int must_list_refs = 0;\n> > -             for (i = 0; i < nr_heads; i++) {\n> > +             for (int i = 0; i < nr_heads; i++) {\n> >                       if (!to_fetch[i]->exact_oid) {\n> >                               must_list_refs = 1;\n> >                               break;\n> > @@ -494,16 +591,26 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >                         &transport->pack_lockfiles, data->version);\n> >\n> >       data->finished_handshake = 0;\n> > +     if (args.object_info) {\n> > +             struct ref *ref_cpy_reader = object_info_refs;\n> > +             for (int i = 0; ref_cpy_reader; i++) {\n> > +                     oid_object_info_extended(the_repository, &ref_cpy_reader->old_oid,\n> > +                             &args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n> > +                     ref_cpy_reader = ref_cpy_reader->next;\n> > +             }\n> > +     }\n> > +\n> >       data->options.self_contained_and_connected =\n> >               args.self_contained_and_connected;\n> >       data->options.connectivity_checked = args.connectivity_checked;\n> >\n> > -     if (!refs)\n> > +     if (!refs && !args.object_info)\n> >               ret = -1;\n>\n> This is because, now we don't necessary always fetch the refs, since\n> sometimes we're just happy fetching the object info. Would be nice to\n> have a comment here.\n>\n\nThank you. Acutally, if the code reaches here, it means we fall back\nto downloading the pack file.\nI would expect there is no difference from the old logic, so\n`!args.object_info` might not be needed here.\nI am removing `!args.object_info` in v5.\n\n> >       if (report_unmatched_refs(to_fetch, nr_heads))\n> >               ret = -1;\n> >\n> >  cleanup:\n> > +     free_refs(object_info_refs);\n> >       close(data->fd[0]);\n> >       if (data->fd[1] >= 0)\n> >               close(data->fd[1]);\n>\n>\n> [snip]\n"},{"id":"506256","messageId":"20241028203457.19715-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH v5 0/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-28T20:34:51Z","receivedAt":"2024-10-28T20:35:17Z","isPatch":true,"body":"This is a continuation of Calvin Wan's (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info command at [1].\n\nSometimes it is useful to get information about an object without having to download\nit completely. The server logic for retrieving size has already been implemented and merged in\n\"a2ba162cda (object-info: support for retrieving object info, 2021-04-20)\"[2].\nThis patch series implement the client option for it.\n\nThis patch series add the `remote-object-info` command to `cat-file --batch-command`.\nThis command allows the client to make an object-info command request to a server\nthat supports protocol v2. If the server is v2, but does not have\nobject-info capability, the entire object is fetched and the\nrelevant object info is returned.\n\nA few questions open for discussions please:\n\n1. In the current implementation, if a user puts `remote-object-info` in protocol v1,\n   `cat-file --batch-command` will die. Which way do we prefer? \"error and exit (i.e. die)\"\n   or \"warn and wait for new command\".\n\n2. Right now, only the size is supported. If the batch command format\n   contains objectsize:disk or deltabase, it will die. The question\n   is about objecttype. In the current implementation, it will die too.\n   But dying on objecttype breaks the default format. We have changed the\n   default format to %(objectname) %(objectsize) when remote-object-info is used.\n   Any suggestions on this approach?\n\n[1] https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[2] https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n\nChanges since V4\n================\n\n- Take write_command_and_capabilities() out of fetch_pack.c, put it in a higher level connect.c.\n\n- Move remote-object-info related logic out of fetch_pack.c, and put them in a new file fetch-object-info.c and fetch-object-info.h.\n\n- Add more comments, esapcially where we fallback to downloading pack files and on functions on sending arguments and receiving data.\n\n- Fix typos and formatting errors.\n\nThank you.\n\nEric Ju\n\nCalvin Wan (4):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n\nEric Ju (2):\n  cat-file: add declaration of variable i inside its for loop\n  cat-file: add remote-object-info to batch-command\n\n Documentation/git-cat-file.txt         |  24 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 119 +++-\n connect.c                              |  34 ++\n connect.h                              |   4 +\n fetch-object-info.c                    |  95 ++++\n fetch-object-info.h                    |  18 +\n fetch-pack.c                           |  51 +-\n fetch-pack.h                           |   2 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 739 +++++++++++++++++++++++++\n transport-helper.c                     |  11 +-\n transport.c                            |  80 ++-\n transport.h                            |  11 +\n 18 files changed, 1165 insertions(+), 71 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\nRange-diff against v4:\n1:  20f32be592 ! 1:  f678c6b76f fetch-pack: refactor packet writing\n    @@ Commit message\n     \n         Refactor write_fetch_command_and_capabilities() to be a more general\n         purpose function write_command_and_capabilities(), so that it can be\n    -    used by both fetch and future command.\n    +    used by both fetch and future commands.\n     \n         Here \"command\" means the \"operations\" supported by Git’s wire protocol\n         https://git-scm.com/docs/protocol-v2. An example would be a\n    @@ Commit message\n         the server side such as \"object-info\" implemented in \"a2ba162cda\n         (object-info: support for retrieving object info, 2021-04-20)\".\n     \n    -    In a future separate series, we can move\n    -    write_command_and_capabilities() to a higher-level file, such as\n    +    The new write_command_and_capabilities() function is also moved to\n         connect.c, so that it becomes accessible to other commands.\n     \n         Helped-by: Jonathan Tan <jonathantanmy@google.com>\n    @@ Commit message\n         Signed-off-by: Calvin Wan <calvinwan@google.com>\n         Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n     \n    - ## fetch-pack.c ##\n    -@@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n    - \treturn haves_added;\n    + ## connect.c ##\n    +@@ connect.c: int server_supports(const char *feature)\n    + \treturn !!server_feature_value(feature, NULL);\n      }\n      \n    --static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n    --\t\t\t\t\t\t const struct string_list *server_options)\n    -+static void write_command_and_capabilities(struct strbuf *req_buf,\n    -+\t\t\t\t\t\t\t\t\t\t   const char *command,\n    -+\t\t\t\t\t\t\t\t\t\t   const struct string_list *server_options)\n    - {\n    - \tconst char *hash_name;\n    - \n    --\tensure_server_supports_v2(\"fetch\");\n    --\tpacket_buf_write(req_buf, \"command=fetch\");\n    -+\tensure_server_supports_v2(command);\n    -+\tpacket_buf_write(req_buf, \"command=%s\", command);\n    - \tif (server_supports_v2(\"agent\"))\n    - \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n    - \tif (advertise_sid && server_supports_v2(\"session-id\"))\n    -@@ fetch-pack.c: static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n    - \tpacket_buf_delim(req_buf);\n    - }\n    - \n    -+\n    -+void send_object_info_request(int fd_out, struct object_info_args *args)\n    ++void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n    ++\t\t\t\t\t\t\t\t\tconst struct string_list *server_options)\n     +{\n    -+\tstruct strbuf req_buf = STRBUF_INIT;\n    ++\tconst char *hash_name;\n    ++\tint advertise_sid;\n     +\n    -+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n    ++\tgit_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n     +\n    -+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n    -+\t\tpacket_buf_write(&req_buf, \"size\");\n    -+\n    -+\tif (args->oids) {\n    -+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n    -+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n    ++\tensure_server_supports_v2(command);\n    ++\tpacket_buf_write(req_buf, \"command=%s\", command);\n    ++\tif (server_supports_v2(\"agent\"))\n    ++\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n    ++\tif (advertise_sid && server_supports_v2(\"session-id\"))\n    ++\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n    ++\tif (server_options && server_options->nr) {\n    ++\t\tensure_server_supports_v2(\"server-option\");\n    ++\t\tfor (int i = 0; i < server_options->nr; i++)\n    ++\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n    ++\t\t\t\t\t server_options->items[i].string);\n     +\t}\n     +\n    -+\tpacket_buf_flush(&req_buf);\n    -+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n    -+\t\tdie_errno(_(\"unable to write request to remote\"));\n    -+\n    -+\tstrbuf_release(&req_buf);\n    ++\tif (server_feature_v2(\"object-format\", &hash_name)) {\n    ++\t\tconst int hash_algo = hash_algo_by_name(hash_name);\n    ++\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n    ++\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n    ++\t\t\t\tthe_hash_algo->name, hash_name);\n    ++\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n    ++\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n    ++\t\tdie(_(\"the server does not support algorithm '%s'\"),\n    ++\t\t\tthe_hash_algo->name);\n    ++\t}\n    ++\tpacket_buf_delim(req_buf);\n     +}\n     +\n    + enum protocol {\n    + \tPROTO_LOCAL = 1,\n    + \tPROTO_FILE,\n    +\n    + ## connect.h ##\n    +@@\n    + #ifndef CONNECT_H\n    + #define CONNECT_H\n    + \n    ++#include \"string-list.h\"\n    + #include \"protocol.h\"\n    + \n    + #define CONNECT_VERBOSE       (1u << 0)\n    +@@ connect.h: void check_stateless_delimiter(int stateless_rpc,\n    + \t\t\t       struct packet_reader *reader,\n    + \t\t\t       const char *error);\n    + \n    ++void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n    ++\t\t\t\t\t\t\t\t\tconst struct string_list *server_options);\n    ++\n    + #endif\n    +\n    + ## fetch-pack.c ##\n    +@@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n    + \treturn haves_added;\n    + }\n    + \n    +-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n    +-\t\t\t\t\t\t const struct string_list *server_options)\n    +-{\n    +-\tconst char *hash_name;\n    +-\n    +-\tensure_server_supports_v2(\"fetch\");\n    +-\tpacket_buf_write(req_buf, \"command=fetch\");\n    +-\tif (server_supports_v2(\"agent\"))\n    +-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n    +-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n    +-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n    +-\tif (server_options && server_options->nr) {\n    +-\t\tint i;\n    +-\t\tensure_server_supports_v2(\"server-option\");\n    +-\t\tfor (i = 0; i < server_options->nr; i++)\n    +-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n    +-\t\t\t\t\t server_options->items[i].string);\n    +-\t}\n    +-\n    +-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n    +-\t\tint hash_algo = hash_algo_by_name(hash_name);\n    +-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n    +-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n    +-\t\t\t    the_hash_algo->name, hash_name);\n    +-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n    +-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n    +-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n    +-\t\t    the_hash_algo->name);\n    +-\t}\n    +-\tpacket_buf_delim(req_buf);\n    +-}\n    +-\n      static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n      \t\t\t      struct fetch_pack_args *args,\n      \t\t\t      const struct ref *wants, struct oidset *common,\n2:  75184a49f5 = 2:  e91f01ec4d fetch-pack: move fetch initialization\n3:  7d1f341589 = 3:  dfc685c90b serve: advertise object-info feature\n4:  e59964f6c9 ! 4:  7da1a1c904 transport: add client support for object-info\n    @@ Commit message\n         Signed-off-by: Calvin Wan <calvinwan@google.com>\n         Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n     \n    + ## Makefile ##\n    +@@ Makefile: LIB_OBJS += ewah/ewah_rlw.o\n    + LIB_OBJS += exec-cmd.o\n    + LIB_OBJS += fetch-negotiator.o\n    + LIB_OBJS += fetch-pack.o\n    ++LIB_OBJS += fetch-object-info.o\n    + LIB_OBJS += fmt-merge-msg.o\n    + LIB_OBJS += fsck.o\n    + LIB_OBJS += fsmonitor.o\n    +\n    + ## fetch-object-info.c (new) ##\n    +@@\n    ++#include \"git-compat-util.h\"\n    ++#include \"gettext.h\"\n    ++#include \"hex.h\"\n    ++#include \"pkt-line.h\"\n    ++#include \"connect.h\"\n    ++#include \"oid-array.h\"\n    ++#include \"object-store-ll.h\"\n    ++#include \"fetch-object-info.h\"\n    ++#include \"string-list.h\"\n    ++\n    ++/**\n    ++ * send_object_info_request sends git-cat-file object-info command and its\n    ++ * arguments into the request buffer.\n    ++ */\n    ++static void send_object_info_request(const int fd_out, struct object_info_args *args)\n    ++{\n    ++\tstruct strbuf req_buf = STRBUF_INIT;\n    ++\n    ++\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n    ++\n    ++\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n    ++\t\tpacket_buf_write(&req_buf, \"size\");\n    ++\n    ++\tif (args->oids) {\n    ++\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n    ++\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n    ++\t}\n    ++\n    ++\tpacket_buf_flush(&req_buf);\n    ++\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n    ++\t\tdie_errno(_(\"unable to write request to remote\"));\n    ++\n    ++\tstrbuf_release(&req_buf);\n    ++}\n    ++\n    ++/**\n    ++ * fetch_object_info sends git-cat-file object-info command into the request buf\n    ++ * and read the results from packets.\n    ++ */\n    ++int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n    ++\t\t\t\t\t  struct packet_reader *reader, struct object_info *object_info_data,\n    ++\t\t\t\t\t  const int stateless_rpc, const int fd_out)\n    ++{\n    ++\tint size_index = -1;\n    ++\n    ++\tswitch (version) {\n    ++\tcase protocol_v2:\n    ++\t\tif (!server_supports_v2(\"object-info\"))\n    ++\t\t\treturn -1;\n    ++\t\tif (unsorted_string_list_has_string(args->object_info_options, \"size\")\n    ++\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0))\n    ++\t\t\treturn -1;\n    ++\t\tsend_object_info_request(fd_out, args);\n    ++\t\tbreak;\n    ++\tcase protocol_v1:\n    ++\tcase protocol_v0:\n    ++\t\tdie(_(\"wrong protocol version. expected v2\"));\n    ++\tcase protocol_unknown_version:\n    ++\t\tBUG(\"unknown protocol version\");\n    ++\t}\n    ++\n    ++\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n    ++\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n    ++\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n    ++\t\t\treturn -1;\n    ++\t\t}\n    ++\t\tif (unsorted_string_list_has_string(args->object_info_options, reader->line)) {\n    ++\t\t\tif (!strcmp(reader->line, \"size\")) {\n    ++\t\t\t\tsize_index = i;\n    ++\t\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n    ++\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n    ++\t\t\t}\n    ++\t\t\tcontinue;\n    ++\t\t}\n    ++\t\treturn -1;\n    ++\t}\n    ++\n    ++\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n    ++\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n    ++\n    ++\t\tstring_list_split(&object_info_values, reader->line, ' ', -1);\n    ++\t\tif (0 <= size_index) {\n    ++\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n    ++\t\t\t\tdie(\"object-info: not our ref %s\",\n    ++\t\t\t\t\tobject_info_values.items[0].string);\n    ++\n    ++\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n    ++\t\t}\n    ++\n    ++\t\tstring_list_clear(&object_info_values, 0);\n    ++\t}\n    ++\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n    ++\n    ++\treturn 0;\n    ++}\n    +\n    + ## fetch-object-info.h (new) ##\n    +@@\n    ++#ifndef FETCH_OBJECT_INFO_H\n    ++#define FETCH_OBJECT_INFO_H\n    ++\n    ++#include \"pkt-line.h\"\n    ++#include \"protocol.h\"\n    ++#include \"object-store-ll.h\"\n    ++\n    ++struct object_info_args {\n    ++\tstruct string_list *object_info_options;\n    ++\tconst struct string_list *server_options;\n    ++\tstruct oid_array *oids;\n    ++};\n    ++\n    ++int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n    ++\t\t\t\t\t  struct packet_reader *reader, struct object_info *object_info_data,\n    ++\t\t\t\t\t  int stateless_rpc, int fd_out);\n    ++\n    ++#endif /* FETCH_OBJECT_INFO_H */\n    +\n      ## fetch-pack.c ##\n    -@@ fetch-pack.c: static void write_command_and_capabilities(struct strbuf *req_buf,\n    - \tpacket_buf_delim(req_buf);\n    - }\n    - \n    --\n    - void send_object_info_request(int fd_out, struct object_info_args *args)\n    - {\n    - \tstruct strbuf req_buf = STRBUF_INIT;\n     @@ fetch-pack.c: static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n      \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n      \t\targs->deepen = 1;\n    @@ fetch-pack.h: struct fetch_pack_args {\n      \n      \t/*\n      \t * Indicate that the remote of this request is a promisor remote. The\n    -@@ fetch-pack.h: struct fetch_pack_args {\n    - \tunsigned connectivity_checked:1;\n    - };\n    - \n    -+struct object_info_args {\n    -+\tstruct string_list *object_info_options;\n    -+\tconst struct string_list *server_options;\n    -+\tstruct oid_array *oids;\n    -+};\n    -+\n    - /*\n    -  * sought represents remote references that should be updated from.\n    -  * On return, the names that were found on the remote will have been\n    -@@ fetch-pack.h: int report_unmatched_refs(struct ref **sought, int nr_sought);\n    -  */\n    - int fetch_pack_fsck_objects(void);\n    - \n    -+void send_object_info_request(int fd_out, struct object_info_args *args);\n    -+\n    - #endif\n     \n      ## transport-helper.c ##\n     @@ transport-helper.c: static int fetch_refs(struct transport *transport,\n    @@ transport-helper.c: static int fetch_refs(struct transport *transport,\n      \t * If we reach here, then the server, the client, and/or the transport\n     -\t * helper does not support protocol v2. --negotiate-only requires\n     -\t * protocol v2.\n    -+\t * helper does not support protocol v2. --negotiate-only and cat-file remote-object-info\n    -+\t * require protocol v2.\n    ++\t * helper does not support protocol v2. --negotiate-only and cat-file\n    ++\t * remote-object-info require protocol v2.\n      \t */\n      \tif (data->transport_options.acked_commits) {\n      \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n    @@ transport-helper.c: static int fetch_refs(struct transport *transport,\n      \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\n     \n      ## transport.c ##\n    -@@ transport.c: static struct ref *handshake(struct transport *transport, int for_push,\n    - \treturn refs;\n    - }\n    - \n    -+static int fetch_object_info(struct transport *transport, struct object_info *object_info_data)\n    -+{\n    -+\tint size_index = -1;\n    -+\tstruct git_transport_data *data = transport->data;\n    -+\tstruct object_info_args args = { 0 };\n    -+\tstruct packet_reader reader;\n    -+\n    -+\targs.server_options = transport->server_options;\n    -+\targs.object_info_options = transport->smart_options->object_info_options;\n    -+\targs.oids = transport->smart_options->object_info_oids;\n    -+\n    -+\tconnect_setup(transport, 0);\n    -+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n    -+\t\t\tPACKET_READ_CHOMP_NEWLINE |\n    -+\t\t\tPACKET_READ_GENTLE_ON_EOF |\n    -+\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n    -+\tdata->version = discover_version(&reader);\n    -+\n    -+\ttransport->hash_algo = reader.hash_algo;\n    -+\n    -+\tswitch (data->version) {\n    -+\tcase protocol_v2:\n    -+\t\tif (!server_supports_v2(\"object-info\"))\n    -+\t\t\treturn -1;\n    -+\t\tif (unsorted_string_list_has_string(args.object_info_options, \"size\")\n    -+\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0))\n    -+\t\t\treturn -1;\n    -+\t\tsend_object_info_request(data->fd[1], &args);\n    -+\t\tbreak;\n    -+\tcase protocol_v1:\n    -+\tcase protocol_v0:\n    -+\t\tdie(_(\"wrong protocol version. expected v2\"));\n    -+\tcase protocol_unknown_version:\n    -+\t\tBUG(\"unknown protocol version\");\n    -+\t}\n    -+\n    -+\tfor (size_t i = 0; i < args.object_info_options->nr; i++) {\n    -+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL) {\n    -+\t\t\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n    -+\t\t\treturn -1;\n    -+\t\t}\n    -+\t\tif (unsorted_string_list_has_string(args.object_info_options, reader.line)) {\n    -+\t\t\tif (!strcmp(reader.line, \"size\")) {\n    -+\t\t\t\tsize_index = i;\n    -+\t\t\t\tfor (size_t j = 0; j < args.oids->nr; j++)\n    -+\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n    -+\t\t\t}\n    -+\t\t\tcontinue;\n    -+\t\t}\n    -+\t\treturn -1;\n    -+\t}\n    -+\n    -+\tfor (size_t i = 0; packet_reader_read(&reader) == PACKET_READ_NORMAL && i < args.oids->nr; i++){\n    -+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n    -+\n    -+\t\tstring_list_split(&object_info_values, reader.line, ' ', -1);\n    -+\t\tif (0 <= size_index) {\n    -+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n    -+\t\t\t\tdie(\"object-info: not our ref %s\",\n    -+\t\t\t\t\tobject_info_values.items[0].string);\n    -+\n    -+\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n    -+\t\t}\n    -+\n    -+\t\tstring_list_clear(&object_info_values, 0);\n    -+\t}\n    -+\tcheck_stateless_delimiter(transport->stateless_rpc, &reader, \"stateless delimiter expected\");\n    -+\n    -+\treturn 0;\n    -+}\n    -+\n    - static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n    - \t\t\t\t\tstruct transport_ls_refs_options *options)\n    - {\n    +@@\n    + #include \"hook.h\"\n    + #include \"pkt-line.h\"\n    + #include \"fetch-pack.h\"\n    ++#include \"fetch-object-info.h\"\n    + #include \"remote.h\"\n    + #include \"connect.h\"\n    + #include \"send-pack.h\"\n     @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \tstruct ref *refs = NULL;\n      \tstruct fetch_pack_args args;\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n     +\t\t&& transport->smart_options->object_info\n     +\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n     +\t\tstruct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n    ++\t\tstruct packet_reader reader;\n    ++\t\tstruct object_info_args obj_info_args = { 0 };\n     +\n    -+\t\tif (!fetch_object_info(transport, data->options.object_info_data))\n    -+\t\t\tgoto cleanup;\n    ++\t\tobj_info_args.server_options = transport->server_options;\n    ++\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n    ++\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n    ++\n    ++\t\tconnect_setup(transport, 0);\n    ++\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n    ++\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n    ++\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n    ++\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n    ++\n    ++\t\tdata->version = discover_version(&reader);\n    ++\t\ttransport->hash_algo = reader.hash_algo;\n     +\n    ++\t\tif (!fetch_object_info(data->version, &obj_info_args, &reader,\n    ++\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n    ++\t\t\tdata->fd[1])) {\n    ++\t\t\t/*\n    ++\t\t\t * If the code reaches here, fetch_object_info is successful and\n    ++\t\t\t * remote object info are retrieved from packets (i.e. without\n    ++\t\t\t * downloading the objects).\n    ++\t\t\t */\n    ++\t\t\tgoto cleanup;\n    ++\t\t}\n    + \n    +-\tif (!data->finished_handshake) {\n    +-\t\tint i;\n    ++\t\t/*\n    ++\t\t * If the code reaches here, it means we can't retrieve object info from\n    ++\t\t * packets, and we will fallback to downland the pack files.\n    ++\t\t * We set quiet and no_progress to be true, so that the internal call to\n    ++\t\t * fetch-pack is less verbose.\n    ++\t\t */\n     +\t\targs.object_info_data = data->options.object_info_data;\n     +\t\targs.quiet = 1;\n     +\t\targs.no_progress = 1;\n    ++\n    ++\t\t/*\n    ++\t\t * Allocate memory for object info data according to oids.\n    ++\t\t * The actual results will be retrieved later from the downloaded\n    ++\t\t * pack files.\n    ++\t\t */\n     +\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n     +\t\t\tref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n     +\t\t\tref_itr->exact_oid = 1;\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n     +\t\t\t\tref_itr->next = NULL;\n     +\t\t\telse\n     +\t\t\t\tref_itr->next = alloc_ref(\"\");\n    - \n    --\tif (!data->finished_handshake) {\n    --\t\tint i;\n    ++\n     +\t\t\tref_itr = ref_itr->next;\n     +\t\t}\n     +\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \t\t\t  &transport->pack_lockfiles, data->version);\n      \n      \tdata->finished_handshake = 0;\n    ++\n    ++\t/* Retrieve object info data from the downloaded pack files */\n     +\tif (args.object_info) {\n     +\t\tstruct ref *ref_cpy_reader = object_info_refs;\n     +\t\tfor (int i = 0; ref_cpy_reader; i++) {\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \tdata->options.self_contained_and_connected =\n      \t\targs.self_contained_and_connected;\n      \tdata->options.connectivity_checked = args.connectivity_checked;\n    - \n    --\tif (!refs)\n    -+\tif (!refs && !args.object_info)\n    - \t\tret = -1;\n    - \tif (report_unmatched_refs(to_fetch, nr_heads))\n    +@@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \t\tret = -1;\n      \n      cleanup:\n5:  4443e5e408 = 5:  2107bfc7ca cat-file: add declaration of variable i inside its for loop\n6:  c777a4dd84 ! 6:  740d629626 cat-file: add remote-object-info to batch-command\n    @@\n      ## Metadata ##\n    -Author: Calvin Wan <calvinwan@google.com>\n    +Author: Eric Ju <eric.peijian@gmail.com>\n     \n      ## Commit message ##\n         cat-file: add remote-object-info to batch-command\n    @@ object-store-ll.h: int for_each_object_in_pack(struct packed_git *p,\n     \n      ## t/lib-cat-file.sh (new) ##\n     @@\n    -+# Library of git-cat-file related functions.\n    ++# Library of git-cat-file related tests.\n     +\n     +# Print a string without a trailing newline\n     +echo_without_newline () {\n-- \n2.47.0\n\n"},{"id":"506257","messageId":"20241028203457.19715-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241028203457.19715-1-eric.peijian@gmail.com","subject":"[PATCH v5 1/6] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-28T20:34:52Z","receivedAt":"2024-10-28T20:35:18Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nRefactor write_fetch_command_and_capabilities() to be a more general\npurpose function write_command_and_capabilities(), so that it can be\nused by both fetch and future commands.\n\nHere \"command\" means the \"operations\" supported by Git’s wire protocol\nhttps://git-scm.com/docs/protocol-v2. An example would be a\ngit's subcommand, such as git-fetch(1); or an operation supported by\nthe server side such as \"object-info\" implemented in \"a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20)\".\n\nThe new write_command_and_capabilities() function is also moved to\nconnect.c, so that it becomes accessible to other commands.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 36 ++----------------------------------\n 3 files changed, 40 insertions(+), 34 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 58f53d8dcb..bb4e4eec44 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -688,6 +688,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t\t\t\t\t\tconst struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\tgit_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (int i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t\tthe_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t\tthe_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n enum protocol {\n \tPROTO_LOCAL = 1,\n \tPROTO_FILE,\ndiff --git a/connect.h b/connect.h\nindex 1645126c17..2ed009066e 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -1,6 +1,7 @@\n #ifndef CONNECT_H\n #define CONNECT_H\n \n+#include \"string-list.h\"\n #include \"protocol.h\"\n \n #define CONNECT_VERBOSE       (1u << 0)\n@@ -30,4 +31,7 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t\t\t\t\t\tconst struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f752da93a8..533fb76f95 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1314,38 +1314,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tint i;\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1356,7 +1324,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2174,7 +2142,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.47.0\n\n"},{"id":"506258","messageId":"20241028203457.19715-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241028203457.19715-1-eric.peijian@gmail.com","subject":"[PATCH v5 2/6] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-28T20:34:53Z","receivedAt":"2024-10-28T20:35:19Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 533fb76f95..afffbcaafc 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1645,18 +1645,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.47.0\n\n"},{"id":"506259","messageId":"20241028203457.19715-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241028203457.19715-1-eric.peijian@gmail.com","subject":"[PATCH v5 3/6] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-28T20:34:54Z","receivedAt":"2024-10-28T20:35:20Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex d674764a25..c3d8098642 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -70,7 +70,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -78,6 +78,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.47.0\n\n"},{"id":"506261","messageId":"20241028203457.19715-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241028203457.19715-1-eric.peijian@gmail.com","subject":"[PATCH v5 4/6] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-28T20:34:55Z","receivedAt":"2024-10-28T20:35:21Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes it is useful to get information about an object without having\nto download it completely. The server logic has already been implemented\nin “a2ba162cda (object-info: support for retrieving object info,\n2021-04-20)”.\n\nAdd client functions to communicate with the server.\n\nThe client currently supports requesting a list of object ids with\nfeature 'size' from a v2 server. If a server does not\nadvertise the feature, then the client falls back\nto making the request through 'fetch'.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Makefile            |  1 +\n fetch-object-info.c | 95 +++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h | 18 +++++++++\n fetch-pack.c        |  3 ++\n fetch-pack.h        |  2 +\n transport-helper.c  | 11 +++++-\n transport.c         | 80 ++++++++++++++++++++++++++++++++++++--\n transport.h         | 11 ++++++\n 8 files changed, 216 insertions(+), 5 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n\ndiff --git a/Makefile b/Makefile\nindex 8d8cc6ab90..3969ddcaa8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1024,6 +1024,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..4d7c2d265f\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,95 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"object-store-ll.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/**\n+ * send_object_info_request sends git-cat-file object-info command and its\n+ * arguments into the request buffer.\n+ */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids) {\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\t}\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+/**\n+ * fetch_object_info sends git-cat-file object-info command into the request buf\n+ * and read the results from packets.\n+ */\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t\t\t\t  struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t\t\t\t  const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\treturn -1;\n+\t\tif (unsorted_string_list_has_string(args->object_info_options, \"size\")\n+\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0))\n+\t\t\treturn -1;\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"wrong protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (unsorted_string_list_has_string(args->object_info_options, reader->line)) {\n+\t\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\t\tsize_index = i;\n+\t\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\n+\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..b1e545532f\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,18 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"object-store-ll.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t\t\t\t  struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t\t\t\t  int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex afffbcaafc..8b4143d752 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1651,6 +1651,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex b5c579cdae..cf7cedf161 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 013ec79dc9..334b35174e 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -709,8 +709,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file\n+\t * remote-object-info require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -726,6 +726,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 47fda6a773..41e157d73d 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -418,6 +419,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \tstruct ref *refs = NULL;\n \tstruct fetch_pack_args args;\n \tstruct ref *refs_tmp = NULL, **to_fetch_dup = NULL;\n+\tstruct ref *object_info_refs = NULL;\n \n \tmemset(&args, 0, sizeof(args));\n \targs.uploadpack = data->options.uploadpack;\n@@ -444,11 +446,71 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options\n+\t\t&& transport->smart_options->object_info\n+\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n+\t\tstruct packet_reader reader;\n+\t\tstruct object_info_args obj_info_args = { 0 };\n+\n+\t\tobj_info_args.server_options = transport->server_options;\n+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n+\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n+\n+\t\tconnect_setup(transport, 0);\n+\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\t\tdata->version = discover_version(&reader);\n+\t\ttransport->hash_algo = reader.hash_algo;\n+\n+\t\tif (!fetch_object_info(data->version, &obj_info_args, &reader,\n+\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n+\t\t\tdata->fd[1])) {\n+\t\t\t/*\n+\t\t\t * If the code reaches here, fetch_object_info is successful and\n+\t\t\t * remote object info are retrieved from packets (i.e. without\n+\t\t\t * downloading the objects).\n+\t\t\t */\n+\t\t\tgoto cleanup;\n+\t\t}\n \n-\tif (!data->finished_handshake) {\n-\t\tint i;\n+\t\t/*\n+\t\t * If the code reaches here, it means we can't retrieve object info from\n+\t\t * packets, and we will fallback to downland the pack files.\n+\t\t * We set quiet and no_progress to be true, so that the internal call to\n+\t\t * fetch-pack is less verbose.\n+\t\t */\n+\t\targs.object_info_data = data->options.object_info_data;\n+\t\targs.quiet = 1;\n+\t\targs.no_progress = 1;\n+\n+\t\t/*\n+\t\t * Allocate memory for object info data according to oids.\n+\t\t * The actual results will be retrieved later from the downloaded\n+\t\t * pack files.\n+\t\t */\n+\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n+\t\t\tref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n+\t\t\tref_itr->exact_oid = 1;\n+\t\t\tif (i == transport->smart_options->object_info_oids->nr - 1)\n+\t\t\t\t/* last element, no need to allocate to next */\n+\t\t\t\tref_itr->next = NULL;\n+\t\t\telse\n+\t\t\t\tref_itr->next = alloc_ref(\"\");\n+\n+\t\t\tref_itr = ref_itr->next;\n+\t\t}\n+\n+\t\ttransport->remote_refs = object_info_refs;\n+\n+\t} else if (!data->finished_handshake) {\n \t\tint must_list_refs = 0;\n-\t\tfor (i = 0; i < nr_heads; i++) {\n+\t\tfor (int i = 0; i < nr_heads; i++) {\n \t\t\tif (!to_fetch[i]->exact_oid) {\n \t\t\t\tmust_list_refs = 1;\n \t\t\t\tbreak;\n@@ -494,6 +556,17 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t  &transport->pack_lockfiles, data->version);\n \n \tdata->finished_handshake = 0;\n+\n+\t/* Retrieve object info data from the downloaded pack files */\n+\tif (args.object_info) {\n+\t\tstruct ref *ref_cpy_reader = object_info_refs;\n+\t\tfor (int i = 0; ref_cpy_reader; i++) {\n+\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid,\n+\t\t\t\t&args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n+\t\t\tref_cpy_reader = ref_cpy_reader->next;\n+\t\t}\n+\t}\n+\n \tdata->options.self_contained_and_connected =\n \t\targs.self_contained_and_connected;\n \tdata->options.connectivity_checked = args.connectivity_checked;\n@@ -504,6 +577,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\tret = -1;\n \n cleanup:\n+\tfree_refs(object_info_refs);\n \tclose(data->fd[0]);\n \tif (data->fd[1] >= 0)\n \t\tclose(data->fd[1]);\ndiff --git a/transport.h b/transport.h\nindex 44100fa9b7..42b8ee1251 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to pull only object-info. Fallbacks\n+\t * to pulling entire object if object-info is not supported.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.47.0\n\n"},{"id":"506260","messageId":"20241028203457.19715-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241028203457.19715-1-eric.peijian@gmail.com","subject":"[PATCH v5 5/6] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-28T20:34:56Z","receivedAt":"2024-10-28T20:35:22Z","isPatch":true,"body":"Some code declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n 1 file changed, 3 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex bfdfb51c7c..5db55fabc4 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -673,12 +673,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -686,9 +684,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -714,7 +710,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -724,7 +719,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \n-- \n2.47.0\n\n"},{"id":"506262","messageId":"20241028203457.19715-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241028203457.19715-1-eric.peijian@gmail.com","subject":"[PATCH v5 6/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-10-28T20:34:57Z","receivedAt":"2024-10-28T20:35:24Z","isPatch":true,"body":"Since the `info` command in cat-file --batch-command prints object info\nfor a given object, it is natural to add another command in cat-file\n--batch-command to print object info for a given object from a remote.\nAdd `remote-object-info` to cat-file --batch-command.\n\nWhile `info` takes object ids one at a time, this creates overhead when\nmaking requests to a server so `remote-object-info` instead can take\nmultiple object ids at once.\n\ncat-file --batch-command is generally implemented in the following\nmanner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loop through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication overhead\nin the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Documentation/git-cat-file.txt         |  24 +-\n builtin/cat-file.c                     | 108 +++-\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 739 +++++++++++++++++++++++++\n 7 files changed, 897 insertions(+), 17 deletions(-)\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex d5890ae368..f2be00b599 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -149,6 +149,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified <remote> without\n+\tdownloading objects from remote. If the object-info capability is not\n+\tsupported by the server, the objects will be downloaded instead.\n+\tError when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +297,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +306,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +323,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current the default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +408,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will error\n+and exit when they are in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 5db55fabc4..714c182f39 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -24,6 +24,9 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -42,9 +45,12 @@ struct batch_options {\n \tchar input_delim;\n \tchar output_delim;\n \tconst char *format;\n+\tint use_remote_info;\n };\n \n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -528,7 +534,7 @@ static void batch_one_object(const char *obj_name,\n \tenum get_oid_result result;\n \n \tresult = get_oid_with_context(the_repository, obj_name,\n-\t\t\t\t      flags, &data->oid, &ctx);\n+\t\t\t\t\t\t\t\t  flags, &data->oid, &ctx);\n \tif (result != FOUND) {\n \t\tswitch (result) {\n \t\tcase MISSING_OBJECT:\n@@ -576,6 +582,59 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT\n+\t*/\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\n+\t\t/* 'objectsize' is the only option currently supported */\n+\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -667,6 +726,52 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t   const char *line,\n+\t\t\t   struct strbuf *output,\n+\t\t\t   struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\n+\tchar *line_to_split = xstrdup_or_null(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\n+\topt->use_remote_info = 1;\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\n+\t\tdata->oid = object_info_oids.oid[i];\n+\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can't retrieve\n+\t\t\t * information from server without downloading them, and the objects\n+\t\t\t * have been fetched to client already.\n+\t\t\t * Print the information using the logic for local objects.\n+\t\t\t */\n+\t\t\tdata->skip_object_info = 0;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\n+\t}\n+\topt->use_remote_info = 0;\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -698,6 +803,7 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1},\n \t{ \"info\", parse_cmd_info, 1},\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1},\n \t{ \"flush\", NULL, 0},\n };\n \ndiff --git a/object-file.c b/object-file.c\nindex b1a3463852..181cde98e1 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -3132,3 +3132,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex 53b8e693b1..611e2ca708 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -548,4 +548,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(each_packed_object_fn, void *,\n \t\t\t   enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..9fb20be308\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related tests.\n+\n+# Print a string without a trailing newline\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replaces them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string removing any leading spaces.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex d36cd7c086..d8a851c427 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     oid=$2\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..f4bff07311\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,739 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback git://: fetch objects to client' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback file://: fetch objects to client' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback http://: fetch objects to client' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n-- \n2.47.0\n\n"},{"id":"506670","messageId":"CAP8UFD0Yk1Twv9DMOYwO4R+7P0YHj1CgK8xN=2YdE8sdi4ukow@mail.gmail.com","threadId":"61695","inReplyTo":"20241028203457.19715-2-eric.peijian@gmail.com","subject":"Re: [PATCH v5 1/6] fetch-pack: refactor packet writing","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-11-05T17:44:29Z","receivedAt":"2024-11-05T17:44:43Z","isPatch":true,"body":"On Mon, Oct 28, 2024 at 9:35 PM Eric Ju <eric.peijian@gmail.com> wrote:\n\n>  connect.c    | 34 ++++++++++++++++++++++++++++++++++\n>  connect.h    |  4 ++++\n>  fetch-pack.c | 36 ++----------------------------------\n>  3 files changed, 40 insertions(+), 34 deletions(-)\n>\n> diff --git a/connect.c b/connect.c\n> index 58f53d8dcb..bb4e4eec44 100644\n> --- a/connect.c\n> +++ b/connect.c\n> @@ -688,6 +688,40 @@ int server_supports(const char *feature)\n>         return !!server_feature_value(feature, NULL);\n>  }\n>\n> +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n> +                                                                       const struct string_list *server_options)\n\nWhen I apply your patches this line doesn't seem well indented.\n\n> +{\n> +       const char *hash_name;\n> +       int advertise_sid;\n> +\n> +       git_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nIt looks like moving the function to connect.c required adding the\nabove line into it. There are a few other small changes, including\nprobably spurious indentation changes, in the moved function which\nmake it a bit more difficult than necessary to check that the moved\ncode is the same as the original one.\n\nThis makes me wonder if it was actually a good idea to move the\nfunction, or if moving the function should have been done in a\nseparate step than the step making the small changes. Perhaps patch\n5/6 \"cat-file: add declaration of variable i inside its for loop\"\ncould have been moved before this patch and could have included some\nof the small changes related to the i variable that are made in this\npatch.\n\nIt might have been nice to mention the changes in the commit message anyway.\n\n> +       ensure_server_supports_v2(command);\n> +       packet_buf_write(req_buf, \"command=%s\", command);\n> +       if (server_supports_v2(\"agent\"))\n> +               packet_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n> +       if (advertise_sid && server_supports_v2(\"session-id\"))\n> +               packet_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n> +       if (server_options && server_options->nr) {\n> +               ensure_server_supports_v2(\"server-option\");\n> +               for (int i = 0; i < server_options->nr; i++)\n> +                       packet_buf_write(req_buf, \"server-option=%s\",\n> +                                        server_options->items[i].string);\n> +       }\n> +\n> +       if (server_feature_v2(\"object-format\", &hash_name)) {\n> +               const int hash_algo = hash_algo_by_name(hash_name);\n> +               if (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n> +                       die(_(\"mismatched algorithms: client %s; server %s\"),\n> +                               the_hash_algo->name, hash_name);\n> +               packet_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n> +       } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n> +               die(_(\"the server does not support algorithm '%s'\"),\n> +                       the_hash_algo->name);\n> +       }\n> +       packet_buf_delim(req_buf);\n> +}\n> +\n>  enum protocol {\n>         PROTO_LOCAL = 1,\n>         PROTO_FILE,\n> diff --git a/connect.h b/connect.h\n> index 1645126c17..2ed009066e 100644\n> --- a/connect.h\n> +++ b/connect.h\n> @@ -1,6 +1,7 @@\n>  #ifndef CONNECT_H\n>  #define CONNECT_H\n>\n> +#include \"string-list.h\"\n>  #include \"protocol.h\"\n>\n>  #define CONNECT_VERBOSE       (1u << 0)\n> @@ -30,4 +31,7 @@ void check_stateless_delimiter(int stateless_rpc,\n>                                struct packet_reader *reader,\n>                                const char *error);\n>\n> +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n> +                                                                       const struct string_list *server_options);\n\nWhen I apply your patches the above line doesn't seem well indented either.\n\nYou might want to make sure that your editor uses 8 spaces for each\ntab, see Documentation/CodingGuidelines, or just that your editor\nproperly follows our .editorconfig file.\n\nIt looks like other patches in the series, like patch 4/6, have\nsimilar issues. Otherwise the other patches in the series look good to\nme.\n\nThanks.\n"},{"id":"506690","messageId":"xmqqo72tqig8.fsf@gitster.g","threadId":"61695","inReplyTo":"CAP8UFD0Yk1Twv9DMOYwO4R+7P0YHj1CgK8xN=2YdE8sdi4ukow@mail.gmail.com","subject":"Re: [PATCH v5 1/6] fetch-pack: refactor packet writing","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-06T01:06:31Z","receivedAt":"2024-11-06T01:06:35Z","isPatch":true,"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> It looks like other patches in the series, like patch 4/6, have\n> similar issues. Otherwise the other patches in the series look good to\n> me.\n\nThanks for a review.\n"},{"id":"506748","messageId":"CAN2LT1CvM2oWD3Eni_f5agpzgycAjzaN5gPVU_9_KG9GxwP8dQ@mail.gmail.com","threadId":"61695","inReplyTo":"xmqqo72tqig8.fsf@gitster.g","subject":"Re: [PATCH v5 1/6] fetch-pack: refactor packet writing","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-06T18:00:45Z","receivedAt":"2024-11-06T18:00:56Z","isPatch":true,"body":"On Tue, Nov 5, 2024 at 8:06 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > It looks like other patches in the series, like patch 4/6, have\n> > similar issues. Otherwise the other patches in the series look good to\n> > me.\n>\n> Thanks for a review.\n\nThank you, sir. Yes, I noticed that. I will make sure my IDE respects\nthe .editorconfig and revise them in v6.\n"},{"id":"506754","messageId":"CAN2LT1COKBNukbMZz+AHJu-Fyx+SfrW5fo3=sFVuw-LSZwnyMw@mail.gmail.com","threadId":"61695","inReplyTo":"CAP8UFD0Yk1Twv9DMOYwO4R+7P0YHj1CgK8xN=2YdE8sdi4ukow@mail.gmail.com","subject":"Re: [PATCH v5 1/6] fetch-pack: refactor packet writing","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-06T19:50:33Z","receivedAt":"2024-11-06T19:50:44Z","isPatch":true,"body":"On Tue, Nov 5, 2024 at 12:44 PM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Mon, Oct 28, 2024 at 9:35 PM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> >  connect.c    | 34 ++++++++++++++++++++++++++++++++++\n> >  connect.h    |  4 ++++\n> >  fetch-pack.c | 36 ++----------------------------------\n> >  3 files changed, 40 insertions(+), 34 deletions(-)\n> >\n> > diff --git a/connect.c b/connect.c\n> > index 58f53d8dcb..bb4e4eec44 100644\n> > --- a/connect.c\n> > +++ b/connect.c\n> > @@ -688,6 +688,40 @@ int server_supports(const char *feature)\n> >         return !!server_feature_value(feature, NULL);\n> >  }\n> >\n> > +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n> > +                                                                       const struct string_list *server_options)\n>\n> When I apply your patches this line doesn't seem well indented.\n>\n\nThank you.  I will make sure my IDE respects\nthe .editorconfig and revise them in v6.\n\n> > +{\n> > +       const char *hash_name;\n> > +       int advertise_sid;\n> > +\n> > +       git_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n>\n> It looks like moving the function to connect.c required adding the\n> above line into it. There are a few other small changes, including\n> probably spurious indentation changes, in the moved function which\n> make it a bit more difficult than necessary to check that the moved\n> code is the same as the original one.\n>\n> This makes me wonder if it was actually a good idea to move the\n> function, or if moving the function should have been done in a\n> separate step than the step making the small changes. Perhaps patch\n> 5/6 \"cat-file: add declaration of variable i inside its for loop\"\n> could have been moved before this patch and could have included some\n> of the small changes related to the i variable that are made in this\n> patch.\n>\n> It might have been nice to mention the changes in the commit message anyway.\n>\n\nThank you. In v6, I will move small changes commit \"cat-file: add\ndeclaration of variable i inside its for loop\" to the very first\ncommit, where I will include small changes related to the i variable\nthat are made in this patch.\n\nAbout the extra charges related to `advertise_sid`. I did a bit of\nanalysis, please feel free to correct me.\nIn the original fetch-pack.c code, there are only two places that\nwrite `advertise_sid` :\n1. line 1221 (function do_fetch_pack):\n        if (!server_supports(\"session-id\"))\n                advertise_sid = 0;\n2. line 1895 (function fetch_pack_config)\n        git_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nIn 1, do_fetch_pack() is called when protocol is NOT v2. While\nwrite_fetch_command_and_capabilities() or the new\nwrite_command_and_capabilities() is only used in protocol v2, I think\nit is safe so it is safe to ignore 1, and only consider 2.\n\nIn 2, git_config_get_bool is from config.h and it is an out-of-box\ndependency of connect.c, so I just directly use it.\n\n\n> > +       ensure_server_supports_v2(command);\n> > +       packet_buf_write(req_buf, \"command=%s\", command);\n> > +       if (server_supports_v2(\"agent\"))\n> > +               packet_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n> > +       if (advertise_sid && server_supports_v2(\"session-id\"))\n> > +               packet_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n> > +       if (server_options && server_options->nr) {\n> > +               ensure_server_supports_v2(\"server-option\");\n> > +               for (int i = 0; i < server_options->nr; i++)\n> > +                       packet_buf_write(req_buf, \"server-option=%s\",\n> > +                                        server_options->items[i].string);\n> > +       }\n> > +\n> > +       if (server_feature_v2(\"object-format\", &hash_name)) {\n> > +               const int hash_algo = hash_algo_by_name(hash_name);\n> > +               if (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n> > +                       die(_(\"mismatched algorithms: client %s; server %s\"),\n> > +                               the_hash_algo->name, hash_name);\n> > +               packet_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n> > +       } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n> > +               die(_(\"the server does not support algorithm '%s'\"),\n> > +                       the_hash_algo->name);\n> > +       }\n> > +       packet_buf_delim(req_buf);\n> > +}\n> > +\n> >  enum protocol {\n> >         PROTO_LOCAL = 1,\n> >         PROTO_FILE,\n> > diff --git a/connect.h b/connect.h\n> > index 1645126c17..2ed009066e 100644\n> > --- a/connect.h\n> > +++ b/connect.h\n> > @@ -1,6 +1,7 @@\n> >  #ifndef CONNECT_H\n> >  #define CONNECT_H\n> >\n> > +#include \"string-list.h\"\n> >  #include \"protocol.h\"\n> >\n> >  #define CONNECT_VERBOSE       (1u << 0)\n> > @@ -30,4 +31,7 @@ void check_stateless_delimiter(int stateless_rpc,\n> >                                struct packet_reader *reader,\n> >                                const char *error);\n> >\n> > +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n> > +                                                                       const struct string_list *server_options);\n>\n> When I apply your patches the above line doesn't seem well indented either.\n>\n> You might want to make sure that your editor uses 8 spaces for each\n> tab, see Documentation/CodingGuidelines, or just that your editor\n> properly follows our .editorconfig file.\n>\n> It looks like other patches in the series, like patch 4/6, have\n> similar issues. Otherwise the other patches in the series look good to\n> me.\n>\n> Thanks.\n\nThank you. I will make sure my IDE respects\nthe .editorconfig and revise them in v6.\n"},{"id":"506868","messageId":"20241108162441.50736-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH v6 0/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-08T16:24:35Z","receivedAt":"2024-11-08T16:24:59Z","isPatch":true,"body":"This is a continuation of Calvin Wan's (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info command at [1].\n\nSometimes it is useful to get information about an object without having to download\nit completely. The server logic for retrieving size has already been implemented and merged in\n\"a2ba162cda (object-info: support for retrieving object info, 2021-04-20)\"[2].\nThis patch series implement the client option for it.\n\nThis patch series add the `remote-object-info` command to `cat-file --batch-command`.\nThis command allows the client to make an object-info command request to a server\nthat supports protocol v2. If the server is v2, but does not have\nobject-info capability, the entire object is fetched and the\nrelevant object info is returned.\n\nA few questions open for discussions please:\n\n1. In the current implementation, if a user puts `remote-object-info` in protocol v1,\n   `cat-file --batch-command` will die. Which way do we prefer? \"error and exit (i.e. die)\"\n   or \"warn and wait for new command\".\n\n2. Right now, only the size is supported. If the batch command format\n   contains objectsize:disk or deltabase, it will die. The question\n   is about objecttype. In the current implementation, it will die too.\n   But dying on objecttype breaks the default format. We have changed the\n   default format to %(objectname) %(objectsize) when remote-object-info is used.\n   Any suggestions on this approach?\n\n[1] https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[2] https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n\nChanges since V5\n================\n- Move small changes commit to the very first commit\n- Add more detailed description on what changes when moving write_fetch_command_and_capabilities() to connect.c\n- Fix indentation problems\n\nThank you.\n\nEric Ju\n\nCalvin Wan (4):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n\nEric Ju (2):\n  cat-file: add declaration of variable i inside its for loop\n  cat-file: add remote-object-info to batch-command\n\n Documentation/git-cat-file.txt         |  24 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 116 +++-\n connect.c                              |  34 ++\n connect.h                              |   4 +\n fetch-object-info.c                    |  95 ++++\n fetch-object-info.h                    |  18 +\n fetch-pack.c                           |  51 +-\n fetch-pack.h                           |   2 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 739 +++++++++++++++++++++++++\n transport-helper.c                     |  11 +-\n transport.c                            |  77 ++-\n transport.h                            |  11 +\n 18 files changed, 1162 insertions(+), 68 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\nRange-diff against v5:\n5:  0e533d6d0a ! 1:  858a864651 cat-file: add declaration of variable i inside its for loop\n    @@ Metadata\n      ## Commit message ##\n         cat-file: add declaration of variable i inside its for loop\n     \n    -    Some code declares variable i and only uses it\n    +    Some code used in this series declares variable i and only uses it\n         in a for loop, not in any other logic outside the loop.\n     \n         Change the declaration of i to be inside the for loop for readability.\n    @@ builtin/cat-file.c: static void batch_objects_command(struct batch_options *opt,\n      \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n      \t\t\t\tcontinue;\n      \n    +\n    + ## fetch-pack.c ##\n    +@@ fetch-pack.c: static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n    + \tif (advertise_sid && server_supports_v2(\"session-id\"))\n    + \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n    + \tif (server_options && server_options->nr) {\n    +-\t\tint i;\n    + \t\tensure_server_supports_v2(\"server-option\");\n    +-\t\tfor (i = 0; i < server_options->nr; i++)\n    ++\t\tfor (int i = 0; i < server_options->nr; i++)\n    + \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n    + \t\t\t\t\t server_options->items[i].string);\n    + \t}\n1:  d5c93792d3 ! 2:  51707f08d1 fetch-pack: refactor packet writing\n    @@ Metadata\n      ## Commit message ##\n         fetch-pack: refactor packet writing\n     \n    -    Refactor write_fetch_command_and_capabilities() to be a more general\n    -    purpose function write_command_and_capabilities(), so that it can be\n    -    used by both fetch and future commands.\n    +    Refactor write_fetch_command_and_capabilities() to a more\n    +    general-purpose function, write_command_and_capabilities(), enabling it\n    +    to serve both fetch and additional commands.\n     \n    -    Here \"command\" means the \"operations\" supported by Git’s wire protocol\n    -    https://git-scm.com/docs/protocol-v2. An example would be a\n    -    git's subcommand, such as git-fetch(1); or an operation supported by\n    -    the server side such as \"object-info\" implemented in \"a2ba162cda\n    -    (object-info: support for retrieving object info, 2021-04-20)\".\n    +    In this context, \"command\" refers to the \"operations\" supported by\n    +    Git's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\n    +    subcommand (e.g., git-fetch(1)) or a server-side operation like\n    +    \"object-info\" as implemented in commit a2ba162c\n    +    (object-info: support for retrieving object info, 2021-04-20).\n     \n    -    The new write_command_and_capabilities() function is also moved to\n    -    connect.c, so that it becomes accessible to other commands.\n    +    Furthermore, write_command_and_capabilities() is moved to connect.c,\n    +    making it accessible to additional commands in the future.\n    +\n    +    To move write_command_and_capabilities() to connect.c, we need to\n    +    adjust how `advertise_sid` is managed. Previously,\n    +    in fetch_pack.c, `advertise_sid` was a static variable, modified using\n    +    git_config_get_bool().\n    +\n    +    In connect.c, we now initialize `advertise_sid` at the beginning by\n    +    directly using git_config_get_bool(). This change is safe because:\n    +\n    +    In the original fetch-pack.c code, there are only two places that\n    +    write `advertise_sid` :\n    +    1. In function do_fetch_pack:\n    +            if (!server_supports(\"session-id\"))\n    +                    advertise_sid = 0;\n    +    2. In function fetch_pack_config():\n    +            git_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n    +\n    +    About 1, since do_fetch_pack() is only relevant for protocol v1, this\n    +    assignment can be ignored in our refactor, as\n    +    write_command_and_capabilities() is only used in protocol v2.\n    +\n    +    About 2, git_config_get_bool() is from config.h and it is an out-of-box\n    +    dependency of connect.c, so we can reuse it directly.\n     \n         Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Helped-by: Christian Couder <chriscool@tuxfamily.org>\n    @@ connect.c: int server_supports(const char *feature)\n      }\n      \n     +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n    -+\t\t\t\t\t\t\t\t\tconst struct string_list *server_options)\n    ++\t\t\t\t    const struct string_list *server_options)\n     +{\n     +\tconst char *hash_name;\n     +\tint advertise_sid;\n    @@ connect.h: void check_stateless_delimiter(int stateless_rpc,\n      \t\t\t       const char *error);\n      \n     +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n    -+\t\t\t\t\t\t\t\t\tconst struct string_list *server_options);\n    ++\t\t\t\t    const struct string_list *server_options);\n     +\n      #endif\n     \n    @@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n     -\tif (advertise_sid && server_supports_v2(\"session-id\"))\n     -\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n     -\tif (server_options && server_options->nr) {\n    --\t\tint i;\n     -\t\tensure_server_supports_v2(\"server-option\");\n    --\t\tfor (i = 0; i < server_options->nr; i++)\n    +-\t\tfor (int i = 0; i < server_options->nr; i++)\n     -\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n     -\t\t\t\t\t server_options->items[i].string);\n     -\t}\n2:  4bf51150c0 = 3:  f02338e90e fetch-pack: move fetch initialization\n3:  15b4095e28 = 4:  934bafb1db serve: advertise object-info feature\n4:  68794ae57a ! 5:  e2e94d1a32 transport: add client support for object-info\n    @@ fetch-object-info.c (new)\n     + * and read the results from packets.\n     + */\n     +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n    -+\t\t\t\t\t  struct packet_reader *reader, struct object_info *object_info_data,\n    -+\t\t\t\t\t  const int stateless_rpc, const int fd_out)\n    ++\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n    ++\t\t      const int stateless_rpc, const int fd_out)\n     +{\n     +\tint size_index = -1;\n     +\n    @@ fetch-object-info.h (new)\n     +};\n     +\n     +int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n    -+\t\t\t\t\t  struct packet_reader *reader, struct object_info *object_info_data,\n    -+\t\t\t\t\t  int stateless_rpc, int fd_out);\n    ++\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n    ++\t\t      int stateless_rpc, int fd_out);\n     +\n     +#endif /* FETCH_OBJECT_INFO_H */\n     \n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n     +\t\t}\n      \n     -\tif (!data->finished_handshake) {\n    --\t\tint i;\n     +\t\t/*\n     +\t\t * If the code reaches here, it means we can't retrieve object info from\n     +\t\t * packets, and we will fallback to downland the pack files.\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n     +\t\ttransport->remote_refs = object_info_refs;\n     +\n     +\t} else if (!data->finished_handshake) {\n    + \t\tint i;\n      \t\tint must_list_refs = 0;\n    --\t\tfor (i = 0; i < nr_heads; i++) {\n    -+\t\tfor (int i = 0; i < nr_heads; i++) {\n    - \t\t\tif (!to_fetch[i]->exact_oid) {\n    - \t\t\t\tmust_list_refs = 1;\n    - \t\t\t\tbreak;\n    + \t\tfor (i = 0; i < nr_heads; i++) {\n     @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \t\t\t  &transport->pack_lockfiles, data->version);\n      \n6:  98f8248203 ! 6:  69eb091a6b cat-file: add remote-object-info to batch-command\n    @@ Documentation/git-cat-file.txt: info <object>::\n      \toutput of `--batch-check`.\n      \n     +remote-object-info <remote> <object>...::\n    -+\tPrint object info for object references `<object>` at specified <remote> without\n    -+\tdownloading objects from remote. If the object-info capability is not\n    -+\tsupported by the server, the objects will be downloaded instead.\n    ++\tPrint object info for object references `<object>` at specified <remote>\n    ++\twithout downloading objects from remote. If the object-info capability\n    ++\tis not supported by the server, the objects will be downloaded instead.\n     +\tError when no object references are provided.\n     +\tThis command may be combined with `--buffer`.\n     +\n    @@ builtin/cat-file.c: struct batch_options {\n      \n      static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n      static int use_mailmap;\n    -@@ builtin/cat-file.c: static void batch_one_object(const char *obj_name,\n    - \tenum get_oid_result result;\n    - \n    - \tresult = get_oid_with_context(the_repository, obj_name,\n    --\t\t\t\t      flags, &data->oid, &ctx);\n    -+\t\t\t\t\t\t\t\t  flags, &data->oid, &ctx);\n    - \tif (result != FOUND) {\n    - \t\tswitch (result) {\n    - \t\tcase MISSING_OBJECT:\n     @@ builtin/cat-file.c: static void batch_one_object(const char *obj_name,\n      \tobject_context_release(&ctx);\n      }\n    @@ builtin/cat-file.c: static void parse_cmd_info(struct batch_options *opt,\n      }\n      \n     +static void parse_cmd_remote_object_info(struct batch_options *opt,\n    -+\t\t\t   const char *line,\n    -+\t\t\t   struct strbuf *output,\n    -+\t\t\t   struct expand_data *data)\n    ++\t\t\t\t\t const char *line, struct strbuf *output,\n    ++\t\t\t\t\t struct expand_data *data)\n     +{\n     +\tint count;\n     +\tconst char **argv;\n-- \n2.47.0\n\nbase-commit: 8f8d6eee531b3fa1a8ef14f169b0cb5035f7a772\nMerge Request: https://gitlab.com/gitlab-org/git/-/merge_requests/168\n"},{"id":"506869","messageId":"20241108162441.50736-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241108162441.50736-1-eric.peijian@gmail.com","subject":"[PATCH v6 1/6] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-08T16:24:36Z","receivedAt":"2024-11-08T16:25:00Z","isPatch":true,"body":"Some code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n fetch-pack.c       |  3 +--\n 2 files changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex bfdfb51c7c..5db55fabc4 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -673,12 +673,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -686,9 +684,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -714,7 +710,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -724,7 +719,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f752da93a8..3699cf9945 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1326,9 +1326,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (int i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n-- \n2.47.0\n\n"},{"id":"506870","messageId":"20241108162441.50736-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241108162441.50736-1-eric.peijian@gmail.com","subject":"[PATCH v6 2/6] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-08T16:24:37Z","receivedAt":"2024-11-08T16:25:01Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nRefactor write_fetch_command_and_capabilities() to a more\ngeneral-purpose function, write_command_and_capabilities(), enabling it\nto serve both fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162c\n(object-info: support for retrieving object info, 2021-04-20).\n\nFurthermore, write_command_and_capabilities() is moved to connect.c,\nmaking it accessible to additional commands in the future.\n\nTo move write_command_and_capabilities() to connect.c, we need to\nadjust how `advertise_sid` is managed. Previously,\nin fetch_pack.c, `advertise_sid` was a static variable, modified using\ngit_config_get_bool().\n\nIn connect.c, we now initialize `advertise_sid` at the beginning by\ndirectly using git_config_get_bool(). This change is safe because:\n\nIn the original fetch-pack.c code, there are only two places that\nwrite `advertise_sid` :\n1. In function do_fetch_pack:\n        if (!server_supports(\"session-id\"))\n                advertise_sid = 0;\n2. In function fetch_pack_config():\n        git_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, since do_fetch_pack() is only relevant for protocol v1, this\nassignment can be ignored in our refactor, as\nwrite_command_and_capabilities() is only used in protocol v2.\n\nAbout 2, git_config_get_bool() is from config.h and it is an out-of-box\ndependency of connect.c, so we can reuse it directly.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 35 ++---------------------------------\n 3 files changed, 40 insertions(+), 33 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 58f53d8dcb..5dd544335c 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -688,6 +688,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\tgit_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (int i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t\tthe_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t\tthe_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n enum protocol {\n \tPROTO_LOCAL = 1,\n \tPROTO_FILE,\ndiff --git a/connect.h b/connect.h\nindex 1645126c17..04043cd66d 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -1,6 +1,7 @@\n #ifndef CONNECT_H\n #define CONNECT_H\n \n+#include \"string-list.h\"\n #include \"protocol.h\"\n \n #define CONNECT_VERBOSE       (1u << 0)\n@@ -30,4 +31,7 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3699cf9945..533fb76f95 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1314,37 +1314,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (int i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1355,7 +1324,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2173,7 +2142,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.47.0\n\n"},{"id":"506873","messageId":"20241108162441.50736-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241108162441.50736-1-eric.peijian@gmail.com","subject":"[PATCH v6 3/6] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-08T16:24:38Z","receivedAt":"2024-11-08T16:25:02Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 533fb76f95..afffbcaafc 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1645,18 +1645,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.47.0\n\n"},{"id":"506871","messageId":"20241108162441.50736-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241108162441.50736-1-eric.peijian@gmail.com","subject":"[PATCH v6 4/6] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-08T16:24:39Z","receivedAt":"2024-11-08T16:25:04Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex d674764a25..c3d8098642 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -70,7 +70,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -78,6 +78,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.47.0\n\n"},{"id":"506872","messageId":"20241108162441.50736-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241108162441.50736-1-eric.peijian@gmail.com","subject":"[PATCH v6 5/6] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-08T16:24:40Z","receivedAt":"2024-11-08T16:25:04Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes it is useful to get information about an object without having\nto download it completely. The server logic has already been implemented\nin “a2ba162cda (object-info: support for retrieving object info,\n2021-04-20)”.\n\nAdd client functions to communicate with the server.\n\nThe client currently supports requesting a list of object ids with\nfeature 'size' from a v2 server. If a server does not\nadvertise the feature, then the client falls back\nto making the request through 'fetch'.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Makefile            |  1 +\n fetch-object-info.c | 95 +++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h | 18 +++++++++\n fetch-pack.c        |  3 ++\n fetch-pack.h        |  2 +\n transport-helper.c  | 11 +++++-\n transport.c         | 77 +++++++++++++++++++++++++++++++++++-\n transport.h         | 11 ++++++\n 8 files changed, 215 insertions(+), 3 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n\ndiff --git a/Makefile b/Makefile\nindex d06c9a8ffa..beca828963 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1024,6 +1024,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..c6abc69332\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,95 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"object-store-ll.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/**\n+ * send_object_info_request sends git-cat-file object-info command and its\n+ * arguments into the request buffer.\n+ */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids) {\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\t}\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+/**\n+ * fetch_object_info sends git-cat-file object-info command into the request buf\n+ * and read the results from packets.\n+ */\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\treturn -1;\n+\t\tif (unsorted_string_list_has_string(args->object_info_options, \"size\")\n+\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0))\n+\t\t\treturn -1;\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"wrong protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (unsorted_string_list_has_string(args->object_info_options, reader->line)) {\n+\t\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\t\tsize_index = i;\n+\t\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\n+\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..ce1a05dc96\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,18 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"object-store-ll.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex afffbcaafc..8b4143d752 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1651,6 +1651,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex b5c579cdae..cf7cedf161 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 013ec79dc9..334b35174e 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -709,8 +709,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file\n+\t * remote-object-info require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -726,6 +726,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 47fda6a773..7702b1926e 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -418,6 +419,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \tstruct ref *refs = NULL;\n \tstruct fetch_pack_args args;\n \tstruct ref *refs_tmp = NULL, **to_fetch_dup = NULL;\n+\tstruct ref *object_info_refs = NULL;\n \n \tmemset(&args, 0, sizeof(args));\n \targs.uploadpack = data->options.uploadpack;\n@@ -444,8 +446,69 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options\n+\t\t&& transport->smart_options->object_info\n+\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n+\t\tstruct packet_reader reader;\n+\t\tstruct object_info_args obj_info_args = { 0 };\n+\n+\t\tobj_info_args.server_options = transport->server_options;\n+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n+\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n+\n+\t\tconnect_setup(transport, 0);\n+\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\t\tdata->version = discover_version(&reader);\n+\t\ttransport->hash_algo = reader.hash_algo;\n+\n+\t\tif (!fetch_object_info(data->version, &obj_info_args, &reader,\n+\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n+\t\t\tdata->fd[1])) {\n+\t\t\t/*\n+\t\t\t * If the code reaches here, fetch_object_info is successful and\n+\t\t\t * remote object info are retrieved from packets (i.e. without\n+\t\t\t * downloading the objects).\n+\t\t\t */\n+\t\t\tgoto cleanup;\n+\t\t}\n \n-\tif (!data->finished_handshake) {\n+\t\t/*\n+\t\t * If the code reaches here, it means we can't retrieve object info from\n+\t\t * packets, and we will fallback to downland the pack files.\n+\t\t * We set quiet and no_progress to be true, so that the internal call to\n+\t\t * fetch-pack is less verbose.\n+\t\t */\n+\t\targs.object_info_data = data->options.object_info_data;\n+\t\targs.quiet = 1;\n+\t\targs.no_progress = 1;\n+\n+\t\t/*\n+\t\t * Allocate memory for object info data according to oids.\n+\t\t * The actual results will be retrieved later from the downloaded\n+\t\t * pack files.\n+\t\t */\n+\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n+\t\t\tref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n+\t\t\tref_itr->exact_oid = 1;\n+\t\t\tif (i == transport->smart_options->object_info_oids->nr - 1)\n+\t\t\t\t/* last element, no need to allocate to next */\n+\t\t\t\tref_itr->next = NULL;\n+\t\t\telse\n+\t\t\t\tref_itr->next = alloc_ref(\"\");\n+\n+\t\t\tref_itr = ref_itr->next;\n+\t\t}\n+\n+\t\ttransport->remote_refs = object_info_refs;\n+\n+\t} else if (!data->finished_handshake) {\n \t\tint i;\n \t\tint must_list_refs = 0;\n \t\tfor (i = 0; i < nr_heads; i++) {\n@@ -494,6 +557,17 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t  &transport->pack_lockfiles, data->version);\n \n \tdata->finished_handshake = 0;\n+\n+\t/* Retrieve object info data from the downloaded pack files */\n+\tif (args.object_info) {\n+\t\tstruct ref *ref_cpy_reader = object_info_refs;\n+\t\tfor (int i = 0; ref_cpy_reader; i++) {\n+\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid,\n+\t\t\t\t&args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n+\t\t\tref_cpy_reader = ref_cpy_reader->next;\n+\t\t}\n+\t}\n+\n \tdata->options.self_contained_and_connected =\n \t\targs.self_contained_and_connected;\n \tdata->options.connectivity_checked = args.connectivity_checked;\n@@ -504,6 +578,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\tret = -1;\n \n cleanup:\n+\tfree_refs(object_info_refs);\n \tclose(data->fd[0]);\n \tif (data->fd[1] >= 0)\n \t\tclose(data->fd[1]);\ndiff --git a/transport.h b/transport.h\nindex 44100fa9b7..42b8ee1251 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to pull only object-info. Fallbacks\n+\t * to pulling entire object if object-info is not supported.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.47.0\n\n"},{"id":"506874","messageId":"20241108162441.50736-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241108162441.50736-1-eric.peijian@gmail.com","subject":"[PATCH v6 6/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-08T16:24:41Z","receivedAt":"2024-11-08T16:25:05Z","isPatch":true,"body":"Since the `info` command in cat-file --batch-command prints object info\nfor a given object, it is natural to add another command in cat-file\n--batch-command to print object info for a given object from a remote.\nAdd `remote-object-info` to cat-file --batch-command.\n\nWhile `info` takes object ids one at a time, this creates overhead when\nmaking requests to a server so `remote-object-info` instead can take\nmultiple object ids at once.\n\ncat-file --batch-command is generally implemented in the following\nmanner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loop through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication overhead\nin the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Documentation/git-cat-file.txt         |  24 +-\n builtin/cat-file.c                     | 105 ++++\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 739 +++++++++++++++++++++++++\n 7 files changed, 895 insertions(+), 16 deletions(-)\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex d5890ae368..666422201c 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -149,6 +149,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified <remote>\n+\twithout downloading objects from remote. If the object-info capability\n+\tis not supported by the server, the objects will be downloaded instead.\n+\tError when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +297,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +306,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +323,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current the default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +408,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will error\n+and exit when they are in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 5db55fabc4..078e4517b8 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -24,6 +24,9 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -42,9 +45,12 @@ struct batch_options {\n \tchar input_delim;\n \tchar output_delim;\n \tconst char *format;\n+\tint use_remote_info;\n };\n \n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -576,6 +582,59 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT\n+\t*/\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\n+\t\t/* 'objectsize' is the only option currently supported */\n+\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -667,6 +726,51 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\n+\tchar *line_to_split = xstrdup_or_null(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\n+\topt->use_remote_info = 1;\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\n+\t\tdata->oid = object_info_oids.oid[i];\n+\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can't retrieve\n+\t\t\t * information from server without downloading them, and the objects\n+\t\t\t * have been fetched to client already.\n+\t\t\t * Print the information using the logic for local objects.\n+\t\t\t */\n+\t\t\tdata->skip_object_info = 0;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\n+\t}\n+\topt->use_remote_info = 0;\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -698,6 +802,7 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1},\n \t{ \"info\", parse_cmd_info, 1},\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1},\n \t{ \"flush\", NULL, 0},\n };\n \ndiff --git a/object-file.c b/object-file.c\nindex b1a3463852..181cde98e1 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -3132,3 +3132,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex 53b8e693b1..611e2ca708 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -548,4 +548,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(each_packed_object_fn, void *,\n \t\t\t   enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..9fb20be308\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related tests.\n+\n+# Print a string without a trailing newline\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replaces them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string removing any leading spaces.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex d36cd7c086..d8a851c427 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     oid=$2\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..f4bff07311\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,739 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback git://: fetch objects to client' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback file://: fetch objects to client' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID fallback' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+\n+test_expect_success 'remote-object-info fallback http://: fetch objects to client' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# Prove object is not on the client\n+\t\techo \"$hello_oid missing\" >expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\t# These results prove remote-object-info can retrieve object info\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results are for the info command\n+\t\t# They prove objects are downloaded\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n-- \n2.47.0\n\n"},{"id":"506962","messageId":"xmqq4j4egza6.fsf@gitster.g","threadId":"61695","inReplyTo":"20241108162441.50736-1-eric.peijian@gmail.com","subject":"Re: [PATCH v6 0/6] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-11T04:38:57Z","receivedAt":"2024-11-11T04:39:01Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n> This is a continuation of Calvin Wan's (calvinwan@google.com)\n> patch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info command at [1].\n>\n> Sometimes it is useful to get information about an object without having to download\n> it completely. The server logic for retrieving size has already been implemented and merged in\n> \"a2ba162cda (object-info: support for retrieving object info, 2021-04-20)\"[2].\n> This patch series implement the client option for it.\n>\n> This patch series add the `remote-object-info` command to `cat-file --batch-command`.\n> This command allows the client to make an object-info command request to a server\n> that supports protocol v2. If the server is v2, but does not have\n> object-info capability, the entire object is fetched and the\n> relevant object info is returned.\n>\n> A few questions open for discussions please:\n>\n> 1. In the current implementation, if a user puts `remote-object-info` in protocol v1,\n>    `cat-file --batch-command` will die. Which way do we prefer? \"error and exit (i.e. die)\"\n>    or \"warn and wait for new command\".\n\nIn the primary use case envisioned, would it be a program that is\ndriving the \"cat-file --batch-command\" process?  Can it sensibly\nreact to \"warn and wait\" and throw different commands to achieve\nwhat it wanted to do with the remote-object-info command?\n\nIf the answer is \"no\", die would be more appropriate.\n\n> 2. Right now, only the size is supported. If the batch command format\n>    contains objectsize:disk or deltabase, it will die. The question\n>    is about objecttype. In the current implementation, it will die too.\n>    But dying on objecttype breaks the default format. We have changed the\n>    default format to %(objectname) %(objectsize) when remote-object-info is used.\n>    Any suggestions on this approach?\n\nWhy bend the default format to the shortcoming of the new feature?\nWhat makes it impossible to learn what type of object it is?  If the\nlimitation that makes it impossible cannot be avoided, would it make\nmore sense to fall back to the \"fetch and locally inspect\" just like\n\"the other side does not know how to do object-info\" case?\n\nAnother thing you did not list, which is related, is where the\n\"fetch and locally inspect\" fallback fetch the object into.  Would\nwe use a quarantine mechanism, so that a mere request for remote\nobject info for an object will not contaminate our local object\nstore until the next gc realizes that such an object is dangling?\n\nThanks.\n"},{"id":"507528","messageId":"CAN2LT1Cmsw3RB1kbRBvoeLs8WaQeZWqrG96EQfMkMe_jdKaO4g@mail.gmail.com","threadId":"61695","inReplyTo":"xmqq4j4egza6.fsf@gitster.g","subject":"Re: [PATCH v6 0/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-18T16:28:23Z","receivedAt":"2024-11-18T16:28:35Z","isPatch":true,"body":"On Sun, Nov 10, 2024 at 11:39 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> > This is a continuation of Calvin Wan's (calvinwan@google.com)\n> > patch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info command at [1].\n> >\n> > Sometimes it is useful to get information about an object without having to download\n> > it completely. The server logic for retrieving size has already been implemented and merged in\n> > \"a2ba162cda (object-info: support for retrieving object info, 2021-04-20)\"[2].\n> > This patch series implement the client option for it.\n> >\n> > This patch series add the `remote-object-info` command to `cat-file --batch-command`.\n> > This command allows the client to make an object-info command request to a server\n> > that supports protocol v2. If the server is v2, but does not have\n> > object-info capability, the entire object is fetched and the\n> > relevant object info is returned.\n> >\n> > A few questions open for discussions please:\n> >\n> > 1. In the current implementation, if a user puts `remote-object-info` in protocol v1,\n> >    `cat-file --batch-command` will die. Which way do we prefer? \"error and exit (i.e. die)\"\n> >    or \"warn and wait for new command\".\n>\n> In the primary use case envisioned, would it be a program that is\n> driving the \"cat-file --batch-command\" process?  Can it sensibly\n> react to \"warn and wait\" and throw different commands to achieve\n> what it wanted to do with the remote-object-info command?\n>\n> If the answer is \"no\", die would be more appropriate.\n>\n\nThank you, sir.\n\nI’m inclined to answer \"no.\"\n\nOur primary use case is to use git cat-file remote-object-info in a\npromisor remote setup to retrieve metadata about an object stored in\nthe promisor remote, without fetching it back to the local repository.\nThis approach helps conserve disk space. I don’t believe other\ncommands can achieve this functionality, particularly without\nrequiring the object to be downloaded.\n\nIn the context of GitLab, we can mandate a specific version of Git to\nbe used alongside GitLab. Therefore, it is acceptable to error out if\nthe required Git version is not available, as we can ensure\ncompatibility by enforcing the version requirement.\n\nAlso, Mr. Christian Couder provided me with another more concrete example:\n\nFor example, consider a partial clone user initially interested in\nonly the foo/ and bar/ directories. They might execute git clone\n--filter=blob:none --no-checkout <url> followed by git sparse-checkout\nset foo bar. Later, they decided to estimate how much space would be\nrequired to fetch everything related to the baz/ directory.\n\nThe challenge arises because remote-object-info might need to operate\nrecursively to calculate the total size for all objects related to\nbaz/. A driver program that drives these recursive operations would\nstruggle to handle a “warn and wait” mechanism effectively, as it\nwould need to issue additional commands dynamically based on the\nwarning. If the only alternative is to fetch the objects directly, it\nwould defeat the purpose of using remote-object-info—which is intended\nto provide size information without actually downloading the objects.\n\n\n\n> > 2. Right now, only the size is supported. If the batch command format\n> >    contains objectsize:disk or deltabase, it will die. The question\n> >    is about objecttype. In the current implementation, it will die too.\n> >    But dying on objecttype breaks the default format. We have changed the\n> >    default format to %(objectname) %(objectsize) when remote-object-info is used.\n> >    Any suggestions on this approach?\n>\n> Why bend the default format to the shortcoming of the new feature?\n> What makes it impossible to learn what type of object it is?  If the\n> limitation that makes it impossible cannot be avoided, would it make\n> more sense to fall back to the \"fetch and locally inspect\" just like\n> \"the other side does not know how to do object-info\" case?\n>\n\nThank you.\n\nIt is indeed possible to determine the type of an object, and the plan\nwas to include this functionality in a follow-up patch series to\nensure an iterative development approach. We expect that developers\nusing this feature will have some experience with Git and will notice\nthe warnings in the documentation, which caution against relying on\nthe default format remaining unchanged.\n\nWhile the “fetch and locally inspect” approach is an option, it would\nundermines the purpose of the feature, as highlighted by Christian’s\npartial clone and sparse checkout example. This feature is\nspecifically designed to provide information without requiring the\nobjects to be fetched, making such an alternative counterproductive.\n\n\n> Another thing you did not list, which is related, is where the\n> \"fetch and locally inspect\" fallback fetch the object into.  Would\n> we use a quarantine mechanism, so that a mere request for remote\n> object info for an object will not contaminate our local object\n> store until the next gc realizes that such an object is dangling?\n>\n> Thanks.\n\nThank you.\n\nCurrently, the fetched object becomes a loose object in the local\nobject store. We have a bunch of test cases covering it in\nt1017-cat-file-remote-object-info.sh to cover it. For example:\n'remote-object-info fallback git://: fetch objects to client' '\n'remote-object-info fallback file://: fetch objects to client' '\n'remote-object-info fallback http://: fetch objects to client' '\nwhere transfer.advertiseobjectinfo is set to false.\n\n\nI am not sure about adding a quarantine mechanism at this stage:\n\nPros:\n- The quarantine area can be garbage collected, preventing\ncontamination of the local storage.\n- The quarantine area could be utilized to calculate metadata\ninformation, such as in the partial clone and sparse checkout example\nmentioned above. Objects in the quarantine can be selectively included\nor excluded from such calculations.\n\nCons:\n- Implementing a quarantine mechanism seems like a separate feature.\nThis patch series already introduces a number of changes, and\nincluding the quarantine mechanism might make it too extensive.\n\n- Based on Mr. Patrick Steinhardt’s comment at [1], since\nremote-object-info operates only on protocol v2, adding a quarantine\nmechanism may lead to differing client-side behavior depending on the\nprotocol, which could complicate the feature’s consistency.\n\nIn my opinion, the quarantine mechanism appears to have a broader\nscope that extends beyond just remote-object-info. If deemed\nnecessary, it would be more appropriate to address it in its own\ndedicated patch series.\n\n\n[1] https://gitlab.com/gitlab-org/git/-/merge_requests/168#note_2212333586\n"},{"id":"507554","messageId":"xmqqplmshybx.fsf@gitster.g","threadId":"61695","inReplyTo":"CAN2LT1Cmsw3RB1kbRBvoeLs8WaQeZWqrG96EQfMkMe_jdKaO4g@mail.gmail.com","subject":"Re: [PATCH v6 0/6] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-19T00:16:50Z","receivedAt":"2024-11-19T00:16:54Z","isPatch":true,"body":"Peijian Ju <eric.peijian@gmail.com> writes:\n\n> While the “fetch and locally inspect” approach is an option, it would\n> undermines the purpose of the feature, as highlighted by Christian’s\n> partial clone and sparse checkout example. This feature is\n> specifically designed to provide information without requiring the\n> objects to be fetched, making such an alternative counterproductive.\n\nThanks, then wouldn't it make more sense to say, because support for\nnew protocol capabilities on the server side would have to happen at\na lot fewer places than the clients, we only work when the necessary\nprotocol extension support is available, without any \"fetch and\nlocally inspect\" fallback?\n\nThe above is after reading your \"cons\" here of the fallback.\n\n> Cons:\n> - Implementing a quarantine mechanism seems like a separate feature.\n> This patch series already introduces a number of changes, and\n> including the quarantine mechanism might make it too extensive.\n\nNot an excuse to introduce incomplete changes that are not\nsufficient to be useful, though.\n\n> - Based on Mr. Patrick Steinhardt’s comment at [1], since\n> remote-object-info operates only on protocol v2, adding a quarantine\n> mechanism may lead to differing client-side behavior depending on the\n> protocol, which could complicate the feature’s consistency.\n\nNot doing quarantine would give even _more_ different client-side\nbehaviour, though.  When talking with a server with v2, you'll not\nsee a cruft object left locally, but with older servers, you'll see\ncrufts left behind.  After a failed remote-object-info call, you can\ndo an object-info to figure out what you needed to learn about the\nobject, but only after the failed remote-object-info was against an\nolder server.\n\nSo, I do not see it as a reason against putting temporary objects\ninto quarantine.\n\nNot that I consider it important to give the same client-side\nbehaviour when talking with older and newer servers, though.  It is\nnatural for a new feature to be available only with versions of Git\nthat supports the feature, after all.\n\nAnd if we throw that away as a goal, it starts to make more sense\nnot to add \"fetch and locally inspect\" anywhere.\n"},{"id":"507568","messageId":"ZzwwuU2MifISW5BJ@pks.im","threadId":"61695","inReplyTo":"xmqqplmshybx.fsf@gitster.g","subject":"Re: [PATCH v6 0/6] cat-file: add remote-object-info to batch-command","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-11-19T06:31:27Z","receivedAt":"2024-11-19T06:31:42Z","isPatch":true,"body":"On Tue, Nov 19, 2024 at 09:16:50AM +0900, Junio C Hamano wrote:\n> Peijian Ju <eric.peijian@gmail.com> writes:\n> > - Based on Mr. Patrick Steinhardt’s comment at [1], since\n> > remote-object-info operates only on protocol v2, adding a quarantine\n> > mechanism may lead to differing client-side behavior depending on the\n> > protocol, which could complicate the feature’s consistency.\n> \n> Not doing quarantine would give even _more_ different client-side\n> behaviour, though.  When talking with a server with v2, you'll not\n> see a cruft object left locally, but with older servers, you'll see\n> crufts left behind.  After a failed remote-object-info call, you can\n> do an object-info to figure out what you needed to learn about the\n> object, but only after the failed remote-object-info was against an\n> older server.\n> \n> So, I do not see it as a reason against putting temporary objects\n> into quarantine.\n\nI agree, and that's also what I wanted to say in the linked comment.\n\n> Not that I consider it important to give the same client-side\n> behaviour when talking with older and newer servers, though.  It is\n> natural for a new feature to be available only with versions of Git\n> that supports the feature, after all.\n\nI think having subtly different behaviour like this is a recipe for\nconfusion. As an end user (or rather end developer in this context) it\nis quite likely that you start to rely on the object either being\nfetched or not fetched as they probably won't end up testing against\nservers with both protocol versions. So you'd have to be aware of the\nfallback behaviour, and given that it is rather subtle and thus easy to\nmiss the end result would likely be confusion when it works different in\nsome repos than in others.\n\n> And if we throw that away as a goal, it starts to make more sense\n> not to add \"fetch and locally inspect\" anywhere.\n\nWhile having a quarantine directory would help with the case where you\nhave differing end user behaviour depending on the protocol, it of\ncourse wouldn't help with the implied performance hit when using the\nfallback logic.\n\nSo maybe not having the fallback is the best solution after all, and\nwhen users have a good use case for why they need it we could implement\nit in a future iteration.\n\nPatrick\n"},{"id":"507571","messageId":"xmqqy11fd8i3.fsf@gitster.g","threadId":"61695","inReplyTo":"ZzwwuU2MifISW5BJ@pks.im","subject":"Re: [PATCH v6 0/6] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-19T06:48:20Z","receivedAt":"2024-11-19T06:48:24Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> While having a quarantine directory would help with the case where you\n> have differing end user behaviour depending on the protocol, it of\n> course wouldn't help with the implied performance hit when using the\n> fallback logic.\n>\n> So maybe not having the fallback is the best solution after all, and\n> when users have a good use case for why they need it we could implement\n> it in a future iteration.\n\nAnd I would strongly suspect that we won't have to implement any\nfallback---hopefully folks upgrade their server side to be capable\nof whatever capability is needed fast enough ;-).\n\nThanks.\n"},{"id":"507630","messageId":"CAN2LT1Dm17-mmoMQr457fb5ta-TxG6Fj3Ma-gPh4YRJV9rRDrw@mail.gmail.com","threadId":"61695","inReplyTo":"xmqqy11fd8i3.fsf@gitster.g","subject":"Re: [PATCH v6 0/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-19T16:35:49Z","receivedAt":"2024-11-19T16:36:03Z","isPatch":true,"body":"On Tue, Nov 19, 2024 at 1:48 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Patrick Steinhardt <ps@pks.im> writes:\n>\n> > While having a quarantine directory would help with the case where you\n> > have differing end user behaviour depending on the protocol, it of\n> > course wouldn't help with the implied performance hit when using the\n> > fallback logic.\n> >\n> > So maybe not having the fallback is the best solution after all, and\n> > when users have a good use case for why they need it we could implement\n> > it in a future iteration.\n>\n> And I would strongly suspect that we won't have to implement any\n> fallback---hopefully folks upgrade their server side to be capable\n> of whatever capability is needed fast enough ;-).\n>\n> Thanks.\n\nThank you, sir, and also Patrick and Christian, for helping clarify\nthis for me. That makes sense. I will remove the \"fetch and inspect\"\napproach in the next series.\n"},{"id":"507674","messageId":"xmqqr076aei7.fsf@gitster.g","threadId":"61695","inReplyTo":"CAN2LT1Dm17-mmoMQr457fb5ta-TxG6Fj3Ma-gPh4YRJV9rRDrw@mail.gmail.com","subject":"Re: [PATCH v6 0/6] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-20T01:19:12Z","receivedAt":"2024-11-20T01:42:57Z","isPatch":true,"body":"Peijian Ju <eric.peijian@gmail.com> writes:\n\n> On Tue, Nov 19, 2024 at 1:48 AM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>> > While having a quarantine directory would help with the case where you\n>> > have differing end user behaviour depending on the protocol, it of\n>> > course wouldn't help with the implied performance hit when using the\n>> > fallback logic.\n>> >\n>> > So maybe not having the fallback is the best solution after all, and\n>> > when users have a good use case for why they need it we could implement\n>> > it in a future iteration.\n>>\n>> And I would strongly suspect that we won't have to implement any\n>> fallback---hopefully folks upgrade their server side to be capable\n>> of whatever capability is needed fast enough ;-).\n>>\n>> Thanks.\n>\n> Thank you, sir, and also Patrick and Christian, for helping clarify\n> this for me. That makes sense. I will remove the \"fetch and inspect\"\n> approach in the next series.\n\nThanks, everybody.  And thank you for working on this topic.\n\n"},{"id":"508013","messageId":"20241125053616.25170-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH v7 0/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-25T05:36:10Z","receivedAt":"2024-11-25T05:36:38Z","isPatch":true,"body":"This patch series is a continuation of Calvin Wan’s (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info\ncommand at [1].\n\nSometimes it is beneficial to retrieve information about an object without\nhaving to download it completely. The server logic for retrieving size has\nalready been implemented and merged in \"a2ba162cda (object-info: support for\nretrieving object info, 2021-04-20)\"[2]. This patch series implement the client\noption for it.\n\nThis patch series add the `remote-object-info` command to\n`cat-file --batch-command`. This command allows the client to make an\nobject-info command request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info capability,\n`cat-file --batch-command` will die.\n\nIf a user attempts to use `remote-object-info` with protocol v1,,\n`cat-file --batch-command` will die.\n\nCurrently, only the size is supported in this implementation. If the batch\ncommand format contains objecttype objectsize:disk or deltabase, it will die.\n\nThe default format is set to %(objectname) %(objectsize) when remote-object-info\nis used. When \"%(objecttype)\" is supported, the default format will be unified.\n\n[1] https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[2] https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n\nChanges since V6\n================\n- Removal of “Fetch and Inspect” Fallback. In the previous patch series, if the\n  object-info capability was not supported (i.e., transfer.advertiseobjectinfo\n  was set to false), the remote-object-info command would fall back to fetching\n  the object locally and inspecting it. This “fetch and inspect” behavior has\n  been removed. Instead, the client will now error and exit if the object-info\n  capability is not supported by the server. For more details, refer to the\n  discussion at this thread\n  https://lore.kernel.org/git/CAN2LT1Dm17-mmoMQr457fb5ta-TxG6Fj3Ma-gPh4YRJV9rRDrw@mail.gmail.com/.\n\n- Test Updates. Adjusted tests to cover cases where the object-info capability\n  is not supported by the server.\n\n- Documentation Updates. Removed references to the “fetch and inspect” fallback\n  mechanism.\n\n- Typos and Formatting Fixes.\n\nCalvin Wan (4):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n\nEric Ju (2):\n  cat-file: add declaration of variable i inside its for loop\n  cat-file: add remote-object-info to batch-command\n\n Documentation/git-cat-file.txt         |  24 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 110 ++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    |  92 ++++\n fetch-object-info.h                    |  18 +\n fetch-pack.c                           |  51 +-\n fetch-pack.h                           |   2 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 652 +++++++++++++++++++++++++\n transport-helper.c                     |  11 +-\n transport.c                            |  28 +-\n transport.h                            |  11 +\n 18 files changed, 1021 insertions(+), 68 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\nRange-diff against v6:\n1:  0998382d5e = 1:  92feca4218 cat-file: add declaration of variable i inside its for loop\n2:  26b861f416 ! 2:  58a24a4b92 fetch-pack: refactor packet writing\n    @@ connect.h: void check_stateless_delimiter(int stateless_rpc,\n      \t\t\t       struct packet_reader *reader,\n      \t\t\t       const char *error);\n      \n    ++/**\n    ++ * write_command_and_capabilities writes a command along with the requested\n    ++ * server capabilities/features into a request buffer.\n    ++ */\n     +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n     +\t\t\t\t    const struct string_list *server_options);\n     +\n3:  044d0ec46c = 3:  6bd0f945c2 fetch-pack: move fetch initialization\n4:  09b4a2c081 = 4:  000c82b681 serve: advertise object-info feature\n5:  4782211b31 ! 5:  97c03a9c2c transport: add client support for object-info\n    @@ Metadata\n      ## Commit message ##\n         transport: add client support for object-info\n     \n    -    Sometimes it is useful to get information about an object without having\n    -    to download it completely. The server logic has already been implemented\n    -    in “a2ba162cda (object-info: support for retrieving object info,\n    -    2021-04-20)”.\n    +    Sometimes, it is beneficial to retrieve information about an object\n    +    without downloading it entirely. The server-side logic for this\n    +    functionality was implemented in commit \"a2ba162cda (object-info:\n    +    support for retrieving object info, 2021-04-20).\"\n     \n    -    Add client functions to communicate with the server.\n    +    This commit introduces client functions to interact with the server.\n     \n    -    The client currently supports requesting a list of object ids with\n    -    feature 'size' from a v2 server. If a server does not\n    -    advertise the feature, then the client falls back\n    -    to making the request through 'fetch'.\n    +    Currently, the client supports requesting a list of object IDs with\n    +    the ‘size’ feature from a v2 server. If the server does not advertise\n    +    this feature (i.e., transfer.advertiseobjectinfo is set to false),\n    +    the client will return an error and exit.\n     \n         Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Helped-by: Christian Couder <chriscool@tuxfamily.org>\n    @@ fetch-object-info.c (new)\n     +\tswitch (version) {\n     +\tcase protocol_v2:\n     +\t\tif (!server_supports_v2(\"object-info\"))\n    -+\t\t\treturn -1;\n    -+\t\tif (unsorted_string_list_has_string(args->object_info_options, \"size\")\n    -+\t\t\t\t&& !server_supports_feature(\"object-info\", \"size\", 0))\n    -+\t\t\treturn -1;\n    ++\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n     +\t\tsend_object_info_request(fd_out, args);\n     +\t\tbreak;\n     +\tcase protocol_v1:\n    @@ transport.c\n      #include \"remote.h\"\n      #include \"connect.h\"\n      #include \"send-pack.h\"\n    -@@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n    - \tstruct ref *refs = NULL;\n    - \tstruct fetch_pack_args args;\n    - \tstruct ref *refs_tmp = NULL, **to_fetch_dup = NULL;\n    -+\tstruct ref *object_info_refs = NULL;\n    - \n    - \tmemset(&args, 0, sizeof(args));\n    - \targs.uploadpack = data->options.uploadpack;\n     @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \targs.server_options = transport->server_options;\n      \targs.negotiation_tips = data->options.negotiation_tips;\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n     +\tif (transport->smart_options\n     +\t\t&& transport->smart_options->object_info\n     +\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n    -+\t\tstruct ref *ref_itr = object_info_refs = alloc_ref(\"\");\n     +\t\tstruct packet_reader reader;\n     +\t\tstruct object_info_args obj_info_args = { 0 };\n     +\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n     +\t\tdata->version = discover_version(&reader);\n     +\t\ttransport->hash_algo = reader.hash_algo;\n     +\n    -+\t\tif (!fetch_object_info(data->version, &obj_info_args, &reader,\n    -+\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n    -+\t\t\tdata->fd[1])) {\n    -+\t\t\t/*\n    -+\t\t\t * If the code reaches here, fetch_object_info is successful and\n    -+\t\t\t * remote object info are retrieved from packets (i.e. without\n    -+\t\t\t * downloading the objects).\n    -+\t\t\t */\n    -+\t\t\tgoto cleanup;\n    -+\t\t}\n    ++\t\tret = fetch_object_info(data->version, &obj_info_args, &reader,\n    ++\t\t\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n    ++\t\t\t\t\tdata->fd[1]);\n    ++\t\tgoto cleanup;\n      \n     -\tif (!data->finished_handshake) {\n    -+\t\t/*\n    -+\t\t * If the code reaches here, it means we can't retrieve object info from\n    -+\t\t * packets, and we will fallback to downland the pack files.\n    -+\t\t * We set quiet and no_progress to be true, so that the internal call to\n    -+\t\t * fetch-pack is less verbose.\n    -+\t\t */\n    -+\t\targs.object_info_data = data->options.object_info_data;\n    -+\t\targs.quiet = 1;\n    -+\t\targs.no_progress = 1;\n    -+\n    -+\t\t/*\n    -+\t\t * Allocate memory for object info data according to oids.\n    -+\t\t * The actual results will be retrieved later from the downloaded\n    -+\t\t * pack files.\n    -+\t\t */\n    -+\t\tfor (size_t i = 0; i < transport->smart_options->object_info_oids->nr; i++) {\n    -+\t\t\tref_itr->old_oid = transport->smart_options->object_info_oids->oid[i];\n    -+\t\t\tref_itr->exact_oid = 1;\n    -+\t\t\tif (i == transport->smart_options->object_info_oids->nr - 1)\n    -+\t\t\t\t/* last element, no need to allocate to next */\n    -+\t\t\t\tref_itr->next = NULL;\n    -+\t\t\telse\n    -+\t\t\t\tref_itr->next = alloc_ref(\"\");\n    -+\n    -+\t\t\tref_itr = ref_itr->next;\n    -+\t\t}\n    -+\n    -+\t\ttransport->remote_refs = object_info_refs;\n    -+\n     +\t} else if (!data->finished_handshake) {\n      \t\tint i;\n      \t\tint must_list_refs = 0;\n      \t\tfor (i = 0; i < nr_heads; i++) {\n    -@@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n    - \t\t\t  &transport->pack_lockfiles, data->version);\n    - \n    - \tdata->finished_handshake = 0;\n    -+\n    -+\t/* Retrieve object info data from the downloaded pack files */\n    -+\tif (args.object_info) {\n    -+\t\tstruct ref *ref_cpy_reader = object_info_refs;\n    -+\t\tfor (int i = 0; ref_cpy_reader; i++) {\n    -+\t\t\toid_object_info_extended(the_repository, &ref_cpy_reader->old_oid,\n    -+\t\t\t\t&args.object_info_data[i], OBJECT_INFO_LOOKUP_REPLACE);\n    -+\t\t\tref_cpy_reader = ref_cpy_reader->next;\n    -+\t\t}\n    -+\t}\n    -+\n    - \tdata->options.self_contained_and_connected =\n    - \t\targs.self_contained_and_connected;\n    - \tdata->options.connectivity_checked = args.connectivity_checked;\n    -@@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n    - \t\tret = -1;\n    - \n    - cleanup:\n    -+\tfree_refs(object_info_refs);\n    - \tclose(data->fd[0]);\n    - \tif (data->fd[1] >= 0)\n    - \t\tclose(data->fd[1]);\n     \n      ## transport.h ##\n     @@\n    @@ transport.h: struct git_transport_options {\n      \tunsigned connectivity_checked:1;\n      \n     +\t/*\n    -+\t * Transport will attempt to pull only object-info. Fallbacks\n    -+\t * to pulling entire object if object-info is not supported.\n    ++\t * Transport will attempt to retrieve only object-info.\n    ++\t * If object-info is not supported, the operation will error and exit.\n     +\t */\n     +\tunsigned object_info : 1;\n     +\n6:  91689c0d0d ! 6:  7d1936591d cat-file: add remote-object-info to batch-command\n    @@ Commit message\n         Since the `info` command in cat-file --batch-command prints object info\n         for a given object, it is natural to add another command in cat-file\n         --batch-command to print object info for a given object from a remote.\n    +\n         Add `remote-object-info` to cat-file --batch-command.\n     \n    -    While `info` takes object ids one at a time, this creates overhead when\n    -    making requests to a server so `remote-object-info` instead can take\n    -    multiple object ids at once.\n    +    While `info` takes object ids one at a time, this creates\n    +    overhead when making requests to a server.So `remote-object-info`\n    +    instead can take multiple object ids at once.\n     \n         cat-file --batch-command is generally implemented in the following\n         manner:\n    @@ Documentation/git-cat-file.txt: info <object>::\n      \toutput of `--batch-check`.\n      \n     +remote-object-info <remote> <object>...::\n    -+\tPrint object info for object references `<object>` at specified <remote>\n    -+\twithout downloading objects from remote. If the object-info capability\n    -+\tis not supported by the server, the objects will be downloaded instead.\n    ++\tPrint object info for object references `<object>` at specified\n    ++\t`<remote>` without downloading objects from the remote.\n    ++\tError when the `object-info` capability is not supported by the server.\n     +\tError when no object references are provided.\n     +\tThis command may be combined with `--buffer`.\n     +\n    @@ builtin/cat-file.c: static void parse_cmd_info(struct batch_options *opt,\n     +\t\tdata->oid = object_info_oids.oid[i];\n     +\n     +\t\tif (remote_object_info[i].sizep) {\n    -+\t\t\tdata->size = *remote_object_info[i].sizep;\n    -+\t\t} else {\n     +\t\t\t/*\n    -+\t\t\t * When reaching here, it means remote-object-info can't retrieve\n    -+\t\t\t * information from server without downloading them, and the objects\n    -+\t\t\t * have been fetched to client already.\n    -+\t\t\t * Print the information using the logic for local objects.\n    ++\t\t\t * When reaching here, it means remote-object-info can retrieve\n    ++\t\t\t * information from server without downloading them.\n     +\t\t\t */\n    -+\t\t\tdata->skip_object_info = 0;\n    ++\t\t\tdata->size = *remote_object_info[i].sizep;\n    ++\t\t\topt->batch_mode = BATCH_MODE_INFO;\n    ++\t\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n     +\t\t}\n    -+\n    -+\t\topt->batch_mode = BATCH_MODE_INFO;\n    -+\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n    -+\n     +\t}\n     +\topt->use_remote_info = 0;\n     +\tdata->skip_object_info = 0;\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\n     +# Test --batch-command remote-object-info with 'git://' and\n     +# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n    -+\n    -+test_expect_success 'remote-object-info fallback git://: fetch objects to client' '\n    ++test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n     +\t(\n     +\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n     +\t\tset_transport_variables \"$daemon_parent\" &&\n    -+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n    -+\n    -+\t\t# Prove object is not on the client\n    -+\t\techo \"$hello_oid missing\" >expect &&\n    -+\t\techo \"$tree_oid missing\" >>expect &&\n    -+\t\techo \"$commit_oid missing\" >>expect &&\n    -+\t\techo \"$tag_oid missing\" >>expect &&\n     +\n    -+\t\t# These results prove remote-object-info can retrieve object info\n    -+\t\techo \"$hello_oid $hello_size\" >>expect &&\n    -+\t\techo \"$tree_oid $tree_size\" >>expect &&\n    -+\t\techo \"$commit_oid $commit_size\" >>expect &&\n    -+\t\techo \"$tag_oid $tag_size\" >>expect &&\n    -+\n    -+\t\t# These results are for the info command\n    -+\t\t# They prove objects are downloaded\n    -+\t\techo \"$hello_oid $hello_size\" >>expect &&\n    -+\t\techo \"$tree_oid $tree_size\" >>expect &&\n    -+\t\techo \"$commit_oid $commit_size\" >>expect &&\n    -+\t\techo \"$tag_oid $tag_size\" >>expect &&\n    -+\n    -+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n    -+\t\tinfo $hello_oid\n    -+\t\tinfo $tree_oid\n    -+\t\tinfo $commit_oid\n    -+\t\tinfo $tag_oid\n    ++\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n     +\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n    -+\t\tinfo $hello_oid\n    -+\t\tinfo $tree_oid\n    -+\t\tinfo $commit_oid\n    -+\t\tinfo $tag_oid\n     +\t\tEOF\n    ++\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n     +\n     +\t\t# revert server state back\n    -+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n    ++\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n     +\n    -+\t\ttest_cmp expect actual\n     +\t)\n     +'\n     +\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\n     +# Test --batch-command remote-object-info with 'file://' and\n     +# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n    -+\n    -+test_expect_success 'remote-object-info fallback file://: fetch objects to client' '\n    ++test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n     +\t(\n     +\t\tset_transport_variables \"server\" &&\n     +\t\tserver_path=\"$(pwd)/server\" &&\n     +\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n    -+\t\tcd file_client_empty &&\n     +\n    -+\t\t# Prove object is not on the client\n    -+\t\techo \"$hello_oid missing\" >expect &&\n    -+\t\techo \"$tree_oid missing\" >>expect &&\n    -+\t\techo \"$commit_oid missing\" >>expect &&\n    -+\t\techo \"$tag_oid missing\" >>expect &&\n    -+\n    -+\t\t# These results prove remote-object-info can retrieve object info\n    -+\t\techo \"$hello_oid $hello_size\" >>expect &&\n    -+\t\techo \"$tree_oid $tree_size\" >>expect &&\n    -+\t\techo \"$commit_oid $commit_size\" >>expect &&\n    -+\t\techo \"$tag_oid $tag_size\" >>expect &&\n    -+\n    -+\t\t# These results are for the info command\n    -+\t\t# They prove objects are downloaded\n    -+\t\techo \"$hello_oid $hello_size\" >>expect &&\n    -+\t\techo \"$tree_oid $tree_size\" >>expect &&\n    -+\t\techo \"$commit_oid $commit_size\" >>expect &&\n    -+\t\techo \"$tag_oid $tag_size\" >>expect &&\n    -+\n    -+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n    -+\t\tinfo $hello_oid\n    -+\t\tinfo $tree_oid\n    -+\t\tinfo $commit_oid\n    -+\t\tinfo $tag_oid\n    ++\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n     +\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n    -+\t\tinfo $hello_oid\n    -+\t\tinfo $tree_oid\n    -+\t\tinfo $commit_oid\n    -+\t\tinfo $tag_oid\n     +\t\tEOF\n    ++\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n     +\n     +\t\t# revert server state back\n    -+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true &&\n    -+\t\ttest_cmp expect actual\n    ++\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n     +\t)\n     +'\n     +\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t)\n     +'\n     +\n    -+test_expect_success 'remote-object-info fails on server with legacy protocol fallback' '\n    ++test_expect_success 'remote-object-info fails on server with legacy protocol' '\n     +\t(\n     +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t)\n     +'\n     +\n    -+test_expect_success 'remote-object-info fails on malformed OID fallback' '\n    ++test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n     +\t(\n     +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t)\n     +'\n     +\n    ++\n     +# Test --batch-command remote-object-info with 'http://' transport and\n     +# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n    -+\n    -+test_expect_success 'remote-object-info fallback http://: fetch objects to client' '\n    ++test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n     +\t(\n     +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n     +\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n    -+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n    -+\n    -+\t\t# Prove object is not on the client\n    -+\t\techo \"$hello_oid missing\" >expect &&\n    -+\t\techo \"$tree_oid missing\" >>expect &&\n    -+\t\techo \"$commit_oid missing\" >>expect &&\n    -+\t\techo \"$tag_oid missing\" >>expect &&\n     +\n    -+\t\t# These results prove remote-object-info can retrieve object info\n    -+\t\techo \"$hello_oid $hello_size\" >>expect &&\n    -+\t\techo \"$tree_oid $tree_size\" >>expect &&\n    -+\t\techo \"$commit_oid $commit_size\" >>expect &&\n    -+\t\techo \"$tag_oid $tag_size\" >>expect &&\n    -+\n    -+\t\t# These results are for the info command\n    -+\t\t# They prove objects are downloaded\n    -+\t\techo \"$hello_oid $hello_size\" >>expect &&\n    -+\t\techo \"$tree_oid $tree_size\" >>expect &&\n    -+\t\techo \"$commit_oid $commit_size\" >>expect &&\n    -+\t\techo \"$tag_oid $tag_size\" >>expect &&\n    -+\n    -+\t\tgit cat-file --batch-command >actual <<-EOF &&\n    -+\t\tinfo $hello_oid\n    -+\t\tinfo $tree_oid\n    -+\t\tinfo $commit_oid\n    -+\t\tinfo $tag_oid\n    ++\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n     +\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n    -+\t\tinfo $hello_oid\n    -+\t\tinfo $tree_oid\n    -+\t\tinfo $commit_oid\n    -+\t\tinfo $tag_oid\n     +\t\tEOF\n    ++\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n     +\n     +\t\t# revert server state back\n    -+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n    -+\n    -+\t\ttest_cmp expect actual\n    ++\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n     +\t)\n     +'\n     +\n-- \n2.47.0\n\nInformation Footer:\nbase-commit: 8f8d6eee531b3fa1a8ef14f169b0cb5035f7a772\nMerge Request: https://gitlab.com/gitlab-org/git/-/merge_requests/168\n"},{"id":"508014","messageId":"20241125053616.25170-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241125053616.25170-1-eric.peijian@gmail.com","subject":"[PATCH v7 1/6] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-25T05:36:11Z","receivedAt":"2024-11-25T05:36:39Z","isPatch":true,"body":"Some code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n fetch-pack.c       |  3 +--\n 2 files changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex bfdfb51c7c..5db55fabc4 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -673,12 +673,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -686,9 +684,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -714,7 +710,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -724,7 +719,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex fe1fb3c1b7..bb7ec96963 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1328,9 +1328,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (int i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n-- \n2.47.0\n\n"},{"id":"508015","messageId":"20241125053616.25170-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241125053616.25170-1-eric.peijian@gmail.com","subject":"[PATCH v7 2/6] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-25T05:36:12Z","receivedAt":"2024-11-25T05:36:40Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nRefactor write_fetch_command_and_capabilities() to a more\ngeneral-purpose function, write_command_and_capabilities(), enabling it\nto serve both fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162c\n(object-info: support for retrieving object info, 2021-04-20).\n\nFurthermore, write_command_and_capabilities() is moved to connect.c,\nmaking it accessible to additional commands in the future.\n\nTo move write_command_and_capabilities() to connect.c, we need to\nadjust how `advertise_sid` is managed. Previously,\nin fetch_pack.c, `advertise_sid` was a static variable, modified using\ngit_config_get_bool().\n\nIn connect.c, we now initialize `advertise_sid` at the beginning by\ndirectly using git_config_get_bool(). This change is safe because:\n\nIn the original fetch-pack.c code, there are only two places that\nwrite `advertise_sid` :\n1. In function do_fetch_pack:\n        if (!server_supports(\"session-id\"))\n                advertise_sid = 0;\n2. In function fetch_pack_config():\n        git_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, since do_fetch_pack() is only relevant for protocol v1, this\nassignment can be ignored in our refactor, as\nwrite_command_and_capabilities() is only used in protocol v2.\n\nAbout 2, git_config_get_bool() is from config.h and it is an out-of-box\ndependency of connect.c, so we can reuse it directly.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  8 ++++++++\n fetch-pack.c | 35 ++---------------------------------\n 3 files changed, 44 insertions(+), 33 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 58f53d8dcb..5dd544335c 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -688,6 +688,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\tgit_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (int i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t\tthe_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t\tthe_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n enum protocol {\n \tPROTO_LOCAL = 1,\n \tPROTO_FILE,\ndiff --git a/connect.h b/connect.h\nindex 1645126c17..8b56a68b62 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -1,6 +1,7 @@\n #ifndef CONNECT_H\n #define CONNECT_H\n \n+#include \"string-list.h\"\n #include \"protocol.h\"\n \n #define CONNECT_VERBOSE       (1u << 0)\n@@ -30,4 +31,11 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+/**\n+ * write_command_and_capabilities writes a command along with the requested\n+ * server capabilities/features into a request buffer.\n+ */\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex bb7ec96963..bcee4004a1 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1316,37 +1316,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (int i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1357,7 +1326,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2175,7 +2144,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.47.0\n\n"},{"id":"508016","messageId":"20241125053616.25170-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241125053616.25170-1-eric.peijian@gmail.com","subject":"[PATCH v7 3/6] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-25T05:36:13Z","receivedAt":"2024-11-25T05:36:41Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex bcee4004a1..eb4aface36 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1647,18 +1647,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.47.0\n\n"},{"id":"508017","messageId":"20241125053616.25170-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241125053616.25170-1-eric.peijian@gmail.com","subject":"[PATCH v7 4/6] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-25T05:36:14Z","receivedAt":"2024-11-25T05:36:42Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex d674764a25..c3d8098642 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -70,7 +70,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -78,6 +78,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.47.0\n\n"},{"id":"508018","messageId":"20241125053616.25170-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241125053616.25170-1-eric.peijian@gmail.com","subject":"[PATCH v7 5/6] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-25T05:36:15Z","receivedAt":"2024-11-25T05:36:43Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\"\n\nThis commit introduces client functions to interact with the server.\n\nCurrently, the client supports requesting a list of object IDs with\nthe ‘size’ feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client will return an error and exit.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Makefile            |  1 +\n fetch-object-info.c | 92 +++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h | 18 +++++++++\n fetch-pack.c        |  3 ++\n fetch-pack.h        |  2 +\n transport-helper.c  | 11 +++++-\n transport.c         | 28 +++++++++++++-\n transport.h         | 11 ++++++\n 8 files changed, 163 insertions(+), 3 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n\ndiff --git a/Makefile b/Makefile\nindex d06c9a8ffa..beca828963 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1024,6 +1024,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..2aa9f2b70d\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,92 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"object-store-ll.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/**\n+ * send_object_info_request sends git-cat-file object-info command and its\n+ * arguments into the request buffer.\n+ */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids) {\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\t}\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+/**\n+ * fetch_object_info sends git-cat-file object-info command into the request buf\n+ * and read the results from packets.\n+ */\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"wrong protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (unsorted_string_list_has_string(args->object_info_options, reader->line)) {\n+\t\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\t\tsize_index = i;\n+\t\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\n+\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..ce1a05dc96\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,18 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"object-store-ll.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex eb4aface36..6e5d7df2dc 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1653,6 +1653,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex b5c579cdae..cf7cedf161 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\ndiff --git a/transport-helper.c b/transport-helper.c\nindex bc27653cde..bf0a1877c7 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -711,8 +711,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file\n+\t * remote-object-info require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -728,6 +728,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 47fda6a773..746ec19ddc 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -444,8 +445,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options\n+\t\t&& transport->smart_options->object_info\n+\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct packet_reader reader;\n+\t\tstruct object_info_args obj_info_args = { 0 };\n+\n+\t\tobj_info_args.server_options = transport->server_options;\n+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n+\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n+\n+\t\tconnect_setup(transport, 0);\n+\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\t\tdata->version = discover_version(&reader);\n+\t\ttransport->hash_algo = reader.hash_algo;\n+\n+\t\tret = fetch_object_info(data->version, &obj_info_args, &reader,\n+\t\t\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n+\t\t\t\t\tdata->fd[1]);\n+\t\tgoto cleanup;\n \n-\tif (!data->finished_handshake) {\n+\t} else if (!data->finished_handshake) {\n \t\tint i;\n \t\tint must_list_refs = 0;\n \t\tfor (i = 0; i < nr_heads; i++) {\ndiff --git a/transport.h b/transport.h\nindex 44100fa9b7..e61e931863 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to retrieve only object-info.\n+\t * If object-info is not supported, the operation will error and exit.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.47.0\n\n"},{"id":"508019","messageId":"20241125053616.25170-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241125053616.25170-1-eric.peijian@gmail.com","subject":"[PATCH v7 6/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-11-25T05:36:16Z","receivedAt":"2024-11-25T05:36:44Z","isPatch":true,"body":"Since the `info` command in cat-file --batch-command prints object info\nfor a given object, it is natural to add another command in cat-file\n--batch-command to print object info for a given object from a remote.\n\nAdd `remote-object-info` to cat-file --batch-command.\n\nWhile `info` takes object ids one at a time, this creates\noverhead when making requests to a server.So `remote-object-info`\ninstead can take multiple object ids at once.\n\ncat-file --batch-command is generally implemented in the following\nmanner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loop through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication overhead\nin the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Documentation/git-cat-file.txt         |  24 +-\n builtin/cat-file.c                     |  99 ++++\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 652 +++++++++++++++++++++++++\n 7 files changed, 802 insertions(+), 16 deletions(-)\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex d5890ae368..6a2f9fd752 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -149,6 +149,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tError when the `object-info` capability is not supported by the server.\n+\tError when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +297,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +306,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +323,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current the default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +408,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will error\n+and exit when they are in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 5db55fabc4..ad17be69b0 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -24,6 +24,9 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -42,9 +45,12 @@ struct batch_options {\n \tchar input_delim;\n \tchar output_delim;\n \tconst char *format;\n+\tint use_remote_info;\n };\n \n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -576,6 +582,59 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT\n+\t*/\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\n+\t\t/* 'objectsize' is the only option currently supported */\n+\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -667,6 +726,45 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\n+\tchar *line_to_split = xstrdup_or_null(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\n+\topt->use_remote_info = 1;\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\n+\t\tdata->oid = object_info_oids.oid[i];\n+\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\t\t}\n+\t}\n+\topt->use_remote_info = 0;\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -698,6 +796,7 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1},\n \t{ \"info\", parse_cmd_info, 1},\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1},\n \t{ \"flush\", NULL, 0},\n };\n \ndiff --git a/object-file.c b/object-file.c\nindex b1a3463852..181cde98e1 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -3132,3 +3132,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex 53b8e693b1..611e2ca708 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -548,4 +548,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(each_packed_object_fn, void *,\n \t\t\t   enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..9fb20be308\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related tests.\n+\n+# Print a string without a trailing newline\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replaces them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string removing any leading spaces.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex d36cd7c086..d8a851c427 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     oid=$2\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..0e3044d8d6\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,652 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n-- \n2.47.0\n\n"},{"id":"508061","messageId":"Z0RIpRh0QlPWk3F8@pks.im","threadId":"61695","inReplyTo":"20241125053616.25170-2-eric.peijian@gmail.com","subject":"Re: [PATCH v7 1/6] cat-file: add declaration of variable i inside its for loop","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-11-25T09:51:33Z","receivedAt":"2024-11-25T09:51:50Z","isPatch":true,"body":"On Mon, Nov 25, 2024 at 12:36:11AM -0500, Eric Ju wrote:\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index fe1fb3c1b7..bb7ec96963 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1328,9 +1328,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>  \tif (advertise_sid && server_supports_v2(\"session-id\"))\n>  \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n>  \tif (server_options && server_options->nr) {\n> -\t\tint i;\n>  \t\tensure_server_supports_v2(\"server-option\");\n> -\t\tfor (i = 0; i < server_options->nr; i++)\n> +\t\tfor (int i = 0; i < server_options->nr; i++)\n>  \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n>  \t\t\t\t\t server_options->items[i].string);\n>  \t}\n\nIt's somewhat curious that you change the type to `size_t` while at it\nin other spots, but not here. Doubly so because `server_options` is a\n`struct string_list`, and `string_list::nr` is of type `size_t`.\n\nPatrick\n"},{"id":"508062","messageId":"Z0RIqUAoEob8lGfM@pks.im","threadId":"61695","inReplyTo":"20241125053616.25170-3-eric.peijian@gmail.com","subject":"Re: [PATCH v7 2/6] fetch-pack: refactor packet writing","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-11-25T09:51:37Z","receivedAt":"2024-11-25T09:51:52Z","isPatch":true,"body":"On Mon, Nov 25, 2024 at 12:36:12AM -0500, Eric Ju wrote:\n> diff --git a/connect.h b/connect.h\n> index 1645126c17..8b56a68b62 100644\n> --- a/connect.h\n> +++ b/connect.h\n> @@ -1,6 +1,7 @@\n>  #ifndef CONNECT_H\n>  #define CONNECT_H\n>  \n> +#include \"string-list.h\"\n>  #include \"protocol.h\"\n>  \n>  #define CONNECT_VERBOSE       (1u << 0)\n\nInstead of including this header, you can add a forward declaration of\n`struct string_list`. This is mostly done to keep compilation times at\nbay by not including too many headers.\n\n> @@ -30,4 +31,11 @@ void check_stateless_delimiter(int stateless_rpc,\n>  \t\t\t       struct packet_reader *reader,\n>  \t\t\t       const char *error);\n>  \n> +/**\n> + * write_command_and_capabilities writes a command along with the requested\n\nNit: we don't typically use Go-style comments where the comment starts\nwith the name of what's being documented.\n\nPatrick\n"},{"id":"508063","messageId":"Z0RIrKwUnaWWm_gJ@pks.im","threadId":"61695","inReplyTo":"20241125053616.25170-6-eric.peijian@gmail.com","subject":"Re: [PATCH v7 5/6] transport: add client support for object-info","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-11-25T09:51:40Z","receivedAt":"2024-11-25T09:51:55Z","isPatch":true,"body":"On Mon, Nov 25, 2024 at 12:36:15AM -0500, Eric Ju wrote:\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> new file mode 100644\n> index 0000000000..2aa9f2b70d\n> --- /dev/null\n> +++ b/fetch-object-info.c\n> @@ -0,0 +1,92 @@\n> +#include \"git-compat-util.h\"\n> +#include \"gettext.h\"\n> +#include \"hex.h\"\n> +#include \"pkt-line.h\"\n> +#include \"connect.h\"\n> +#include \"oid-array.h\"\n> +#include \"object-store-ll.h\"\n> +#include \"fetch-object-info.h\"\n> +#include \"string-list.h\"\n> +\n> +/**\n> + * send_object_info_request sends git-cat-file object-info command and its\n> + * arguments into the request buffer.\n> + */\n> +static void send_object_info_request(const int fd_out, struct object_info_args *args)\n> +{\n> +\tstruct strbuf req_buf = STRBUF_INIT;\n> +\n> +\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n> +\n> +\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n> +\t\tpacket_buf_write(&req_buf, \"size\");\n\nDo we have a document somewhere that spells out the wire format that\nclient- and server-side talk with each other? If so it would be nice to\npoint it out in the commit message so that I know where to look, and\notherwise we should document it. Without such a doc it's hard to figure\nout whether this is correct.\n\n> +\tif (args->oids) {\n> +\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n> +\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n> +\t}\n\nNit: needless curly braces.\n\n> +\tpacket_buf_flush(&req_buf);\n> +\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n> +\t\tdie_errno(_(\"unable to write request to remote\"));\n\nSo we write the whole request into `req_buf` first before sending it to\nthe remote. Isn't that quite inefficient memory wise? In other words,\ncouldn't we instead stream the request line by line or at least in\nbatches to the file descriptor?\n\n> +\tstrbuf_release(&req_buf);\n> +}\n> +\n> +/**\n\nNit: s|/**|/*|\n\n> + * fetch_object_info sends git-cat-file object-info command into the request buf\n> + * and read the results from packets.\n> + */\n> +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n> +\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n> +\t\t      const int stateless_rpc, const int fd_out)\n> +{\n> +\tint size_index = -1;\n> +\n> +\tswitch (version) {\n> +\tcase protocol_v2:\n> +\t\tif (!server_supports_v2(\"object-info\"))\n> +\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n> +\t\tsend_object_info_request(fd_out, args);\n> +\t\tbreak;\n> +\tcase protocol_v1:\n> +\tcase protocol_v0:\n> +\t\tdie(_(\"wrong protocol version. expected v2\"));\n> +\tcase protocol_unknown_version:\n> +\t\tBUG(\"unknown protocol version\");\n> +\t}\n> +\n> +\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n> +\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n> +\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n> +\t\t\treturn -1;\n> +\t\t}\n> +\t\tif (unsorted_string_list_has_string(args->object_info_options, reader->line)) {\n\nHum. Does this result in quadratic runtime behaviour?\n\n> +\t\t\tif (!strcmp(reader->line, \"size\")) {\n> +\t\t\t\tsize_index = i;\n> +\t\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n> +\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n\nThis might be a bit more future proof in case the `sizep` type were ever\nto change:\n\n\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n\nIt also allows you to skip double-checking whether you picked the\ncorrect type. In fact, the type is actually an `unsigned long`, which\nis confusing but ultimately does not make much of a difference because\nit should have the same size.\n\n> +\t\t\t}\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n> +\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n> +\n> +\t\tstring_list_split(&object_info_values, reader->line, ' ', -1);\n> +\t\tif (0 <= size_index) {\n> +\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n> +\t\t\t\tdie(\"object-info: not our ref %s\",\n> +\t\t\t\t\tobject_info_values.items[0].string);\n> +\n> +\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n\nWe're completely missing error handling for strtoul(3p) here. That\nfunction is also discouraged nowadays because error handling is hard to\ndo correct. We have `strtoul_ui()` and friends, but don't have a variant\nyet that know to return an `unsigned long`. We might backfill that\nomission and then use it instead.\n\n> diff --git a/transport-helper.c b/transport-helper.c\n> index bc27653cde..bf0a1877c7 100644\n> --- a/transport-helper.c\n> +++ b/transport-helper.c\n> @@ -728,6 +728,13 @@ static int fetch_refs(struct transport *transport,\n>  \t\tfree_refs(dummy);\n>  \t}\n>  \n> +\t/* fail the command explicitly to avoid further commands input. */\n> +\tif (transport->smart_options->object_info)\n> +\t\tdie(_(\"remote-object-info requires protocol v2\"));\n\nThe code path that checks for for protocol v2 with \"--negotiate-only\"\nknows to warn and return an error. Should we do the same here?\n\n> diff --git a/transport.c b/transport.c\n> index 47fda6a773..746ec19ddc 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -9,6 +9,7 @@\n>  #include \"hook.h\"\n>  #include \"pkt-line.h\"\n>  #include \"fetch-pack.h\"\n> +#include \"fetch-object-info.h\"\n>  #include \"remote.h\"\n>  #include \"connect.h\"\n>  #include \"send-pack.h\"\n> @@ -444,8 +445,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \targs.server_options = transport->server_options;\n>  \targs.negotiation_tips = data->options.negotiation_tips;\n>  \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n> +\targs.object_info = transport->smart_options->object_info;\n> +\n> +\tif (transport->smart_options\n> +\t\t&& transport->smart_options->object_info\n> +\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n\nFormatting is wrong here:\n\n\tif (transport->smart_options &&\n\t    transport->smart_options->object_info &&\n\t    transport->smart_options->object_info_oids->nr > 0) {\n\nPatrick\n"},{"id":"508064","messageId":"Z0RIrw2PszaY2Way@pks.im","threadId":"61695","inReplyTo":"20241125053616.25170-7-eric.peijian@gmail.com","subject":"Re: [PATCH v7 6/6] cat-file: add remote-object-info to batch-command","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-11-25T09:51:43Z","receivedAt":"2024-11-25T09:51:58Z","isPatch":true,"body":"On Mon, Nov 25, 2024 at 12:36:16AM -0500, Eric Ju wrote:\n> diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\n> index d5890ae368..6a2f9fd752 100644\n> --- a/Documentation/git-cat-file.txt\n> +++ b/Documentation/git-cat-file.txt\n> @@ -314,7 +323,10 @@ newline. The available atoms are:\n>  \tline) are output in place of the `%(rest)` atom.\n>  \n>  If no format is specified, the default format is `%(objectname)\n> -%(objecttype) %(objectsize)`.\n> +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> +WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n> +DO NOT RELY on the current the default format to stay the same!!!\n\nIs this stale or do we still not support `%(objecttype)`? I thought we\nwanted to support that, as well, so that we don't have to change the\ndefault format.\n\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 5db55fabc4..ad17be69b0 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -576,6 +582,59 @@ static void batch_one_object(const char *obj_name,\n>  \tobject_context_release(&ctx);\n>  }\n>  \n> +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> +{\n> +\tint retval = 0;\n> +\tstruct remote *remote = NULL;\n> +\tstruct object_id oid;\n> +\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> +\tstatic struct transport *gtransport;\n> +\n> +\t/*\n> +\t * Change the format to \"%(objectname) %(objectsize)\" when\n> +\t * remote-object-info command is used. Once we start supporting objecttype\n> +\t * the default format should change to DEFAULT_FORMAT\n> +\t*/\n> +\tif (!opt->format)\n> +\t\topt->format = \"%(objectname) %(objectsize)\";\n\nSeems like it isn't stale. Hum.\n\n> +\tremote = remote_get(argv[0]);\n> +\tif (!remote)\n> +\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n> +\n> +\toid_array_clear(&object_info_oids);\n> +\tfor (size_t i = 1; i < argc; i++) {\n> +\t\tif (get_oid_hex(argv[i], &oid))\n> +\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n> +\t\toid_array_append(&object_info_oids, &oid);\n> +\t}\n\nShould we return an error when the user didn't pass any object IDs?\n\n> @@ -667,6 +726,45 @@ static void parse_cmd_info(struct batch_options *opt,\n>  \tbatch_one_object(line, output, opt, data);\n>  }\n>  \n> +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> +\t\t\t\t\t const char *line, struct strbuf *output,\n> +\t\t\t\t\t struct expand_data *data)\n> +{\n> +\tint count;\n> +\tconst char **argv;\n> +\n> +\tchar *line_to_split = xstrdup_or_null(line);\n> +\tcount = split_cmdline(line_to_split, &argv);\n> +\tif (get_remote_info(opt, count, argv))\n> +\t\tgoto cleanup;\n> +\n> +\topt->use_remote_info = 1;\n> +\tdata->skip_object_info = 1;\n> +\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n> +\n\nNit: empty newline at the start of a block.\n\n> diff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\n> new file mode 100644\n> index 0000000000..9fb20be308\n> --- /dev/null\n> +++ b/t/lib-cat-file.sh\n\nI think it would make sense to split the introduction of\n\"lib-cat-file.sh\" into a separate commit.\n\nPatrick\n"},{"id":"508504","messageId":"CAN2LT1ASdzLdm4Mt0aKe8kAo05uJFY4o+wJkvVt82o7i56WAzw@mail.gmail.com","threadId":"61695","inReplyTo":"Z0RIrKwUnaWWm_gJ@pks.im","subject":"Re: [PATCH v7 5/6] transport: add client support for object-info","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-03T03:15:06Z","receivedAt":"2024-12-03T03:15:18Z","isPatch":true,"body":"Hi Patrick,\n\nThank you for your feedback. I have a few questions. I agree with the\ncomments I didn’t specifically respond to and will address them in v8.\n\nEric.\n\nOn Mon, Nov 25, 2024 at 4:51 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Mon, Nov 25, 2024 at 12:36:15AM -0500, Eric Ju wrote:\n> > diff --git a/fetch-object-info.c b/fetch-object-info.c\n> > new file mode 100644\n> > index 0000000000..2aa9f2b70d\n> > --- /dev/null\n> > +++ b/fetch-object-info.c\n> > @@ -0,0 +1,92 @@\n> > +#include \"git-compat-util.h\"\n> > +#include \"gettext.h\"\n> > +#include \"hex.h\"\n> > +#include \"pkt-line.h\"\n> > +#include \"connect.h\"\n> > +#include \"oid-array.h\"\n> > +#include \"object-store-ll.h\"\n> > +#include \"fetch-object-info.h\"\n> > +#include \"string-list.h\"\n> > +\n> > +/**\n> > + * send_object_info_request sends git-cat-file object-info command and its\n> > + * arguments into the request buffer.\n> > + */\n> > +static void send_object_info_request(const int fd_out, struct object_info_args *args)\n> > +{\n> > +     struct strbuf req_buf = STRBUF_INIT;\n> > +\n> > +     write_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n> > +\n> > +     if (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n> > +             packet_buf_write(&req_buf, \"size\");\n>\n> Do we have a document somewhere that spells out the wire format that\n> client- and server-side talk with each other? If so it would be nice to\n> point it out in the commit message so that I know where to look, and\n> otherwise we should document it. Without such a doc it's hard to figure\n> out whether this is correct.\n>\n\nThank you. Is this what you are looking for?\nhttps://git-scm.com/docs/protocol-v2#_object_info\nIf so, I will put it in the commit message in v8.\n\n> > +     if (args->oids) {\n> > +             for (size_t i = 0; i < args->oids->nr; i++)\n> > +                     packet_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n> > +     }\n>\n> Nit: needless curly braces.\n>\n> > +     packet_buf_flush(&req_buf);\n> > +     if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n> > +             die_errno(_(\"unable to write request to remote\"));\n>\n> So we write the whole request into `req_buf` first before sending it to\n> the remote. Isn't that quite inefficient memory wise? In other words,\n> couldn't we instead stream the request line by line or at least in\n> batches to the file descriptor?\n>\n\nThank you.\n\nI followed the `send_fetch_request()` logic in `fetch-pack.c`.  I’m\nnot entirely clear on how to “stream the request line by line or in\nbatches.” Could you point me to an example or reference that\ndemonstrates this approach?\n\n> > +     strbuf_release(&req_buf);\n> > +}\n> > +\n> > +/**\n>\n> Nit: s|/**|/*|\n>\n> > + * fetch_object_info sends git-cat-file object-info command into the request buf\n> > + * and read the results from packets.\n> > + */\n> > +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n> > +                   struct packet_reader *reader, struct object_info *object_info_data,\n> > +                   const int stateless_rpc, const int fd_out)\n> > +{\n> > +     int size_index = -1;\n> > +\n> > +     switch (version) {\n> > +     case protocol_v2:\n> > +             if (!server_supports_v2(\"object-info\"))\n> > +                     die(_(\"object-info capability is not enabled on the server\"));\n> > +             send_object_info_request(fd_out, args);\n> > +             break;\n> > +     case protocol_v1:\n> > +     case protocol_v0:\n> > +             die(_(\"wrong protocol version. expected v2\"));\n> > +     case protocol_unknown_version:\n> > +             BUG(\"unknown protocol version\");\n> > +     }\n> > +\n> > +     for (size_t i = 0; i < args->object_info_options->nr; i++) {\n> > +             if (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n> > +                     check_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n> > +                     return -1;\n> > +             }\n> > +             if (unsorted_string_list_has_string(args->object_info_options, reader->line)) {\n>\n> Hum. Does this result in quadratic runtime behaviour?\n>\n> > +                     if (!strcmp(reader->line, \"size\")) {\n> > +                             size_index = i;\n> > +                             for (size_t j = 0; j < args->oids->nr; j++)\n> > +                                     object_info_data[j].sizep = xcalloc(1, sizeof(long));\n>\n> This might be a bit more future proof in case the `sizep` type were ever\n> to change:\n>\n>         object_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n>\n> It also allows you to skip double-checking whether you picked the\n> correct type. In fact, the type is actually an `unsigned long`, which\n> is confusing but ultimately does not make much of a difference because\n> it should have the same size.\n>\n> > +                     }\n> > +                     continue;\n> > +             }\n> > +             return -1;\n> > +     }\n> > +\n> > +     for (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n> > +             struct string_list object_info_values = STRING_LIST_INIT_DUP;\n> > +\n> > +             string_list_split(&object_info_values, reader->line, ' ', -1);\n> > +             if (0 <= size_index) {\n> > +                     if (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n> > +                             die(\"object-info: not our ref %s\",\n> > +                                     object_info_values.items[0].string);\n> > +\n> > +                     *object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n>\n> We're completely missing error handling for strtoul(3p) here. That\n> function is also discouraged nowadays because error handling is hard to\n> do correct. We have `strtoul_ui()` and friends, but don't have a variant\n> yet that know to return an `unsigned long`. We might backfill that\n> omission and then use it instead.\n>\n> > diff --git a/transport-helper.c b/transport-helper.c\n> > index bc27653cde..bf0a1877c7 100644\n> > --- a/transport-helper.c\n> > +++ b/transport-helper.c\n> > @@ -728,6 +728,13 @@ static int fetch_refs(struct transport *transport,\n> >               free_refs(dummy);\n> >       }\n> >\n> > +     /* fail the command explicitly to avoid further commands input. */\n> > +     if (transport->smart_options->object_info)\n> > +             die(_(\"remote-object-info requires protocol v2\"));\n>\n> The code path that checks for for protocol v2 with \"--negotiate-only\"\n> knows to warn and return an error. Should we do the same here?\n>\n\nThank you.\n\nIf we follow \"warn and return an error\" as \"--negotiate-only\", we will\nend up with \"warn and wait\". This was the question I was asking in the\nprevious patches:\n\n     In the current implementation, if a user puts\n`remote-object-info` in protocol v1,\n     `cat-file --batch-command` will die. Which way do we prefer?\n\"error and exit (i.e. die)\"\n     or \"warn and wait for new command\".\n\nAnd we decided to take the path of \"error and exit (i.e. die)\", the\nreason was explained at\nhttps://lore.kernel.org/git/CAN2LT1Cmsw3RB1kbRBvoeLs8WaQeZWqrG96EQfMkMe_jdKaO4g@mail.gmail.com/:\n\n    Our primary use case is to use git cat-file remote-object-info in\na promisor remote setup to retrieve metadata\n    about an object stored in the promisor remote, without fetching it\nback to the local repository.\n    This approach helps conserve disk space. I don’t believe other\ncommands can achieve this functionality,\n    particularly without requiring the object to be downloaded.\n\n\n> > diff --git a/transport.c b/transport.c\n> > index 47fda6a773..746ec19ddc 100644\n> > --- a/transport.c\n> > +++ b/transport.c\n> > @@ -9,6 +9,7 @@\n> >  #include \"hook.h\"\n> >  #include \"pkt-line.h\"\n> >  #include \"fetch-pack.h\"\n> > +#include \"fetch-object-info.h\"\n> >  #include \"remote.h\"\n> >  #include \"connect.h\"\n> >  #include \"send-pack.h\"\n> > @@ -444,8 +445,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >       args.server_options = transport->server_options;\n> >       args.negotiation_tips = data->options.negotiation_tips;\n> >       args.reject_shallow_remote = transport->smart_options->reject_shallow;\n> > +     args.object_info = transport->smart_options->object_info;\n> > +\n> > +     if (transport->smart_options\n> > +             && transport->smart_options->object_info\n> > +             && transport->smart_options->object_info_oids->nr > 0) {\n>\n> Formatting is wrong here:\n>\n>         if (transport->smart_options &&\n>             transport->smart_options->object_info &&\n>             transport->smart_options->object_info_oids->nr > 0) {\n>\n> Patrick\n"},{"id":"508552","messageId":"CAN2LT1DLpFdLPAJ+ver5dgxmxpdp1nDYfK+OVTkF2twVLVkeyw@mail.gmail.com","threadId":"61695","inReplyTo":"Z0RIqUAoEob8lGfM@pks.im","subject":"Re: [PATCH v7 2/6] fetch-pack: refactor packet writing","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-03T19:09:36Z","receivedAt":"2024-12-03T19:09:47Z","isPatch":true,"body":"Thank you, Patrick.\n\nYour comments make perfect sense to me, and I will revise them\naccordingly to v8.\n\n\nOn Mon, Nov 25, 2024 at 4:51 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Mon, Nov 25, 2024 at 12:36:12AM -0500, Eric Ju wrote:\n> > diff --git a/connect.h b/connect.h\n> > index 1645126c17..8b56a68b62 100644\n> > --- a/connect.h\n> > +++ b/connect.h\n> > @@ -1,6 +1,7 @@\n> >  #ifndef CONNECT_H\n> >  #define CONNECT_H\n> >\n> > +#include \"string-list.h\"\n> >  #include \"protocol.h\"\n> >\n> >  #define CONNECT_VERBOSE       (1u << 0)\n>\n> Instead of including this header, you can add a forward declaration of\n> `struct string_list`. This is mostly done to keep compilation times at\n> bay by not including too many headers.\n>\n> > @@ -30,4 +31,11 @@ void check_stateless_delimiter(int stateless_rpc,\n> >                              struct packet_reader *reader,\n> >                              const char *error);\n> >\n> > +/**\n> > + * write_command_and_capabilities writes a command along with the requested\n>\n> Nit: we don't typically use Go-style comments where the comment starts\n> with the name of what's being documented.\n>\n> Patrick\n"},{"id":"508553","messageId":"CAN2LT1C6mZrhWiK8x27yOPjRt=v8f_jnYwc6g_LtxhVGy3bQaQ@mail.gmail.com","threadId":"61695","inReplyTo":"Z0RIrw2PszaY2Way@pks.im","subject":"Re: [PATCH v7 6/6] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-03T19:23:01Z","receivedAt":"2024-12-03T19:23:12Z","isPatch":true,"body":"On Mon, Nov 25, 2024 at 4:51 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Mon, Nov 25, 2024 at 12:36:16AM -0500, Eric Ju wrote:\n> > diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\n> > index d5890ae368..6a2f9fd752 100644\n> > --- a/Documentation/git-cat-file.txt\n> > +++ b/Documentation/git-cat-file.txt\n> > @@ -314,7 +323,10 @@ newline. The available atoms are:\n> >       line) are output in place of the `%(rest)` atom.\n> >\n> >  If no format is specified, the default format is `%(objectname)\n> > -%(objecttype) %(objectsize)`.\n> > +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> > +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> > +WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n> > +DO NOT RELY on the current the default format to stay the same!!!\n>\n> Is this stale or do we still not support `%(objecttype)`? I thought we\n> wanted to support that, as well, so that we don't have to change the\n> default format.\n>\n\nPlease see my next reply.\n\n> > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > index 5db55fabc4..ad17be69b0 100644\n> > --- a/builtin/cat-file.c\n> > +++ b/builtin/cat-file.c\n> > @@ -576,6 +582,59 @@ static void batch_one_object(const char *obj_name,\n> >       object_context_release(&ctx);\n> >  }\n> >\n> > +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> > +{\n> > +     int retval = 0;\n> > +     struct remote *remote = NULL;\n> > +     struct object_id oid;\n> > +     struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> > +     static struct transport *gtransport;\n> > +\n> > +     /*\n> > +      * Change the format to \"%(objectname) %(objectsize)\" when\n> > +      * remote-object-info command is used. Once we start supporting objecttype\n> > +      * the default format should change to DEFAULT_FORMAT\n> > +     */\n> > +     if (!opt->format)\n> > +             opt->format = \"%(objectname) %(objectsize)\";\n>\n> Seems like it isn't stale. Hum.\n>\n\nNo, this isn’t stale. As I mentioned in my response to Junio in\nhttps://lore.kernel.org/git/CAN2LT1Cmsw3RB1kbRBvoeLs8WaQeZWqrG96EQfMkMe_jdKaO4g@mail.gmail.com/,\nadding type support is planned for the next patch series. Based on the\ndocumentation at https://git-scm.com/docs/protocol-v2#_object_info, it\nseems type isn’t yet supported on the server side either. My plan is\nto implement the logic for both server and client in the next series.\n\nUnless the reviewers feel strongly that this must be included now, I’d\nprefer to stick to the original plan.\n\n> > +     remote = remote_get(argv[0]);\n> > +     if (!remote)\n> > +             die(_(\"must supply valid remote when using remote-object-info\"));\n> > +\n> > +     oid_array_clear(&object_info_oids);\n> > +     for (size_t i = 1; i < argc; i++) {\n> > +             if (get_oid_hex(argv[i], &oid))\n> > +                     die(_(\"Not a valid object name %s\"), argv[i]);\n> > +             oid_array_append(&object_info_oids, &oid);\n> > +     }\n>\n> Should we return an error when the user didn't pass any object IDs?\n>\n\nThank you. Revising in v8 and also adding a new test case to cover it.\n\n> > @@ -667,6 +726,45 @@ static void parse_cmd_info(struct batch_options *opt,\n> >       batch_one_object(line, output, opt, data);\n> >  }\n> >\n> > +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> > +                                      const char *line, struct strbuf *output,\n> > +                                      struct expand_data *data)\n> > +{\n> > +     int count;\n> > +     const char **argv;\n> > +\n> > +     char *line_to_split = xstrdup_or_null(line);\n> > +     count = split_cmdline(line_to_split, &argv);\n> > +     if (get_remote_info(opt, count, argv))\n> > +             goto cleanup;\n> > +\n> > +     opt->use_remote_info = 1;\n> > +     data->skip_object_info = 1;\n> > +     for (size_t i = 0; i < object_info_oids.nr; i++) {\n> > +\n>\n> Nit: empty newline at the start of a block.\n>\n\nThank you. Fixing in v8.\n\n> > diff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\n> > new file mode 100644\n> > index 0000000000..9fb20be308\n> > --- /dev/null\n> > +++ b/t/lib-cat-file.sh\n>\n> I think it would make sense to split the introduction of\n> \"lib-cat-file.sh\" into a separate commit.\n>\n\nThank you. Will split it into a separate commit in v8.\n\n> Patrick\n"},{"id":"508554","messageId":"CAN2LT1Ao=oV3Ufv=-2Eiq2xj+YfEF8-Dw8SoANdzOx1FhJua0g@mail.gmail.com","threadId":"61695","inReplyTo":"Z0RIpRh0QlPWk3F8@pks.im","subject":"Re: [PATCH v7 1/6] cat-file: add declaration of variable i inside its for loop","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-03T19:26:22Z","receivedAt":"2024-12-03T19:26:34Z","isPatch":true,"body":"On Mon, Nov 25, 2024 at 4:51 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Mon, Nov 25, 2024 at 12:36:11AM -0500, Eric Ju wrote:\n> > diff --git a/fetch-pack.c b/fetch-pack.c\n> > index fe1fb3c1b7..bb7ec96963 100644\n> > --- a/fetch-pack.c\n> > +++ b/fetch-pack.c\n> > @@ -1328,9 +1328,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> >       if (advertise_sid && server_supports_v2(\"session-id\"))\n> >               packet_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n> >       if (server_options && server_options->nr) {\n> > -             int i;\n> >               ensure_server_supports_v2(\"server-option\");\n> > -             for (i = 0; i < server_options->nr; i++)\n> > +             for (int i = 0; i < server_options->nr; i++)\n> >                       packet_buf_write(req_buf, \"server-option=%s\",\n> >                                        server_options->items[i].string);\n> >       }\n>\n> It's somewhat curious that you change the type to `size_t` while at it\n> in other spots, but not here. Doubly so because `server_options` is a\n> `struct string_list`, and `string_list::nr` is of type `size_t`.\n>\n> Patrick\n\nThank you.\n\nYes, I missed that. Will correct it in v8.\n"},{"id":"508664","messageId":"Z1F3STEa_bsE82Cc@pks.im","threadId":"61695","inReplyTo":"CAN2LT1C6mZrhWiK8x27yOPjRt=v8f_jnYwc6g_LtxhVGy3bQaQ@mail.gmail.com","subject":"Re: [PATCH v7 6/6] cat-file: add remote-object-info to batch-command","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-12-05T09:50:01Z","receivedAt":"2024-12-05T09:50:20Z","isPatch":true,"body":"On Tue, Dec 03, 2024 at 02:23:01PM -0500, Peijian Ju wrote:\n> On Mon, Nov 25, 2024 at 4:51 AM Patrick Steinhardt <ps@pks.im> wrote:\n> > On Mon, Nov 25, 2024 at 12:36:16AM -0500, Eric Ju wrote:\n> > > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > > index 5db55fabc4..ad17be69b0 100644\n> > > --- a/builtin/cat-file.c\n> > > +++ b/builtin/cat-file.c\n> > > @@ -576,6 +582,59 @@ static void batch_one_object(const char *obj_name,\n> > >       object_context_release(&ctx);\n> > >  }\n> > >\n> > > +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> > > +{\n> > > +     int retval = 0;\n> > > +     struct remote *remote = NULL;\n> > > +     struct object_id oid;\n> > > +     struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> > > +     static struct transport *gtransport;\n> > > +\n> > > +     /*\n> > > +      * Change the format to \"%(objectname) %(objectsize)\" when\n> > > +      * remote-object-info command is used. Once we start supporting objecttype\n> > > +      * the default format should change to DEFAULT_FORMAT\n> > > +     */\n> > > +     if (!opt->format)\n> > > +             opt->format = \"%(objectname) %(objectsize)\";\n> >\n> > Seems like it isn't stale. Hum.\n> >\n> \n> No, this isn’t stale. As I mentioned in my response to Junio in\n> https://lore.kernel.org/git/CAN2LT1Cmsw3RB1kbRBvoeLs8WaQeZWqrG96EQfMkMe_jdKaO4g@mail.gmail.com/,\n> adding type support is planned for the next patch series. Based on the\n> documentation at https://git-scm.com/docs/protocol-v2#_object_info, it\n> seems type isn’t yet supported on the server side either. My plan is\n> to implement the logic for both server and client in the next series.\n> \n> Unless the reviewers feel strongly that this must be included now, I’d\n> prefer to stick to the original plan.\n\nThe problem is that you cannot introduce a different format first and\nthen change it in a subsequent patch series because that would be a\nbackwards-incompatible change. So if the follow-up patch series would\nimplement that you cannot revert back to the default format, and\nconsequently the behaviour would now be inconsistent with the non-remote\ncase without a good reason.\n\nPatrick\n"},{"id":"508665","messageId":"CAP8UFD1BprG24AmZH2Y9pRJRpwBbq3qJ+qZR4-kFyHn5rRCGnw@mail.gmail.com","threadId":"61695","inReplyTo":"Z1F3STEa_bsE82Cc@pks.im","subject":"Re: [PATCH v7 6/6] cat-file: add remote-object-info to batch-command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-12-05T10:34:08Z","receivedAt":"2024-12-05T10:34:22Z","isPatch":true,"body":"On Thu, Dec 5, 2024 at 10:52 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Tue, Dec 03, 2024 at 02:23:01PM -0500, Peijian Ju wrote:\n> > No, this isn’t stale. As I mentioned in my response to Junio in\n> > https://lore.kernel.org/git/CAN2LT1Cmsw3RB1kbRBvoeLs8WaQeZWqrG96EQfMkMe_jdKaO4g@mail.gmail.com/,\n> > adding type support is planned for the next patch series. Based on the\n> > documentation at https://git-scm.com/docs/protocol-v2#_object_info, it\n> > seems type isn’t yet supported on the server side either. My plan is\n> > to implement the logic for both server and client in the next series.\n> >\n> > Unless the reviewers feel strongly that this must be included now, I’d\n> > prefer to stick to the original plan.\n>\n> The problem is that you cannot introduce a different format first and\n> then change it in a subsequent patch series because that would be a\n> backwards-incompatible change. So if the follow-up patch series would\n> implement that you cannot revert back to the default format, and\n> consequently the behaviour would now be inconsistent with the non-remote\n> case without a good reason.\n\nThe doc added with this patch clearly says that the default format is\nvery likely to change in the future and that users should not rely on\nit. Also there are very simple ways for users (who are likely to be\nvery advanced users) to use a custom format instead of the default\nformat.\n\nIf we always reject any backward-incompatible change, even on features\nwe have clearly marked as experimental or temporary, then it means\nthere is no point in marking features as experimental or temporary in\nthe docs, and it will make developing new features more difficult as\nwe will likely have to spend a lot of time to get it right the first\ntime instead developing them organically. As we will likely fail in\nsome cases to get it right the first time, it will mean more things we\nwill have to redo in other ways and more things we will have to\ndeprecate and eventually remove, which will be bad for backward\ncompatibility anyway.\n"},{"id":"509535","messageId":"20241223232523.76236-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241223232523.76236-1-eric.peijian@gmail.com","subject":"[PATCH v8 1/6] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-23T23:25:18Z","receivedAt":"2024-12-23T23:25:55Z","isPatch":true,"body":"Some code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n fetch-pack.c       |  3 +--\n 2 files changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex b13561cf73..69ea642dc6 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -676,12 +676,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -689,9 +687,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -717,7 +713,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -727,7 +722,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3a227721ed..72c6a254c9 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1329,9 +1329,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (int i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n-- \n2.47.0\n\n"},{"id":"509536","messageId":"20241223232523.76236-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH v8 0/6] add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-23T23:25:17Z","receivedAt":"2024-12-23T23:25:55Z","isPatch":true,"body":"This patch series is a continuation of Calvin Wan’s (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info\ncommand at [1].\n\nSometimes it is beneficial to retrieve information about an object without\nhaving to download it completely. The server logic for retrieving size has\nalready been implemented and merged in \"a2ba162cda (object-info: support for\nretrieving object info, 2021-04-20)\"[2]. This patch series implement the client\noption for it.\n\nThis patch series add the `remote-object-info` command to\n`cat-file --batch-command`. This command allows the client to make an\nobject-info command request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info capability,\n`cat-file --batch-command` will die.\n\nIf a user attempts to use `remote-object-info` with protocol v1,,\n`cat-file --batch-command` will die.\n\nCurrently, only the size (%(objectsize)) is supported in this implementation.\nThe type (%(objecttype)) is not included in this patch series, as it is not yet\nsupported on the server side either. The plan is to implement the necessary\nlogic for both the server and client in a subsequent series.\n\nThe default format for remote-object-info is set to %(objectname) %(objectsize).\nOnce %(objecttype) is supported, the default format will be unified accordingly.\n\nIf the batch command format includes unsupported fields such as %(objecttype),\n%(objectsize:disk), or %(deltabase), the command will terminate with an error.\n\nChanges since V7\n================\n- Introduced strtoul_ul() in git-compat-util.h to ensure proper error handling\n  using strtoul from the standard library.\n- Separated the test library into its own commit for better clarity\n  and organization.\n- Use string_list_has_string() instead of unsorted_string_list_has_string() to\n  avoid quadratic runtime behaviour\n- Added a documentation link to the wire format in the commit message to\n  provide additional context.\n- New test case \"remote-object-info fails on not providing OID\"\n- Fixed typos and formatting issues for improved readability.\n\nCalvin Wan (4):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n\nEric Ju (2):\n  cat-file: add declaration of variable i inside its for loop\n  cat-file: add remote-object-info to batch-command\n\n Documentation/git-cat-file.txt         |  24 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 110 ++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    |  92 ++++\n fetch-object-info.h                    |  18 +\n fetch-pack.c                           |  51 +-\n fetch-pack.h                           |   2 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 652 +++++++++++++++++++++++++\n transport-helper.c                     |  11 +-\n transport.c                            |  28 +-\n transport.h                            |  11 +\n 18 files changed, 1021 insertions(+), 68 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\nRange-diff against v7:\n-:  ---------- > 1:  c09e21a9d6 cat-file: add declaration of variable i inside its for loop\n-:  ---------- > 2:  ed04a4a7c4 fetch-pack: refactor packet writing\n-:  ---------- > 3:  bc52c4f80c fetch-pack: move fetch initialization\n-:  ---------- > 4:  4c1b989c41 serve: advertise object-info feature\n-:  ---------- > 5:  dbc95a9ae5 transport: add client support for object-info\n-:  ---------- > 6:  f244ec8a2f cat-file: add remote-object-info to batch-command\n-- \n2.47.0\n\nInformation Footer:\nbase-commit: 8f8d6eee531b3fa1a8ef14f169b0cb5035f7a772\nMerge Request: https://gitlab.com/gitlab-org/git/-/merge_requests/168\n"},{"id":"509537","messageId":"20241223232523.76236-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241223232523.76236-1-eric.peijian@gmail.com","subject":"[PATCH v8 2/6] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-23T23:25:19Z","receivedAt":"2024-12-23T23:25:56Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nRefactor write_fetch_command_and_capabilities() to a more\ngeneral-purpose function, write_command_and_capabilities(), enabling it\nto serve both fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162c\n(object-info: support for retrieving object info, 2021-04-20).\n\nFurthermore, write_command_and_capabilities() is moved to connect.c,\nmaking it accessible to additional commands in the future.\n\nTo move write_command_and_capabilities() to connect.c, we need to\nadjust how `advertise_sid` is managed. Previously,\nin fetch_pack.c, `advertise_sid` was a static variable, modified using\ngit_config_get_bool().\n\nIn connect.c, we now initialize `advertise_sid` at the beginning by\ndirectly using git_config_get_bool(). This change is safe because:\n\nIn the original fetch-pack.c code, there are only two places that\nwrite `advertise_sid` :\n1. In function do_fetch_pack:\n        if (!server_supports(\"session-id\"))\n                advertise_sid = 0;\n2. In function fetch_pack_config():\n        git_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, since do_fetch_pack() is only relevant for protocol v1, this\nassignment can be ignored in our refactor, as\nwrite_command_and_capabilities() is only used in protocol v2.\n\nAbout 2, git_config_get_bool() is from config.h and it is an out-of-box\ndependency of connect.c, so we can reuse it directly.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  8 ++++++++\n fetch-pack.c | 35 ++---------------------------------\n 3 files changed, 44 insertions(+), 33 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 10fad43e98..2b51cf09bf 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -689,6 +689,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\tgit_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (int i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t\tthe_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t\tthe_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n enum protocol {\n \tPROTO_LOCAL = 1,\n \tPROTO_FILE,\ndiff --git a/connect.h b/connect.h\nindex 1645126c17..8b56a68b62 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -1,6 +1,7 @@\n #ifndef CONNECT_H\n #define CONNECT_H\n \n+#include \"string-list.h\"\n #include \"protocol.h\"\n \n #define CONNECT_VERBOSE       (1u << 0)\n@@ -30,4 +31,11 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+/**\n+ * write_command_and_capabilities writes a command along with the requested\n+ * server capabilities/features into a request buffer.\n+ */\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 72c6a254c9..78e7d38c47 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1317,37 +1317,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (int i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1358,7 +1327,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2186,7 +2155,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.47.0\n\n"},{"id":"509538","messageId":"20241223232523.76236-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241223232523.76236-1-eric.peijian@gmail.com","subject":"[PATCH v8 3/6] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-23T23:25:20Z","receivedAt":"2024-12-23T23:25:57Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 78e7d38c47..51de82e414 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1648,18 +1648,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.47.0\n\n"},{"id":"509539","messageId":"20241223232523.76236-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241223232523.76236-1-eric.peijian@gmail.com","subject":"[PATCH v8 4/6] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-23T23:25:21Z","receivedAt":"2024-12-23T23:25:58Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex c8694e3751..7a388d26d9 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -70,7 +70,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -78,6 +78,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.47.0\n\n"},{"id":"509540","messageId":"20241223232523.76236-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241223232523.76236-1-eric.peijian@gmail.com","subject":"[PATCH v8 5/6] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-23T23:25:22Z","receivedAt":"2024-12-23T23:25:59Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\"\n\nThis commit introduces client functions to interact with the server.\n\nCurrently, the client supports requesting a list of object IDs with\nthe ‘size’ feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client will return an error and exit.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Makefile            |  1 +\n fetch-object-info.c | 92 +++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h | 18 +++++++++\n fetch-pack.c        |  3 ++\n fetch-pack.h        |  2 +\n transport-helper.c  | 11 +++++-\n transport.c         | 28 +++++++++++++-\n transport.h         | 11 ++++++\n 8 files changed, 163 insertions(+), 3 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n\ndiff --git a/Makefile b/Makefile\nindex 3fa4bf0d06..70e9ec0464 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1020,6 +1020,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..2aa9f2b70d\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,92 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"object-store-ll.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/**\n+ * send_object_info_request sends git-cat-file object-info command and its\n+ * arguments into the request buffer.\n+ */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids) {\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\t}\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+/**\n+ * fetch_object_info sends git-cat-file object-info command into the request buf\n+ * and read the results from packets.\n+ */\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"wrong protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (unsorted_string_list_has_string(args->object_info_options, reader->line)) {\n+\t\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\t\tsize_index = i;\n+\t\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n+\t\t\t}\n+\t\t\tcontinue;\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\n+\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..ce1a05dc96\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,18 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"object-store-ll.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 51de82e414..704bc21b47 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1654,6 +1654,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 9d3470366f..119d3369f1 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\ndiff --git a/transport-helper.c b/transport-helper.c\nindex d457b42550..9da1547b2c 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -710,8 +710,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file\n+\t * remote-object-info require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -727,6 +727,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 10d820c333..5a2629de52 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -464,8 +465,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options\n+\t\t&& transport->smart_options->object_info\n+\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct packet_reader reader;\n+\t\tstruct object_info_args obj_info_args = { 0 };\n+\n+\t\tobj_info_args.server_options = transport->server_options;\n+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n+\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n+\n+\t\tconnect_setup(transport, 0);\n+\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\t\tdata->version = discover_version(&reader);\n+\t\ttransport->hash_algo = reader.hash_algo;\n+\n+\t\tret = fetch_object_info(data->version, &obj_info_args, &reader,\n+\t\t\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n+\t\t\t\t\tdata->fd[1]);\n+\t\tgoto cleanup;\n \n-\tif (!data->finished_handshake) {\n+\t} else if (!data->finished_handshake) {\n \t\tint i;\n \t\tint must_list_refs = 0;\n \t\tfor (i = 0; i < nr_heads; i++) {\ndiff --git a/transport.h b/transport.h\nindex 44100fa9b7..e61e931863 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to retrieve only object-info.\n+\t * If object-info is not supported, the operation will error and exit.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.47.0\n\n"},{"id":"509541","messageId":"20241223232523.76236-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20241223232523.76236-1-eric.peijian@gmail.com","subject":"[PATCH v8 6/6] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-23T23:25:23Z","receivedAt":"2024-12-23T23:26:00Z","isPatch":true,"body":"Since the `info` command in cat-file --batch-command prints object info\nfor a given object, it is natural to add another command in cat-file\n--batch-command to print object info for a given object from a remote.\n\nAdd `remote-object-info` to cat-file --batch-command.\n\nWhile `info` takes object ids one at a time, this creates\noverhead when making requests to a server.So `remote-object-info`\ninstead can take multiple object ids at once.\n\ncat-file --batch-command is generally implemented in the following\nmanner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loop through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication overhead\nin the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Documentation/git-cat-file.txt         |  24 +-\n builtin/cat-file.c                     |  99 ++++\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 652 +++++++++++++++++++++++++\n 7 files changed, 802 insertions(+), 16 deletions(-)\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex d5890ae368..6a2f9fd752 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -149,6 +149,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tError when the `object-info` capability is not supported by the server.\n+\tError when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +297,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +306,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +323,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current the default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +408,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will error\n+and exit when they are in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 69ea642dc6..998addf28c 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -27,6 +27,9 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -45,9 +48,12 @@ struct batch_options {\n \tchar input_delim;\n \tchar output_delim;\n \tconst char *format;\n+\tint use_remote_info;\n };\n \n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -579,6 +585,59 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT\n+\t*/\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\n+\t\t/* 'objectsize' is the only option currently supported */\n+\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -670,6 +729,45 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\n+\tchar *line_to_split = xstrdup_or_null(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\n+\topt->use_remote_info = 1;\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\n+\t\tdata->oid = object_info_oids.oid[i];\n+\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\t\t}\n+\t}\n+\topt->use_remote_info = 0;\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -701,6 +799,7 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1},\n \t{ \"info\", parse_cmd_info, 1},\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1},\n \t{ \"flush\", NULL, 0},\n };\n \ndiff --git a/object-file.c b/object-file.c\nindex 5b792b3dd4..96f204c93a 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -3128,3 +3128,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex cd3bd5bd99..20208e1d4f 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -553,4 +553,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\t\t   void *data, enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..9fb20be308\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related tests.\n+\n+# Print a string without a trailing newline\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replaces them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string removing any leading spaces.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex ff9bf213aa..5c7d581ea2 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -3,6 +3,7 @@\n test_description='git cat-file'\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -98,18 +99,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     oid=$2\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..0e3044d8d6\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,652 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n-- \n2.47.0\n\n"},{"id":"509579","messageId":"xmqqpllew1k6.fsf@gitster.g","threadId":"61695","inReplyTo":"20241223232523.76236-1-eric.peijian@gmail.com","subject":"Re: [PATCH v8 0/6] add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-26T21:56:41Z","receivedAt":"2024-12-26T21:56:45Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n> Range-diff against v7:\n> -:  ---------- > 1:  c09e21a9d6 cat-file: add declaration of variable i inside its for loop\n> -:  ---------- > 2:  ed04a4a7c4 fetch-pack: refactor packet writing\n> -:  ---------- > 3:  bc52c4f80c fetch-pack: move fetch initialization\n> -:  ---------- > 4:  4c1b989c41 serve: advertise object-info feature\n> -:  ---------- > 5:  dbc95a9ae5 transport: add client support for object-info\n> -:  ---------- > 6:  f244ec8a2f cat-file: add remote-object-info to batch-command\n\nThis is curious.  Did you compare the right things?\n\n    -- \n    2.47.0\n\n    Information Footer:\n    base-commit: 8f8d6eee531b3fa1a8ef14f169b0cb5035f7a772\n    Merge Request: https://gitlab.com/gitlab-org/git/-/merge_requests/168\n\nIf the base-commit information is relevant, please do not write it\nbelow the \"signature\" like (i.e. a line that consists only of\ndash-dash-space near the end of the message), as some e-mail programs\nconsider them irrelevant and omit from quoting.\n\nI tried to apply them on top of 8f8d6eee (The seventh batch,\n2024-11-01) but the last step [6/6] fails to apply (the first five\napplied cleanly, and matched what I already had).\n\nCould you help to figure out what is going wrong on your end?\n\nThanks.\n"},{"id":"509753","messageId":"CAN2LT1Dx5TK208Tc_-XRyysE_Et+0M6C4kvqCPzjUvKUt5a_mg@mail.gmail.com","threadId":"61695","inReplyTo":"xmqqpllew1k6.fsf@gitster.g","subject":"Re: [PATCH v8 0/6] add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2024-12-30T23:25:21Z","receivedAt":"2024-12-30T23:25:33Z","isPatch":true,"body":"Sorry for the noise. I forgot to CC others, so I am resending it.\n\nOn Thu, Dec 26, 2024 at 5:56 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> > Range-diff against v7:\n> > -:  ---------- > 1:  c09e21a9d6 cat-file: add declaration of variable i inside its for loop\n> > -:  ---------- > 2:  ed04a4a7c4 fetch-pack: refactor packet writing\n> > -:  ---------- > 3:  bc52c4f80c fetch-pack: move fetch initialization\n> > -:  ---------- > 4:  4c1b989c41 serve: advertise object-info feature\n> > -:  ---------- > 5:  dbc95a9ae5 transport: add client support for object-info\n> > -:  ---------- > 6:  f244ec8a2f cat-file: add remote-object-info to batch-command\n>\n> This is curious.  Did you compare the right things?\n>\n\n\nThank you.\n\nI think I may compare it wrong.\n\n>     --\n>     2.47.0\n>\n>     Information Footer:\n>     base-commit: 8f8d6eee531b3fa1a8ef14f169b0cb5035f7a772\n>     Merge Request: https://gitlab.com/gitlab-org/git/-/merge_requests/168\n>\n> If the base-commit information is relevant, please do not write it\n> below the \"signature\" like (i.e. a line that consists only of\n> dash-dash-space near the end of the message), as some e-mail programs\n> consider them irrelevant and omit from quoting.\n>\n\nRoger that.\n\n> I tried to apply them on top of 8f8d6eee (The seventh batch,\n> 2024-11-01) but the last step [6/6] fails to apply (the first five\n> applied cleanly, and matched what I already had).\n>\n> Could you help to figure out what is going wrong on your end?\n>\n\nShould I resend v8 or send a v9 instead?\n\n> Thanks.\n"},{"id":"510104","messageId":"CAFySSZAqh6J14+r9JLM3LmRmV02ZvPRf5dB3rWVnUZS_5XaHcQ@mail.gmail.com","threadId":"61695","inReplyTo":"20241223232523.76236-6-eric.peijian@gmail.com","subject":"Re: [PATCH v8 5/6] transport: add client support for object-info","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2025-01-07T18:31:40Z","receivedAt":"2025-01-07T18:31:53Z","isPatch":true,"body":"Thanks for picking up this series btw!\n\nOn Mon, Dec 23, 2024 at 3:25 PM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> From: Calvin Wan <calvinwan@google.com>\n>\n> Sometimes, it is beneficial to retrieve information about an object\n> without downloading it entirely. The server-side logic for this\n> functionality was implemented in commit \"a2ba162cda (object-info:\n> support for retrieving object info, 2021-04-20).\"\n>\n> This commit introduces client functions to interact with the server.\n>\n> Currently, the client supports requesting a list of object IDs with\n> the ‘size’ feature from a v2 server. If the server does not advertise\n> this feature (i.e., transfer.advertiseobjectinfo is set to false),\n> the client will return an error and exit.\n>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> ---\n>  Makefile            |  1 +\n>  fetch-object-info.c | 92 +++++++++++++++++++++++++++++++++++++++++++++\n>  fetch-object-info.h | 18 +++++++++\n>  fetch-pack.c        |  3 ++\n>  fetch-pack.h        |  2 +\n>  transport-helper.c  | 11 +++++-\n>  transport.c         | 28 +++++++++++++-\n>  transport.h         | 11 ++++++\n>  8 files changed, 163 insertions(+), 3 deletions(-)\n>  create mode 100644 fetch-object-info.c\n>  create mode 100644 fetch-object-info.h\n>\n> diff --git a/Makefile b/Makefile\n> index 3fa4bf0d06..70e9ec0464 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -1020,6 +1020,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n>  LIB_OBJS += exec-cmd.o\n>  LIB_OBJS += fetch-negotiator.o\n>  LIB_OBJS += fetch-pack.o\n> +LIB_OBJS += fetch-object-info.o\n>  LIB_OBJS += fmt-merge-msg.o\n>  LIB_OBJS += fsck.o\n>  LIB_OBJS += fsmonitor.o\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> new file mode 100644\n> index 0000000000..2aa9f2b70d\n> --- /dev/null\n> +++ b/fetch-object-info.c\n> @@ -0,0 +1,92 @@\n> +#include \"git-compat-util.h\"\n> +#include \"gettext.h\"\n> +#include \"hex.h\"\n> +#include \"pkt-line.h\"\n> +#include \"connect.h\"\n> +#include \"oid-array.h\"\n> +#include \"object-store-ll.h\"\n> +#include \"fetch-object-info.h\"\n> +#include \"string-list.h\"\n> +\n> +/**\n> + * send_object_info_request sends git-cat-file object-info command and its\n> + * arguments into the request buffer.\n> + */\n> +static void send_object_info_request(const int fd_out, struct object_info_args *args)\n> +{\n> +       struct strbuf req_buf = STRBUF_INIT;\n> +\n> +       write_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n> +\n> +       if (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n> +               packet_buf_write(&req_buf, \"size\");\n> +\n> +       if (args->oids) {\n> +               for (size_t i = 0; i < args->oids->nr; i++)\n> +                       packet_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n> +       }\n> +\n> +       packet_buf_flush(&req_buf);\n> +       if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n> +               die_errno(_(\"unable to write request to remote\"));\n> +\n> +       strbuf_release(&req_buf);\n> +}\n> +\n> +/**\n> + * fetch_object_info sends git-cat-file object-info command into the request buf\n> + * and read the results from packets.\n> + */\n> +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n> +                     struct packet_reader *reader, struct object_info *object_info_data,\n> +                     const int stateless_rpc, const int fd_out)\n> +{\n> +       int size_index = -1;\n> +\n> +       switch (version) {\n> +       case protocol_v2:\n> +               if (!server_supports_v2(\"object-info\"))\n> +                       die(_(\"object-info capability is not enabled on the server\"));\n> +               send_object_info_request(fd_out, args);\n> +               break;\n> +       case protocol_v1:\n> +       case protocol_v0:\n> +               die(_(\"wrong protocol version. expected v2\"));\n\ns/wrong/unsupported\n\n> +       case protocol_unknown_version:\n> +               BUG(\"unknown protocol version\");\n> +       }\n> +\n> +       for (size_t i = 0; i < args->object_info_options->nr; i++) {\n> +               if (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n> +                       check_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n> +                       return -1;\n> +               }\n> +               if (unsorted_string_list_has_string(args->object_info_options, reader->line)) {\n> +                       if (!strcmp(reader->line, \"size\")) {\n> +                               size_index = i;\n> +                               for (size_t j = 0; j < args->oids->nr; j++)\n> +                                       object_info_data[j].sizep = xcalloc(1, sizeof(long));\n> +                       }\n> +                       continue;\n> +               }\n> +               return -1;\n> +       }\n\nI think we can flatten this logic a bit more here to make it more intuitive.\n\nif (!unsorted_string_list_has_string(args->object_info_options, reader->line))\n        return -1;\nif (!strcmp(reader->line, \"size\")) {\n        size_index = i;\n        for (size_t j = 0; j < args->oids->nr; j++)\n                object_info_data[j].sizep = xcalloc(1, sizeof(long));\n}\n"},{"id":"510108","messageId":"xmqqikqqsbet.fsf@gitster.g","threadId":"61695","inReplyTo":"CAFySSZAqh6J14+r9JLM3LmRmV02ZvPRf5dB3rWVnUZS_5XaHcQ@mail.gmail.com","subject":"Re: [PATCH v8 5/6] transport: add client support for object-info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-07T18:53:46Z","receivedAt":"2025-01-07T18:53:50Z","isPatch":true,"body":"Calvin Wan <calvinwan@google.com> writes:\n\n> Thanks for picking up this series btw!\n> ...\n> I think we can flatten this logic a bit more here to make it more intuitive.\n>\n> if (!unsorted_string_list_has_string(args->object_info_options, reader->line))\n>         return -1;\n> if (!strcmp(reader->line, \"size\")) {\n>         size_index = i;\n>         for (size_t j = 0; j < args->oids->nr; j++)\n>                 object_info_data[j].sizep = xcalloc(1, sizeof(long));\n> }\n\nIndeed the updated code structure gets easier to follow.\n\nThanks, both of you.\n"},{"id":"510125","messageId":"CAFySSZAnKQdOtAs9AXVQLPqyuZSuG+B-M+6uH3qdY33EaQE9vg@mail.gmail.com","threadId":"61695","inReplyTo":"20241223232523.76236-7-eric.peijian@gmail.com","subject":"Re: [PATCH v8 6/6] cat-file: add remote-object-info to batch-command","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2025-01-07T21:29:34Z","receivedAt":"2025-01-07T21:29:46Z","isPatch":true,"body":"On Mon, Dec 23, 2024 at 3:26 PM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> Since the `info` command in cat-file --batch-command prints object info\n> for a given object, it is natural to add another command in cat-file\n> --batch-command to print object info for a given object from a remote.\n>\n> Add `remote-object-info` to cat-file --batch-command.\n>\n> While `info` takes object ids one at a time, this creates\n> overhead when making requests to a server.So `remote-object-info`\n> instead can take multiple object ids at once.\n>\n> cat-file --batch-command is generally implemented in the following\n> manner:\n>\n>  - Receive and parse input from user\n>  - Call respective function attached to command\n>  - Get object info, print object info\n>\n> In --buffer mode, this changes to:\n>\n>  - Receive and parse input from user\n>  - Store respective function attached to command in a queue\n>  - After flush, loop through commands in queue\n>     - Call respective function attached to command\n>     - Get object info, print object info\n>\n> Notice how the getting and printing of object info is accomplished one\n> at a time. As described above, this creates a problem for making\n> requests to a server. Therefore, `remote-object-info` is implemented in\n> the following manner:\n>\n>  - Receive and parse input from user\n>  If command is `remote-object-info`:\n>     - Get object info from remote\n>     - Loop through and print each object info\n>  Else:\n>     - Call respective function attached to command\n>     - Parse input, get object info, print object info\n>\n> And finally for --buffer mode `remote-object-info`:\n>  - Receive and parse input from user\n>  - Store respective function attached to command in a queue\n>  - After flush, loop through commands in queue:\n>     If command is `remote-object-info`:\n>         - Get object info from remote\n>         - Loop through and print each object info\n>     Else:\n>         - Call respective function attached to command\n>         - Get object info, print object info\n>\n> To summarize, `remote-object-info` gets object info from the remote and\n> then loop through the object info passed in, printing the info.\n>\n> In order for remote-object-info to avoid remote communication overhead\n> in the non-buffer mode, the objects are passed in as such:\n>\n> remote-object-info <remote> <oid> <oid> ... <oid>\n>\n> rather than\n>\n> remote-object-info <remote> <oid>\n> remote-object-info <remote> <oid>\n> ...\n> remote-object-info <remote> <oid>\n>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> ---\n>  Documentation/git-cat-file.txt         |  24 +-\n>  builtin/cat-file.c                     |  99 ++++\n>  object-file.c                          |  11 +\n>  object-store-ll.h                      |   3 +\n>  t/lib-cat-file.sh                      |  16 +\n>  t/t1006-cat-file.sh                    |  13 +-\n>  t/t1017-cat-file-remote-object-info.sh | 652 +++++++++++++++++++++++++\n>  7 files changed, 802 insertions(+), 16 deletions(-)\n>  create mode 100644 t/lib-cat-file.sh\n>  create mode 100755 t/t1017-cat-file-remote-object-info.sh\n>\n> diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\n> index d5890ae368..6a2f9fd752 100644\n> --- a/Documentation/git-cat-file.txt\n> +++ b/Documentation/git-cat-file.txt\n> @@ -149,6 +149,13 @@ info <object>::\n>         Print object info for object reference `<object>`. This corresponds to the\n>         output of `--batch-check`.\n>\n> +remote-object-info <remote> <object>...::\n> +       Print object info for object references `<object>` at specified\n> +       `<remote>` without downloading objects from the remote.\n> +       Error when the `object-info` capability is not supported by the server.\n> +       Error when no object references are provided.\n> +       This command may be combined with `--buffer`.\n> +\n>  flush::\n>         Used with `--buffer` to execute all preceding commands that were issued\n>         since the beginning or since the last flush was issued. When `--buffer`\n> @@ -290,7 +297,8 @@ newline. The available atoms are:\n>         The full hex representation of the object name.\n>\n>  `objecttype`::\n> -       The type of the object (the same as `cat-file -t` reports).\n> +       The type of the object (the same as `cat-file -t` reports). See\n> +       `CAVEATS` below. Not supported by `remote-object-info`.\n>\n>  `objectsize`::\n>         The size, in bytes, of the object (the same as `cat-file -s`\n> @@ -298,13 +306,14 @@ newline. The available atoms are:\n>\n>  `objectsize:disk`::\n>         The size, in bytes, that the object takes up on disk. See the\n> -       note about on-disk sizes in the `CAVEATS` section below.\n> +       note about on-disk sizes in the `CAVEATS` section below. Not\n> +       supported by `remote-object-info`.\n>\n>  `deltabase`::\n>         If the object is stored as a delta on-disk, this expands to the\n>         full hex representation of the delta base object name.\n>         Otherwise, expands to the null OID (all zeroes). See `CAVEATS`\n> -       below.\n> +       below. Not supported by `remote-object-info`.\n>\n>  `rest`::\n>         If this atom is used in the output string, input lines are split\n> @@ -314,7 +323,10 @@ newline. The available atoms are:\n>         line) are output in place of the `%(rest)` atom.\n>\n>  If no format is specified, the default format is `%(objectname)\n> -%(objecttype) %(objectsize)`.\n> +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> +WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n> +DO NOT RELY on the current the default format to stay the same!!!\n\nI remember this was one of my initial concerns when I first worked on\nthis series -- without a use case for other fields, it's definitely\nhard to say how a default format for such would look and obviously\nwhen implemented, would cause the default format of %(objectsize) to\nchange as well. I'm glad to see this outcome is well documented so we\ncan have this feature working with a backdoor to change it if\nnecessary for the future. Thanks again for your work on this series.\n"},{"id":"510166","messageId":"CAN2LT1Af_uzcsJBdFFNoTrLOyQiY9Cbts2=RnPWMS41vQi3m4Q@mail.gmail.com","threadId":"61695","inReplyTo":"CAFySSZAqh6J14+r9JLM3LmRmV02ZvPRf5dB3rWVnUZS_5XaHcQ@mail.gmail.com","subject":"Re: [PATCH v8 5/6] transport: add client support for object-info","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-08T15:55:38Z","receivedAt":"2025-01-08T15:55:50Z","isPatch":true,"body":"On Tue, Jan 7, 2025 at 1:31 PM Calvin Wan <calvinwan@google.com> wrote:\n>\n> Thanks for picking up this series btw!\n>\n> On Mon, Dec 23, 2024 at 3:25 PM Eric Ju <eric.peijian@gmail.com> wrote:\n> >\n> > From: Calvin Wan <calvinwan@google.com>\n> >\n> > Sometimes, it is beneficial to retrieve information about an object\n> > without downloading it entirely. The server-side logic for this\n> > functionality was implemented in commit \"a2ba162cda (object-info:\n> > support for retrieving object info, 2021-04-20).\"\n> >\n> > This commit introduces client functions to interact with the server.\n> >\n> > Currently, the client supports requesting a list of object IDs with\n> > the ‘size’ feature from a v2 server. If the server does not advertise\n> > this feature (i.e., transfer.advertiseobjectinfo is set to false),\n> > the client will return an error and exit.\n> >\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju  <eric.peijian@gmail.com>\n> > ---\n> >  Makefile            |  1 +\n> >  fetch-object-info.c | 92 +++++++++++++++++++++++++++++++++++++++++++++\n> >  fetch-object-info.h | 18 +++++++++\n> >  fetch-pack.c        |  3 ++\n> >  fetch-pack.h        |  2 +\n> >  transport-helper.c  | 11 +++++-\n> >  transport.c         | 28 +++++++++++++-\n> >  transport.h         | 11 ++++++\n> >  8 files changed, 163 insertions(+), 3 deletions(-)\n> >  create mode 100644 fetch-object-info.c\n> >  create mode 100644 fetch-object-info.h\n> >\n> > diff --git a/Makefile b/Makefile\n> > index 3fa4bf0d06..70e9ec0464 100644\n> > --- a/Makefile\n> > +++ b/Makefile\n> > @@ -1020,6 +1020,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n> >  LIB_OBJS += exec-cmd.o\n> >  LIB_OBJS += fetch-negotiator.o\n> >  LIB_OBJS += fetch-pack.o\n> > +LIB_OBJS += fetch-object-info.o\n> >  LIB_OBJS += fmt-merge-msg.o\n> >  LIB_OBJS += fsck.o\n> >  LIB_OBJS += fsmonitor.o\n> > diff --git a/fetch-object-info.c b/fetch-object-info.c\n> > new file mode 100644\n> > index 0000000000..2aa9f2b70d\n> > --- /dev/null\n> > +++ b/fetch-object-info.c\n> > @@ -0,0 +1,92 @@\n> > +#include \"git-compat-util.h\"\n> > +#include \"gettext.h\"\n> > +#include \"hex.h\"\n> > +#include \"pkt-line.h\"\n> > +#include \"connect.h\"\n> > +#include \"oid-array.h\"\n> > +#include \"object-store-ll.h\"\n> > +#include \"fetch-object-info.h\"\n> > +#include \"string-list.h\"\n> > +\n> > +/**\n> > + * send_object_info_request sends git-cat-file object-info command and its\n> > + * arguments into the request buffer.\n> > + */\n> > +static void send_object_info_request(const int fd_out, struct object_info_args *args)\n> > +{\n> > +       struct strbuf req_buf = STRBUF_INIT;\n> > +\n> > +       write_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n> > +\n> > +       if (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n> > +               packet_buf_write(&req_buf, \"size\");\n> > +\n> > +       if (args->oids) {\n> > +               for (size_t i = 0; i < args->oids->nr; i++)\n> > +                       packet_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n> > +       }\n> > +\n> > +       packet_buf_flush(&req_buf);\n> > +       if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n> > +               die_errno(_(\"unable to write request to remote\"));\n> > +\n> > +       strbuf_release(&req_buf);\n> > +}\n> > +\n> > +/**\n> > + * fetch_object_info sends git-cat-file object-info command into the request buf\n> > + * and read the results from packets.\n> > + */\n> > +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n> > +                     struct packet_reader *reader, struct object_info *object_info_data,\n> > +                     const int stateless_rpc, const int fd_out)\n> > +{\n> > +       int size_index = -1;\n> > +\n> > +       switch (version) {\n> > +       case protocol_v2:\n> > +               if (!server_supports_v2(\"object-info\"))\n> > +                       die(_(\"object-info capability is not enabled on the server\"));\n> > +               send_object_info_request(fd_out, args);\n> > +               break;\n> > +       case protocol_v1:\n> > +       case protocol_v0:\n> > +               die(_(\"wrong protocol version. expected v2\"));\n>\n> s/wrong/unsupported\n>\n\nThank you. Fixing it in v9.\n\n> > +       case protocol_unknown_version:\n> > +               BUG(\"unknown protocol version\");\n> > +       }\n> > +\n> > +       for (size_t i = 0; i < args->object_info_options->nr; i++) {\n> > +               if (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n> > +                       check_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n> > +                       return -1;\n> > +               }\n> > +               if (unsorted_string_list_has_string(args->object_info_options, reader->line)) {\n> > +                       if (!strcmp(reader->line, \"size\")) {\n> > +                               size_index = i;\n> > +                               for (size_t j = 0; j < args->oids->nr; j++)\n> > +                                       object_info_data[j].sizep = xcalloc(1, sizeof(long));\n> > +                       }\n> > +                       continue;\n> > +               }\n> > +               return -1;\n> > +       }\n>\n> I think we can flatten this logic a bit more here to make it more intuitive.\n>\n> if (!unsorted_string_list_has_string(args->object_info_options, reader->line))\n>         return -1;\n> if (!strcmp(reader->line, \"size\")) {\n>         size_index = i;\n>         for (size_t j = 0; j < args->oids->nr; j++)\n>                 object_info_data[j].sizep = xcalloc(1, sizeof(long));\n> }\n\nThank you. Revising it in v9.\n"},{"id":"510180","messageId":"20250108183740.67022-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH v9 0/8] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-08T18:37:31Z","receivedAt":"2025-01-08T18:37:58Z","isPatch":true,"body":"Because I mistakenly sent a wrong range-diff in v8, please consider this v9 as\nboth an update addressing new comments from Calvin Wan at\nhttps://lore.kernel.org/git/CAFySSZAqh6J14+r9JLM3LmRmV02ZvPRf5dB3rWVnUZS_5XaHcQ@mail.gmail.com/\nand a resend of the corrected range-diff for v8.\n\nThis patch series is a continuation of Calvin Wan’s (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info\ncommand at [1].\n\nSometimes it is beneficial to retrieve information about an object without\nhaving to download it completely. The server logic for retrieving size has\nalready been implemented and merged in \"a2ba162cda (object-info: support for\nretrieving object info, 2021-04-20)\"[2]. This patch series implement the client\noption for it.\n\nThis patch series add the `remote-object-info` command to\n`cat-file --batch-command`. This command allows the client to make an\nobject-info command request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info capability,\n`cat-file --batch-command` will die.\n\nIf a user attempts to use `remote-object-info` with protocol v1,,\n`cat-file --batch-command` will die.\n\nCurrently, only the size (%(objectsize)) is supported in this implementation.\nThe type (%(objecttype)) is not included in this patch series, as it is not yet\nsupported on the server side either. The plan is to implement the necessary\nlogic for both the server and client in a subsequent series.\n\nThe default format for remote-object-info is set to %(objectname) %(objectsize).\nOnce %(objecttype) is supported, the default format will be unified accordingly.\n\nIf the batch command format includes unsupported fields such as %(objecttype),\n%(objectsize:disk), or %(deltabase), the command will terminate with an error.\n\nChanges since V7 (v8 had an incorrect range-diff)\n================\n- Introduced strtoul_ul() in git-compat-util.h to ensure proper error handling\n  using strtoul from the standard library.\n- Separated the test library into its own commit for better clarity\n  and organization.\n- Use string_list_has_string() instead of unsorted_string_list_has_string() to\n  avoid quadratic runtime behaviour\n- Added a documentation link to the wire format in the commit message to\n  provide additional context.\n- New test case \"remote-object-info fails on not providing OID\"\n- Fixed typos and formatting issues for improved readability.\n- Flattened the memory allocation logic of sizep in object_info_data for better\n  intuitiveness and readability.\n\nCalvin Wan (4):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n\nEric Ju (4):\n  git-compat-util: add strtoul_ul() with error handling\n  cat-file: add declaration of variable i inside its for loop\n  cat-file: split test utility functions into a separate library file\n  cat-file: add remote-object-info to batch-command\n\n Documentation/git-cat-file.txt         |  24 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 110 +++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    |  85 ++++\n fetch-object-info.h                    |  22 +\n fetch-pack.c                           |  51 +-\n fetch-pack.h                           |   2 +\n git-compat-util.h                      |  18 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 664 +++++++++++++++++++++++++\n transport-helper.c                     |  11 +-\n transport.c                            |  28 +-\n transport.h                            |  11 +\n 19 files changed, 1048 insertions(+), 68 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\nRange-diff against v7:\n-:  ---------- > 1:  63997081d1 git-compat-util: add strtoul_ul() with error handling\n1:  5181e849eb ! 2:  f188962f05 cat-file: add declaration of variable i inside its for loop\n    @@ fetch-pack.c: static void write_fetch_command_and_capabilities(struct strbuf *re\n     -\t\tint i;\n      \t\tensure_server_supports_v2(\"server-option\");\n     -\t\tfor (i = 0; i < server_options->nr; i++)\n    -+\t\tfor (int i = 0; i < server_options->nr; i++)\n    ++\t\tfor (size_t i = 0; i < server_options->nr; i++)\n      \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n      \t\t\t\t\t server_options->items[i].string);\n      \t}\n-:  ---------- > 3:  71250a03d2 cat-file: split test utility functions into a separate library file\n2:  0c6acf58c2 ! 4:  0ab26e6cd5 fetch-pack: refactor packet writing\n    @@ connect.c: int server_supports(const char *feature)\n     +\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n     +\tif (server_options && server_options->nr) {\n     +\t\tensure_server_supports_v2(\"server-option\");\n    -+\t\tfor (int i = 0; i < server_options->nr; i++)\n    ++\t\tfor (size_t i = 0; i < server_options->nr; i++)\n     +\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n     +\t\t\t\t\t server_options->items[i].string);\n     +\t}\n    @@ connect.c: int server_supports(const char *feature)\n      \tPROTO_FILE,\n     \n      ## connect.h ##\n    -@@\n    - #ifndef CONNECT_H\n    - #define CONNECT_H\n    - \n    -+#include \"string-list.h\"\n    - #include \"protocol.h\"\n    - \n    - #define CONNECT_VERBOSE       (1u << 0)\n     @@ connect.h: void check_stateless_delimiter(int stateless_rpc,\n      \t\t\t       struct packet_reader *reader,\n      \t\t\t       const char *error);\n      \n    -+/**\n    -+ * write_command_and_capabilities writes a command along with the requested\n    ++/*\n    ++ * Writes a command along with the requested\n     + * server capabilities/features into a request buffer.\n     + */\n    ++struct string_list;\n     +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n     +\t\t\t\t    const struct string_list *server_options);\n     +\n    @@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n     -\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n     -\tif (server_options && server_options->nr) {\n     -\t\tensure_server_supports_v2(\"server-option\");\n    --\t\tfor (int i = 0; i < server_options->nr; i++)\n    +-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n     -\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n     -\t\t\t\t\t server_options->items[i].string);\n     -\t}\n3:  28ef74980c = 5:  8b381b4bdc fetch-pack: move fetch initialization\n4:  cb5bf65b88 = 6:  a0a15e1e4f serve: advertise object-info feature\n5:  79eab87dd2 ! 7:  e1aad1ec30 transport: add client support for object-info\n    @@ Commit message\n         Sometimes, it is beneficial to retrieve information about an object\n         without downloading it entirely. The server-side logic for this\n         functionality was implemented in commit \"a2ba162cda (object-info:\n    -    support for retrieving object info, 2021-04-20).\"\n    +    support for retrieving object info, 2021-04-20).\" And the wire\n    +    format is documented at\n    +    https://git-scm.com/docs/protocol-v2#_object_info.\n     \n         This commit introduces client functions to interact with the server.\n     \n         Currently, the client supports requesting a list of object IDs with\n    -    the ‘size’ feature from a v2 server. If the server does not advertise\n    +    the 'size' feature from a v2 server. If the server does not advertise\n         this feature (i.e., transfer.advertiseobjectinfo is set to false),\n         the client will return an error and exit.\n     \n    +    Notice that the entire request is written into req_buf before being\n    +    sent to the remote. This approach follows the pattern used in the\n    +    `send_fetch_request()` logic within fetch-pack.c.\n    +    Streaming the request is not addressed in this patch.\n    +\n         Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Helped-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Calvin Wan <calvinwan@google.com>\n    @@ fetch-object-info.c (new)\n     +#include \"fetch-object-info.h\"\n     +#include \"string-list.h\"\n     +\n    -+/**\n    -+ * send_object_info_request sends git-cat-file object-info command and its\n    -+ * arguments into the request buffer.\n    -+ */\n    ++/* Sends git-cat-file object-info command and its arguments into the request buffer. */\n     +static void send_object_info_request(const int fd_out, struct object_info_args *args)\n     +{\n     +\tstruct strbuf req_buf = STRBUF_INIT;\n    @@ fetch-object-info.c (new)\n     +\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n     +\t\tpacket_buf_write(&req_buf, \"size\");\n     +\n    -+\tif (args->oids) {\n    ++\tif (args->oids)\n     +\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n     +\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n    -+\t}\n     +\n     +\tpacket_buf_flush(&req_buf);\n     +\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n    @@ fetch-object-info.c (new)\n     +\tstrbuf_release(&req_buf);\n     +}\n     +\n    -+/**\n    -+ * fetch_object_info sends git-cat-file object-info command into the request buf\n    -+ * and read the results from packets.\n    -+ */\n     +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n     +\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n     +\t\t      const int stateless_rpc, const int fd_out)\n    @@ fetch-object-info.c (new)\n     +\t\tbreak;\n     +\tcase protocol_v1:\n     +\tcase protocol_v0:\n    -+\t\tdie(_(\"wrong protocol version. expected v2\"));\n    ++\t\tdie(_(\"unsupported protocol version. expected v2\"));\n     +\tcase protocol_unknown_version:\n     +\t\tBUG(\"unknown protocol version\");\n     +\t}\n    @@ fetch-object-info.c (new)\n     +\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n     +\t\t\treturn -1;\n     +\t\t}\n    -+\t\tif (unsorted_string_list_has_string(args->object_info_options, reader->line)) {\n    -+\t\t\tif (!strcmp(reader->line, \"size\")) {\n    -+\t\t\t\tsize_index = i;\n    -+\t\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n    -+\t\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(long));\n    -+\t\t\t}\n    -+\t\t\tcontinue;\n    ++\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n    ++\t\t\treturn -1;\n    ++\t\tif (!strcmp(reader->line, \"size\")) {\n    ++\t\t\tsize_index = i;\n    ++\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n    ++\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n     +\t\t}\n    -+\t\treturn -1;\n     +\t}\n     +\n     +\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n    @@ fetch-object-info.c (new)\n     +\t\t\t\tdie(\"object-info: not our ref %s\",\n     +\t\t\t\t\tobject_info_values.items[0].string);\n     +\n    -+\t\t\t*object_info_data[i].sizep = strtoul(object_info_values.items[1 + size_index].string, NULL, 10);\n    ++\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))\n    ++\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n    ++\t\t\t\t\tobject_info_values.items[0].string,\n    ++\t\t\t\t\tobject_info_values.items[1 + size_index].string);\n     +\t\t}\n     +\n     +\t\tstring_list_clear(&object_info_values, 0);\n    @@ fetch-object-info.h (new)\n     +\tstruct oid_array *oids;\n     +};\n     +\n    ++/*\n    ++ * Sends git-cat-file object-info command into the request buf and read the\n    ++ * results from packets.\n    ++ */\n     +int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n     +\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n     +\t\t      int stateless_rpc, int fd_out);\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n     +\targs.object_info = transport->smart_options->object_info;\n     +\n    -+\tif (transport->smart_options\n    -+\t\t&& transport->smart_options->object_info\n    -+\t\t&& transport->smart_options->object_info_oids->nr > 0) {\n    ++\tif (transport->smart_options && transport->smart_options->object_info\n    ++\t    && transport->smart_options->object_info_oids->nr > 0) {\n     +\t\tstruct packet_reader reader;\n     +\t\tstruct object_info_args obj_info_args = { 0 };\n     +\n     +\t\tobj_info_args.server_options = transport->server_options;\n    -+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n     +\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n    ++\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n    ++\t\tstring_list_sort(obj_info_args.object_info_options);\n     +\n     +\t\tconnect_setup(transport, 0);\n     +\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n6:  b60863aa5b ! 8:  0795ad53fe cat-file: add remote-object-info to batch-command\n    @@ builtin/cat-file.c: static void batch_one_object(const char *obj_name,\n     +\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n     +\t\toid_array_append(&object_info_oids, &oid);\n     +\t}\n    -+\n    ++\tif (object_info_oids.nr == 0) {\n    ++\t\tdie(_(\"remote-object-info requires objects\"));\n    ++\t}\n     +\tgtransport = transport_get(remote, NULL);\n     +\tif (gtransport->smart_options) {\n     +\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n    @@ builtin/cat-file.c: static void parse_cmd_info(struct batch_options *opt,\n     +\topt->use_remote_info = 1;\n     +\tdata->skip_object_info = 1;\n     +\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n    -+\n     +\t\tdata->oid = object_info_oids.oid[i];\n    -+\n     +\t\tif (remote_object_info[i].sizep) {\n     +\t\t\t/*\n     +\t\t\t * When reaching here, it means remote-object-info can retrieve\n    @@ object-store-ll.h: int for_each_object_in_pack(struct packed_git *p,\n     +\n      #endif /* OBJECT_STORE_LL_H */\n     \n    - ## t/lib-cat-file.sh (new) ##\n    -@@\n    -+# Library of git-cat-file related tests.\n    -+\n    -+# Print a string without a trailing newline\n    -+echo_without_newline () {\n    -+\tprintf '%s' \"$*\"\n    -+}\n    -+\n    -+# Print a string without newlines and replaces them with a NULL character (\\0).\n    -+echo_without_newline_nul () {\n    -+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n    -+}\n    -+\n    -+# Calculate the length of a string removing any leading spaces.\n    -+strlen () {\n    -+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n    -+}\n    -\n    - ## t/t1006-cat-file.sh ##\n    -@@\n    - test_description='git cat-file'\n    - \n    - . ./test-lib.sh\n    -+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n    - \n    - test_cmdmode_usage () {\n    - \ttest_expect_code 129 \"$@\" 2>err &&\n    -@@ t/t1006-cat-file.sh: do\n    - \t'\n    - done\n    - \n    --echo_without_newline () {\n    --    printf '%s' \"$*\"\n    --}\n    --\n    --echo_without_newline_nul () {\n    --\techo_without_newline \"$@\" | tr '\\n' '\\0'\n    --}\n    --\n    --strlen () {\n    --    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n    --}\n    --\n    - run_tests () {\n    -     type=$1\n    -     oid=$2\n    -\n      ## t/t1017-cat-file-remote-object-info.sh (new) ##\n     @@\n     +#!/bin/sh\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t)\n     +'\n     +\n    -+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n    ++test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n     +\t(\n     +\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n     +\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n    @@ t/t1017-cat-file-remote-object-info.sh (new)\n     +\t)\n     +'\n     +\n    ++test_expect_success 'remote-object-info fails on not providing OID' '\n    ++\t(\n    ++\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n    ++\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n    ++\n    ++\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n    ++\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n    ++\t\tEOF\n    ++\t\ttest_grep \"remote-object-info requires objects\" err\n    ++\t)\n    ++'\n    ++\n     +\n     +# Test --batch-command remote-object-info with 'http://' transport and\n     +# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n-- \n2.47.0\n\n"},{"id":"510181","messageId":"20250108183740.67022-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-1-eric.peijian@gmail.com","subject":"[PATCH v9 1/8] git-compat-util: add strtoul_ul() with error handling","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-08T18:37:32Z","receivedAt":"2025-01-08T18:37:59Z","isPatch":true,"body":"We already have strtoul_ui() and similar functions that provide proper\nerror handling using strtoul from the standard library. However,\nthere isn't currently a variant that returns an unsigned long.\nThis commit introduces strtoul_ul() to address this gap, enabling the\nreturn of an unsigned long with proper error handling.\n---\n git-compat-util.h | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex e283c46c6f..3bdb085624 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -1351,6 +1351,24 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n \treturn 0;\n }\n \n+// Converts a string to an unsigned long using the standard library's strtoul,\n+// with additional error handling to ensure robustness.\n+static inline int strtoul_ul(char const *s, int base, unsigned long *result)\n+{\n+\tunsigned long ul;\n+\tchar *p;\n+\n+\terrno = 0;\n+\t/* negative values would be accepted by strtoul */\n+\tif (strchr(s, '-'))\n+\t\treturn -1;\n+\tul = strtoul(s, &p, base);\n+\tif (errno || *p || p == s )\n+\t\treturn -1;\n+\t*result = ul;\n+\treturn 0;\n+}\n+\n static inline int strtol_i(char const *s, int base, int *result)\n {\n \tlong ul;\n-- \n2.47.0\n\n"},{"id":"510182","messageId":"20250108183740.67022-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-1-eric.peijian@gmail.com","subject":"[PATCH v9 2/8] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-08T18:37:33Z","receivedAt":"2025-01-08T18:38:00Z","isPatch":true,"body":"Some code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n fetch-pack.c       |  3 +--\n 2 files changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex b13561cf73..69ea642dc6 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -676,12 +676,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -689,9 +687,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -717,7 +713,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -727,7 +722,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3a227721ed..f5a63f12cd 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1329,9 +1329,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n-- \n2.47.0\n\n"},{"id":"510183","messageId":"20250108183740.67022-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-1-eric.peijian@gmail.com","subject":"[PATCH v9 3/8] cat-file: split test utility functions into a separate library file","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-08T18:37:34Z","receivedAt":"2025-01-08T18:38:01Z","isPatch":true,"body":"This refactor extracts utility functions from the cat-file's test\nt1006-cat-file.sh into a dedicated library file. The goal is to improve\ncode reuse and readability, enabling future tests to leverage these\nutilities without duplicating code\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 13 +------------\n 2 files changed, 17 insertions(+), 12 deletions(-)\n create mode 100644 t/lib-cat-file.sh\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..9fb20be308\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related tests.\n+\n+# Print a string without a trailing newline\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replaces them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string removing any leading spaces.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex ff9bf213aa..5c7d581ea2 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -3,6 +3,7 @@\n test_description='git cat-file'\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -98,18 +99,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     oid=$2\n-- \n2.47.0\n\n"},{"id":"510184","messageId":"20250108183740.67022-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-1-eric.peijian@gmail.com","subject":"[PATCH v9 4/8] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-08T18:37:35Z","receivedAt":"2025-01-08T18:38:02Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nRefactor write_fetch_command_and_capabilities() to a more\ngeneral-purpose function, write_command_and_capabilities(), enabling it\nto serve both fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162c\n(object-info: support for retrieving object info, 2021-04-20).\n\nFurthermore, write_command_and_capabilities() is moved to connect.c,\nmaking it accessible to additional commands in the future.\n\nTo move write_command_and_capabilities() to connect.c, we need to\nadjust how `advertise_sid` is managed. Previously,\nin fetch_pack.c, `advertise_sid` was a static variable, modified using\ngit_config_get_bool().\n\nIn connect.c, we now initialize `advertise_sid` at the beginning by\ndirectly using git_config_get_bool(). This change is safe because:\n\nIn the original fetch-pack.c code, there are only two places that\nwrite `advertise_sid` :\n1. In function do_fetch_pack:\n        if (!server_supports(\"session-id\"))\n                advertise_sid = 0;\n2. In function fetch_pack_config():\n        git_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, since do_fetch_pack() is only relevant for protocol v1, this\nassignment can be ignored in our refactor, as\nwrite_command_and_capabilities() is only used in protocol v2.\n\nAbout 2, git_config_get_bool() is from config.h and it is an out-of-box\ndependency of connect.c, so we can reuse it directly.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  8 ++++++++\n fetch-pack.c | 35 ++---------------------------------\n 3 files changed, 44 insertions(+), 33 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 10fad43e98..d89591f043 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -689,6 +689,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\tgit_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t\tthe_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t\tthe_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n enum protocol {\n \tPROTO_LOCAL = 1,\n \tPROTO_FILE,\ndiff --git a/connect.h b/connect.h\nindex 1645126c17..d904c73a85 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -30,4 +30,12 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+/*\n+ * Writes a command along with the requested\n+ * server capabilities/features into a request buffer.\n+ */\n+struct string_list;\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f5a63f12cd..78e7d38c47 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1317,37 +1317,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1358,7 +1327,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2186,7 +2155,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.47.0\n\n"},{"id":"510185","messageId":"20250108183740.67022-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-1-eric.peijian@gmail.com","subject":"[PATCH v9 5/8] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-08T18:37:36Z","receivedAt":"2025-01-08T18:38:03Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 78e7d38c47..51de82e414 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1648,18 +1648,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.47.0\n\n"},{"id":"510186","messageId":"20250108183740.67022-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-1-eric.peijian@gmail.com","subject":"[PATCH v9 6/8] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-08T18:37:37Z","receivedAt":"2025-01-08T18:38:05Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex c8694e3751..7a388d26d9 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -70,7 +70,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -78,6 +78,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.47.0\n\n"},{"id":"510187","messageId":"20250108183740.67022-8-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-1-eric.peijian@gmail.com","subject":"[PATCH v9 7/8] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-08T18:37:38Z","receivedAt":"2025-01-08T18:38:07Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nThis commit introduces client functions to interact with the server.\n\nCurrently, the client supports requesting a list of object IDs with\nthe 'size' feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client will return an error and exit.\n\nNotice that the entire request is written into req_buf before being\nsent to the remote. This approach follows the pattern used in the\n`send_fetch_request()` logic within fetch-pack.c.\nStreaming the request is not addressed in this patch.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Makefile            |  1 +\n fetch-object-info.c | 85 +++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h | 22 ++++++++++++\n fetch-pack.c        |  3 ++\n fetch-pack.h        |  2 ++\n transport-helper.c  | 11 ++++--\n transport.c         | 28 ++++++++++++++-\n transport.h         | 11 ++++++\n 8 files changed, 160 insertions(+), 3 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n\ndiff --git a/Makefile b/Makefile\nindex 97e8385b66..e8c6702b32 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1020,6 +1020,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..b279e06dc8\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,85 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"object-store-ll.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends git-cat-file object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"unsupported protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\tsize_index = i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\n+\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))\n+\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t\t\tobject_info_values.items[0].string,\n+\t\t\t\t\tobject_info_values.items[1 + size_index].string);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..6184d04d72\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"object-store-ll.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 51de82e414..704bc21b47 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1654,6 +1654,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 9d3470366f..119d3369f1 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\ndiff --git a/transport-helper.c b/transport-helper.c\nindex d457b42550..9da1547b2c 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -710,8 +710,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file\n+\t * remote-object-info require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -727,6 +727,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 10d820c333..b6a2052908 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -464,8 +465,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options && transport->smart_options->object_info\n+\t    && transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct packet_reader reader;\n+\t\tstruct object_info_args obj_info_args = { 0 };\n+\n+\t\tobj_info_args.server_options = transport->server_options;\n+\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n+\t\tstring_list_sort(obj_info_args.object_info_options);\n+\n+\t\tconnect_setup(transport, 0);\n+\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\t\tdata->version = discover_version(&reader);\n+\t\ttransport->hash_algo = reader.hash_algo;\n+\n+\t\tret = fetch_object_info(data->version, &obj_info_args, &reader,\n+\t\t\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n+\t\t\t\t\tdata->fd[1]);\n+\t\tgoto cleanup;\n \n-\tif (!data->finished_handshake) {\n+\t} else if (!data->finished_handshake) {\n \t\tint i;\n \t\tint must_list_refs = 0;\n \t\tfor (i = 0; i < nr_heads; i++) {\ndiff --git a/transport.h b/transport.h\nindex 44100fa9b7..e61e931863 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to retrieve only object-info.\n+\t * If object-info is not supported, the operation will error and exit.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.47.0\n\n"},{"id":"510188","messageId":"20250108183740.67022-9-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-1-eric.peijian@gmail.com","subject":"[PATCH v9 8/8] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-08T18:37:39Z","receivedAt":"2025-01-08T18:38:08Z","isPatch":true,"body":"Since the `info` command in cat-file --batch-command prints object info\nfor a given object, it is natural to add another command in cat-file\n--batch-command to print object info for a given object from a remote.\n\nAdd `remote-object-info` to cat-file --batch-command.\n\nWhile `info` takes object ids one at a time, this creates\noverhead when making requests to a server.So `remote-object-info`\ninstead can take multiple object ids at once.\n\ncat-file --batch-command is generally implemented in the following\nmanner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loop through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication overhead\nin the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Documentation/git-cat-file.txt         |  24 +-\n builtin/cat-file.c                     |  99 ++++\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/t1017-cat-file-remote-object-info.sh | 664 +++++++++++++++++++++++++\n 5 files changed, 797 insertions(+), 4 deletions(-)\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex d5890ae368..6a2f9fd752 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -149,6 +149,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tError when the `object-info` capability is not supported by the server.\n+\tError when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +297,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +306,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +323,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current the default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +408,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will error\n+and exit when they are in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 69ea642dc6..5fb71aaf91 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -27,6 +27,9 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -45,9 +48,12 @@ struct batch_options {\n \tchar input_delim;\n \tchar output_delim;\n \tconst char *format;\n+\tint use_remote_info;\n };\n \n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -579,6 +585,61 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT\n+\t*/\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\tif (object_info_oids.nr == 0) {\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\t}\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\n+\t\t/* 'objectsize' is the only option currently supported */\n+\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -670,6 +731,43 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\n+\tchar *line_to_split = xstrdup_or_null(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\n+\topt->use_remote_info = 1;\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\t\t}\n+\t}\n+\topt->use_remote_info = 0;\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -701,6 +799,7 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1},\n \t{ \"info\", parse_cmd_info, 1},\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1},\n \t{ \"flush\", NULL, 0},\n };\n \ndiff --git a/object-file.c b/object-file.c\nindex 5b792b3dd4..96f204c93a 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -3128,3 +3128,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex cd3bd5bd99..20208e1d4f 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -553,4 +553,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\t\t   void *data, enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..fd6c63cdb9\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,664 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n-- \n2.47.0\n\n"},{"id":"510290","messageId":"CAP8UFD0TBYi0CGOfNYH9FtL3odrqaxz0w1q36RK3gS-Z3pJAmw@mail.gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-9-eric.peijian@gmail.com","subject":"Re: [PATCH v9 8/8] cat-file: add remote-object-info to batch-command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-01-10T11:20:55Z","receivedAt":"2025-01-10T11:21:09Z","isPatch":true,"body":"On Wed, Jan 8, 2025 at 7:39 PM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> Since the `info` command in cat-file --batch-command prints object info\n\nNit: Everywhere in this commit message, it would be a bit clearer and\neasier to read with:\n\ns/cat-file --batch-command/`cat-file --batch-command`/\n\n> for a given object, it is natural to add another command in cat-file\n> --batch-command to print object info for a given object from a remote.\n>\n> Add `remote-object-info` to cat-file --batch-command.\n\ns/`remote-object-info`/a new `remote-object-info` command/\n\n[...]\n\n> To summarize, `remote-object-info` gets object info from the remote and\n> then loop through the object info passed in, printing the info.\n\ns/loop/loops/\n\n> +remote-object-info <remote> <object>...::\n> +       Print object info for object references `<object>` at specified\n> +       `<remote>` without downloading objects from the remote.\n> +       Error when the `object-info` capability is not supported by the server.\n\nI think it's more grammatically correct to use \"Error out when...\" or\n\"Raise an error when...\" than just \"Error when...\"\n\nAlso maybe: s/server/remote/\n\n> +       Error when no object references are provided.\n\nHere also \"Error out when...\" or \"Raise an error when...\"\n\n> +       This command may be combined with `--buffer`.\n\n[...]\n\n>  If no format is specified, the default format is `%(objectname)\n> -%(objecttype) %(objectsize)`.\n> +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> +WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n> +DO NOT RELY on the current the default format to stay the same!!!\n\ns/current the default/current default/\n\n>  CAVEATS\n>  -------\n>\n> +Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n> +currently not supported by the `remote-object-info` command, we will error\n\ns/error/raise an error/\n\nor maybe:\n\ns/error and exit/error out/\n\n> +and exit when they are in the format string.\n\ns/are/appear/\n\n> @@ -45,9 +48,12 @@ struct batch_options {\n>         char input_delim;\n>         char output_delim;\n>         const char *format;\n> +       int use_remote_info;\n\n\"unsigned int\" might be a bit better for bool fields like this.\n\nActually it seems to me that this field is set to 0 and 1 in some\nplaces but we never read it, so I wonder if it's actually useful.\n\n>  };\n\n> @@ -579,6 +585,61 @@ static void batch_one_object(const char *obj_name,\n>         object_context_release(&ctx);\n>  }\n>\n> +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> +{\n> +       int retval = 0;\n> +       struct remote *remote = NULL;\n> +       struct object_id oid;\n> +       struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> +       static struct transport *gtransport;\n> +\n> +       /*\n> +        * Change the format to \"%(objectname) %(objectsize)\" when\n> +        * remote-object-info command is used. Once we start supporting objecttype\n> +        * the default format should change to DEFAULT_FORMAT\n\ns/DEFAULT_FORMAT/DEFAULT_FORMAT./\n\n> +       */\n> +       if (!opt->format)\n> +               opt->format = \"%(objectname) %(objectsize)\";\n> +\n> +       remote = remote_get(argv[0]);\n> +       if (!remote)\n> +               die(_(\"must supply valid remote when using remote-object-info\"));\n> +\n> +       oid_array_clear(&object_info_oids);\n> +       for (size_t i = 1; i < argc; i++) {\n> +               if (get_oid_hex(argv[i], &oid))\n> +                       die(_(\"Not a valid object name %s\"), argv[i]);\n> +               oid_array_append(&object_info_oids, &oid);\n> +       }\n> +       if (object_info_oids.nr == 0) {\n> +               die(_(\"remote-object-info requires objects\"));\n> +       }\n\nWe prefer to drop '{' and '}' and use \"!X\" instead of \"X == 0\" when\npossible, so:\n\n       if (!object_info_oids.nr)\n               die(_(\"remote-object-info requires objects\"));\n\nThanks.\n"},{"id":"510305","messageId":"CAP8UFD1CoaEzqoCN+_6_qPk_5scf3mhvpCRrsuxc1K=ZObUgiA@mail.gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-2-eric.peijian@gmail.com","subject":"Re: [PATCH v9 1/8] git-compat-util: add strtoul_ul() with error handling","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-01-10T11:33:29Z","receivedAt":"2025-01-10T11:33:42Z","isPatch":true,"body":"On Wed, Jan 8, 2025 at 7:38 PM Eric Ju <eric.peijian@gmail.com> wrote:\n\n> +// Converts a string to an unsigned long using the standard library's strtoul,\n> +// with additional error handling to ensure robustness.\n\nWe use comments like this:\n\n/*\n * Converts a string to an unsigned long using the standard library's strtoul,\n * with additional error handling to ensure robustness.\n */\n\nAlso we use the imperative mood in comments before a function, so:\n\ns/Converts/Convert/\n"},{"id":"510306","messageId":"CAP8UFD2uVD4S8pWpg-oOdhNi_0m0ZTGOvaJX8MpZGFU9yAbV7w@mail.gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-3-eric.peijian@gmail.com","subject":"Re: [PATCH v9 2/8] cat-file: add declaration of variable i inside its for loop","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-01-10T11:39:39Z","receivedAt":"2025-01-10T11:39:53Z","isPatch":true,"body":"On Wed, Jan 8, 2025 at 7:38 PM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> Some code used in this series declares variable i and only uses it\n> in a for loop, not in any other logic outside the loop.\n>\n> Change the declaration of i to be inside the for loop for readability.\n\nIt might be nice to say that, while at it, we also change the type\nfrom \"int\" to \"size_t\" where the latter makes more sense.\n"},{"id":"510326","messageId":"CAP8UFD1U3P6ef9x58MBvrhDhdMn_3qe+Pjmf1L=Mi8Q5Ge0EPg@mail.gmail.com","threadId":"61695","inReplyTo":"20250108183740.67022-4-eric.peijian@gmail.com","subject":"Re: [PATCH v9 3/8] cat-file: split test utility functions into a separate library file","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-01-10T14:26:41Z","receivedAt":"2025-01-10T14:26:55Z","isPatch":true,"body":"About the commit subject, maybe something like the following would be\na bit shorter:\n\nt1006: split test utility functions into new \"lib-cat-file.sh\"\n\nOn Wed, Jan 8, 2025 at 7:38 PM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> This refactor extracts utility functions from the cat-file's test\n\ns/test/test script/\n\n> t1006-cat-file.sh into a dedicated library file. The goal is to improve\n\ns/a dedicated library file/a new \"lib-cat-file.sh\" dedicated library file/\n\n> code reuse and readability, enabling future tests to leverage these\n> utilities without duplicating code\n\ns/code/code./\n\n> diff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\n> new file mode 100644\n> index 0000000000..9fb20be308\n> --- /dev/null\n> +++ b/t/lib-cat-file.sh\n> @@ -0,0 +1,16 @@\n> +# Library of git-cat-file related tests.\n\ns/tests/test functions/\n\n> +\n> +# Print a string without a trailing newline\n\ns/newline/newline./\n\n> +echo_without_newline () {\n> +       printf '%s' \"$*\"\n> +}\n> +\n> +# Print a string without newlines and replaces them with a NULL character (\\0).\n\ns/replaces/replace/\n\n> +echo_without_newline_nul () {\n> +       echo_without_newline \"$@\" | tr '\\n' '\\0'\n> +}\n> +\n> +# Calculate the length of a string removing any leading spaces.\n\nThis might be a bit misleading as leading spaces are removed from the\noutput from `wc -c`, not from the string.\n\n> +strlen () {\n> +       echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> +}\n"},{"id":"510465","messageId":"CAN2LT1AXfFEaTtex5+Lp22Y7hqneXk98rJw+D9gKxDajg3LohQ@mail.gmail.com","threadId":"61695","inReplyTo":"CAP8UFD0TBYi0CGOfNYH9FtL3odrqaxz0w1q36RK3gS-Z3pJAmw@mail.gmail.com","subject":"Re: [PATCH v9 8/8] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T01:24:23Z","receivedAt":"2025-01-14T01:24:35Z","isPatch":true,"body":"On Fri, Jan 10, 2025 at 6:21 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Wed, Jan 8, 2025 at 7:39 PM Eric Ju <eric.peijian@gmail.com> wrote:\n> >\n> > Since the `info` command in cat-file --batch-command prints object info\n>\n> Nit: Everywhere in this commit message, it would be a bit clearer and\n> easier to read with:\n>\n> s/cat-file --batch-command/`cat-file --batch-command`/\n>\n\nThank you. Fixed in v10.\n\n> > for a given object, it is natural to add another command in cat-file\n> > --batch-command to print object info for a given object from a remote.\n> >\n> > Add `remote-object-info` to cat-file --batch-command.\n>\n> s/`remote-object-info`/a new `remote-object-info` command/\n>\n\nThank you. Fixed in v10.\n\n> [...]\n>\n> > To summarize, `remote-object-info` gets object info from the remote and\n> > then loop through the object info passed in, printing the info.\n>\n> s/loop/loops/\n>\n\nThank you. Fixed in v10.\n\n> > +remote-object-info <remote> <object>...::\n> > +       Print object info for object references `<object>` at specified\n> > +       `<remote>` without downloading objects from the remote.\n> > +       Error when the `object-info` capability is not supported by the server.\n>\n> I think it's more grammatically correct to use \"Error out when...\" or\n> \"Raise an error when...\" than just \"Error when...\"\n>\n> Also maybe: s/server/remote/\n>\n\nThank you. Fixed in v10. I will use \"Raise an error when...\"\n\n> > +       Error when no object references are provided.\n>\n> Here also \"Error out when...\" or \"Raise an error when...\"\n>\n\nThank you. Fixed in v10.\n\n> > +       This command may be combined with `--buffer`.\n>\n> [...]\n>\n> >  If no format is specified, the default format is `%(objectname)\n> > -%(objecttype) %(objectsize)`.\n> > +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> > +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n> > +WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n> > +DO NOT RELY on the current the default format to stay the same!!!\n>\n> s/current the default/current default/\n>\n\nThank you. Fixed in v10.\n\n> >  CAVEATS\n> >  -------\n> >\n> > +Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n> > +currently not supported by the `remote-object-info` command, we will error\n>\n> s/error/raise an error/\n>\n> or maybe:\n>\n> s/error and exit/error out/\n>\n\nThank you. Replaced with \"raise an error\".\n\n> > +and exit when they are in the format string.\n>\n> s/are/appear/\n>\n\nThank you. Fixed in v10.\n\n> > @@ -45,9 +48,12 @@ struct batch_options {\n> >         char input_delim;\n> >         char output_delim;\n> >         const char *format;\n> > +       int use_remote_info;\n>\n> \"unsigned int\" might be a bit better for bool fields like this.\n>\n> Actually it seems to me that this field is set to 0 and 1 in some\n> places but we never read it, so I wonder if it's actually useful.\n>\n\nThank you. It is used at all and removed in v10.\n\n> >  };\n>\n> > @@ -579,6 +585,61 @@ static void batch_one_object(const char *obj_name,\n> >         object_context_release(&ctx);\n> >  }\n> >\n> > +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> > +{\n> > +       int retval = 0;\n> > +       struct remote *remote = NULL;\n> > +       struct object_id oid;\n> > +       struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> > +       static struct transport *gtransport;\n> > +\n> > +       /*\n> > +        * Change the format to \"%(objectname) %(objectsize)\" when\n> > +        * remote-object-info command is used. Once we start supporting objecttype\n> > +        * the default format should change to DEFAULT_FORMAT\n>\n> s/DEFAULT_FORMAT/DEFAULT_FORMAT./\n>\n\nThank you. Fixed in v10.\n\n> > +       */\n> > +       if (!opt->format)\n> > +               opt->format = \"%(objectname) %(objectsize)\";\n> > +\n> > +       remote = remote_get(argv[0]);\n> > +       if (!remote)\n> > +               die(_(\"must supply valid remote when using remote-object-info\"));\n> > +\n> > +       oid_array_clear(&object_info_oids);\n> > +       for (size_t i = 1; i < argc; i++) {\n> > +               if (get_oid_hex(argv[i], &oid))\n> > +                       die(_(\"Not a valid object name %s\"), argv[i]);\n> > +               oid_array_append(&object_info_oids, &oid);\n> > +       }\n> > +       if (object_info_oids.nr == 0) {\n> > +               die(_(\"remote-object-info requires objects\"));\n> > +       }\n>\n> We prefer to drop '{' and '}' and use \"!X\" instead of \"X == 0\" when\n> possible, so:\n>\n>        if (!object_info_oids.nr)\n>                die(_(\"remote-object-info requires objects\"));\n>\n> Thanks.\n\nThank you. Fixed in v10.\n"},{"id":"510466","messageId":"CAN2LT1A8Ns591s4zrZfccJDcLmXb-6umM6UvzJOA_vV4Oendkg@mail.gmail.com","threadId":"61695","inReplyTo":"CAP8UFD1U3P6ef9x58MBvrhDhdMn_3qe+Pjmf1L=Mi8Q5Ge0EPg@mail.gmail.com","subject":"Re: [PATCH v9 3/8] cat-file: split test utility functions into a separate library file","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T01:33:11Z","receivedAt":"2025-01-14T01:33:22Z","isPatch":true,"body":"Thank you Christian. They are all fixed in v10\n\nOn Fri, Jan 10, 2025 at 9:26 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> About the commit subject, maybe something like the following would be\n> a bit shorter:\n>\n> t1006: split test utility functions into new \"lib-cat-file.sh\"\n>\n> On Wed, Jan 8, 2025 at 7:38 PM Eric Ju <eric.peijian@gmail.com> wrote:\n> >\n> > This refactor extracts utility functions from the cat-file's test\n>\n> s/test/test script/\n>\n> > t1006-cat-file.sh into a dedicated library file. The goal is to improve\n>\n> s/a dedicated library file/a new \"lib-cat-file.sh\" dedicated library file/\n>\n> > code reuse and readability, enabling future tests to leverage these\n> > utilities without duplicating code\n>\n> s/code/code./\n>\n> > diff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\n> > new file mode 100644\n> > index 0000000000..9fb20be308\n> > --- /dev/null\n> > +++ b/t/lib-cat-file.sh\n> > @@ -0,0 +1,16 @@\n> > +# Library of git-cat-file related tests.\n>\n> s/tests/test functions/\n>\n> > +\n> > +# Print a string without a trailing newline\n>\n> s/newline/newline./\n>\n> > +echo_without_newline () {\n> > +       printf '%s' \"$*\"\n> > +}\n> > +\n> > +# Print a string without newlines and replaces them with a NULL character (\\0).\n>\n> s/replaces/replace/\n>\n> > +echo_without_newline_nul () {\n> > +       echo_without_newline \"$@\" | tr '\\n' '\\0'\n> > +}\n> > +\n> > +# Calculate the length of a string removing any leading spaces.\n>\n> This might be a bit misleading as leading spaces are removed from the\n> output from `wc -c`, not from the string.\n>\n\nYes, I will just change it to \"Calculate the length of a string. \"\n\n> > +strlen () {\n> > +       echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> > +}\n"},{"id":"510467","messageId":"CAN2LT1B6xCvR2+Fkj3nvz28P1csAAw7Ukd1YMXw8tZZQO16UCQ@mail.gmail.com","threadId":"61695","inReplyTo":"CAP8UFD2uVD4S8pWpg-oOdhNi_0m0ZTGOvaJX8MpZGFU9yAbV7w@mail.gmail.com","subject":"Re: [PATCH v9 2/8] cat-file: add declaration of variable i inside its for loop","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T01:36:32Z","receivedAt":"2025-01-14T01:36:43Z","isPatch":true,"body":"Thank you. Add that in the commit message.\n\nOn Fri, Jan 10, 2025 at 6:39 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Wed, Jan 8, 2025 at 7:38 PM Eric Ju <eric.peijian@gmail.com> wrote:\n> >\n> > Some code used in this series declares variable i and only uses it\n> > in a for loop, not in any other logic outside the loop.\n> >\n> > Change the declaration of i to be inside the for loop for readability.\n>\n> It might be nice to say that, while at it, we also change the type\n> from \"int\" to \"size_t\" where the latter makes more sense.\n"},{"id":"510468","messageId":"CAN2LT1CSbdptV_y7iyWeWrBRNM-ToDm=aHDha1Hc7FHa12bSZg@mail.gmail.com","threadId":"61695","inReplyTo":"CAP8UFD1CoaEzqoCN+_6_qPk_5scf3mhvpCRrsuxc1K=ZObUgiA@mail.gmail.com","subject":"Re: [PATCH v9 1/8] git-compat-util: add strtoul_ul() with error handling","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T01:39:07Z","receivedAt":"2025-01-14T01:39:18Z","isPatch":true,"body":"Thank you. Fixed in v10.\n\nOn Fri, Jan 10, 2025 at 6:33 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Wed, Jan 8, 2025 at 7:38 PM Eric Ju <eric.peijian@gmail.com> wrote:\n>\n> > +// Converts a string to an unsigned long using the standard library's strtoul,\n> > +// with additional error handling to ensure robustness.\n>\n> We use comments like this:\n>\n> /*\n>  * Converts a string to an unsigned long using the standard library's strtoul,\n>  * with additional error handling to ensure robustness.\n>  */\n>\n> Also we use the imperative mood in comments before a function, so:\n>\n> s/Converts/Convert/\n"},{"id":"510471","messageId":"20250114021502.41499-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH v10 0/8] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T02:14:52Z","receivedAt":"2025-01-14T02:15:26Z","isPatch":true,"body":"This patch series is a continuation of Calvin Wan’s (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info\ncommand at [1].\n\nSometimes it is beneficial to retrieve information about an object without\nhaving to download it completely. The server logic for retrieving size has\nalready been implemented and merged in \"a2ba162cda (object-info: support for\nretrieving object info, 2021-04-20)\"[2]. This patch series implement the client\noption for it.\n\nThis patch series add the `remote-object-info` command to\n`cat-file --batch-command`. This command allows the client to make an\nobject-info command request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info capability,\n`cat-file --batch-command` will die.\n\nIf a user attempts to use `remote-object-info` with protocol v1,,\n`cat-file --batch-command` will die.\n\nCurrently, only the size (%(objectsize)) is supported in this implementation.\nThe type (%(objecttype)) is not included in this patch series, as it is not yet\nsupported on the server side either. The plan is to implement the necessary\nlogic for both the server and client in a subsequent series.\n\nThe default format for remote-object-info is set to %(objectname) %(objectsize).\nOnce %(objecttype) is supported, the default format will be unified accordingly.\n\nIf the batch command format includes unsupported fields such as %(objecttype),\n%(objectsize:disk), or %(deltabase), the command will terminate with an error.\n\nChanges since V9\n================\n- Refactored documentation for improved clarity.\n- Refactored commit messages to provide more detailed and relevant information.\n- Revised comments to align with best practices and reduce potential confusion or misinterpretation.\n- Fixed grammatical errors and typos throughout the code and documentation.\n- Removed unused variables to improve code cleanliness and maintainability.\n\nCalvin Wan (4):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n\nEric Ju (4):\n  git-compat-util: add strtoul_ul() with error handling\n  cat-file: add declaration of variable i inside its for loop\n  t1006: split test utility functions into new \"lib-cat-file.sh\"\n  cat-file: add remote-object-info to batch-command\n\n Documentation/git-cat-file.txt         |  24 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 107 +++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    |  85 ++++\n fetch-object-info.h                    |  22 +\n fetch-pack.c                           |  51 +-\n fetch-pack.h                           |   2 +\n git-compat-util.h                      |  20 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 664 +++++++++++++++++++++++++\n transport-helper.c                     |  11 +-\n transport.c                            |  28 +-\n transport.h                            |  11 +\n 19 files changed, 1047 insertions(+), 68 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\nRange-diff against v9:\n1:  0a77ace719 ! 1:  a567de3dc6 git-compat-util: add strtoul_ul() with error handling\n    @@ git-compat-util.h: static inline int strtoul_ui(char const *s, int base, unsigne\n      \treturn 0;\n      }\n      \n    -+// Converts a string to an unsigned long using the standard library's strtoul,\n    -+// with additional error handling to ensure robustness.\n    ++/*\n    ++ * Convert a string to an unsigned long using the standard library's strtoul,\n    ++ * with additional error handling to ensure robustness.\n    ++ */\n     +static inline int strtoul_ul(char const *s, int base, unsigned long *result)\n     +{\n     +\tunsigned long ul;\n2:  51a0a48d7b ! 2:  8d5140b111 cat-file: add declaration of variable i inside its for loop\n    @@ Commit message\n         in a for loop, not in any other logic outside the loop.\n     \n         Change the declaration of i to be inside the for loop for readability.\n    +    While at it, we also change its type from \"int\" to \"size_t\" where the latter makes more sense.\n     \n         Helped-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n3:  fa1d6678a0 ! 3:  42d0539e9b cat-file: split test utility functions into a separate library file\n    @@ Metadata\n     Author: Eric Ju <eric.peijian@gmail.com>\n     \n      ## Commit message ##\n    -    cat-file: split test utility functions into a separate library file\n    +    t1006: split test utility functions into new \"lib-cat-file.sh\"\n     \n         This refactor extracts utility functions from the cat-file's test\n    -    t1006-cat-file.sh into a dedicated library file. The goal is to improve\n    -    code reuse and readability, enabling future tests to leverage these\n    -    utilities without duplicating code\n    +    script \"t1006-cat-file.sh\" into a new \"lib-cat-file.sh\" dedicated\n    +    library file. The goal is to improve code reuse and readability,\n    +    enabling future tests to leverage these utilities without duplicating\n    +    code.\n     \n      ## t/lib-cat-file.sh (new) ##\n     @@\n    -+# Library of git-cat-file related tests.\n    ++# Library of git-cat-file related test functions.\n     +\n    -+# Print a string without a trailing newline\n    ++# Print a string without a trailing newline.\n     +echo_without_newline () {\n     +\tprintf '%s' \"$*\"\n     +}\n     +\n    -+# Print a string without newlines and replaces them with a NULL character (\\0).\n    ++# Print a string without newlines and replace them with a NULL character (\\0).\n     +echo_without_newline_nul () {\n     +\techo_without_newline \"$@\" | tr '\\n' '\\0'\n     +}\n     +\n    -+# Calculate the length of a string removing any leading spaces.\n    ++# Calculate the length of a string.\n     +strlen () {\n     +\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n     +}\n4:  61dd598576 = 4:  8fdb2c6f81 fetch-pack: refactor packet writing\n5:  106e776dda = 5:  7d5f53de6e fetch-pack: move fetch initialization\n6:  fe9366b59d = 6:  17583ebcd9 serve: advertise object-info feature\n7:  09646f6517 = 7:  22d5eb26d1 transport: add client support for object-info\n8:  99c450fc7e ! 8:  0698aa3606 cat-file: add remote-object-info to batch-command\n    @@ Metadata\n      ## Commit message ##\n         cat-file: add remote-object-info to batch-command\n     \n    -    Since the `info` command in cat-file --batch-command prints object info\n    -    for a given object, it is natural to add another command in cat-file\n    -    --batch-command to print object info for a given object from a remote.\n    +    Since the `info` command in `cat-file --batch-command` prints object\n    +    info for a given object, it is natural to add another command in\n    +    `cat-file --batch-command` to print object info for a given object\n    +    from a remote.\n     \n    -    Add `remote-object-info` to cat-file --batch-command.\n    +    Add `remote-object-info` to `cat-file --batch-command`.\n     \n         While `info` takes object ids one at a time, this creates\n    -    overhead when making requests to a server.So `remote-object-info`\n    +    overhead when making requests to a server. So `remote-object-info`\n         instead can take multiple object ids at once.\n     \n    -    cat-file --batch-command is generally implemented in the following\n    -    manner:\n    +    The `cat-file --batch-command` command is generally implemented in\n    +    the following manner:\n     \n          - Receive and parse input from user\n          - Call respective function attached to command\n    @@ Commit message\n                 - Get object info, print object info\n     \n         To summarize, `remote-object-info` gets object info from the remote and\n    -    then loop through the object info passed in, printing the info.\n    +    then loops through the object info passed in, printing the info.\n     \n    -    In order for remote-object-info to avoid remote communication overhead\n    -    in the non-buffer mode, the objects are passed in as such:\n    +    In order for `remote-object-info` to avoid remote communication\n    +    overhead in the non-buffer mode, the objects are passed in as such:\n     \n         remote-object-info <remote> <oid> <oid> ... <oid>\n     \n    @@ Documentation/git-cat-file.txt: info <object>::\n     +remote-object-info <remote> <object>...::\n     +\tPrint object info for object references `<object>` at specified\n     +\t`<remote>` without downloading objects from the remote.\n    -+\tError when the `object-info` capability is not supported by the server.\n    -+\tError when no object references are provided.\n    ++\tRaise an error when the `object-info` capability is not supported by the remote.\n    ++\tRaise an error when no object references are provided.\n     +\tThis command may be combined with `--buffer`.\n     +\n      flush::\n    @@ Documentation/git-cat-file.txt: newline. The available atoms are:\n     +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n     +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n     +WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n    -+DO NOT RELY on the current the default format to stay the same!!!\n    ++DO NOT RELY on the current default format to stay the same!!!\n      \n      If `--batch` is specified, or if `--batch-command` is used with the `contents`\n      command, the object information is followed by the object contents (consisting\n    @@ Documentation/git-cat-file.txt: scripting purposes.\n      -------\n      \n     +Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n    -+currently not supported by the `remote-object-info` command, we will error\n    -+and exit when they are in the format string.\n    ++currently not supported by the `remote-object-info` command, we will raise\n    ++an error and exit when they appear in the format string.\n     +\n      Note that the sizes of objects on disk are reported accurately, but care\n      should be taken in drawing conclusions about which refs or objects are\n    @@ builtin/cat-file.c\n      enum batch_mode {\n      \tBATCH_MODE_CONTENTS,\n     @@ builtin/cat-file.c: struct batch_options {\n    - \tchar input_delim;\n    - \tchar output_delim;\n    - \tconst char *format;\n    -+\tint use_remote_info;\n      };\n      \n      static const char *force_path;\n    @@ builtin/cat-file.c: static void batch_one_object(const char *obj_name,\n     +\t/*\n     +\t * Change the format to \"%(objectname) %(objectsize)\" when\n     +\t * remote-object-info command is used. Once we start supporting objecttype\n    -+\t * the default format should change to DEFAULT_FORMAT\n    ++\t * the default format should change to DEFAULT_FORMAT.\n     +\t*/\n     +\tif (!opt->format)\n     +\t\topt->format = \"%(objectname) %(objectsize)\";\n    @@ builtin/cat-file.c: static void batch_one_object(const char *obj_name,\n     +\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n     +\t\toid_array_append(&object_info_oids, &oid);\n     +\t}\n    -+\tif (object_info_oids.nr == 0) {\n    ++\tif (!object_info_oids.nr)\n     +\t\tdie(_(\"remote-object-info requires objects\"));\n    -+\t}\n    ++\n     +\tgtransport = transport_get(remote, NULL);\n     +\tif (gtransport->smart_options) {\n     +\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n    @@ builtin/cat-file.c: static void parse_cmd_info(struct batch_options *opt,\n     +\tif (get_remote_info(opt, count, argv))\n     +\t\tgoto cleanup;\n     +\n    -+\topt->use_remote_info = 1;\n     +\tdata->skip_object_info = 1;\n     +\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n     +\t\tdata->oid = object_info_oids.oid[i];\n    @@ builtin/cat-file.c: static void parse_cmd_info(struct batch_options *opt,\n     +\t\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n     +\t\t}\n     +\t}\n    -+\topt->use_remote_info = 0;\n     +\tdata->skip_object_info = 0;\n     +\n     +cleanup:\n-- \n2.47.1\n\n"},{"id":"510470","messageId":"20250114021502.41499-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250114021502.41499-1-eric.peijian@gmail.com","subject":"[PATCH v10 1/8] git-compat-util: add strtoul_ul() with error handling","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T02:14:53Z","receivedAt":"2025-01-14T02:15:27Z","isPatch":true,"body":"We already have strtoul_ui() and similar functions that provide proper\nerror handling using strtoul from the standard library. However,\nthere isn't currently a variant that returns an unsigned long.\nThis commit introduces strtoul_ul() to address this gap, enabling the\nreturn of an unsigned long with proper error handling.\n---\n git-compat-util.h | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex e283c46c6f..f2935750bf 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -1351,6 +1351,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n \treturn 0;\n }\n \n+/*\n+ * Convert a string to an unsigned long using the standard library's strtoul,\n+ * with additional error handling to ensure robustness.\n+ */\n+static inline int strtoul_ul(char const *s, int base, unsigned long *result)\n+{\n+\tunsigned long ul;\n+\tchar *p;\n+\n+\terrno = 0;\n+\t/* negative values would be accepted by strtoul */\n+\tif (strchr(s, '-'))\n+\t\treturn -1;\n+\tul = strtoul(s, &p, base);\n+\tif (errno || *p || p == s )\n+\t\treturn -1;\n+\t*result = ul;\n+\treturn 0;\n+}\n+\n static inline int strtol_i(char const *s, int base, int *result)\n {\n \tlong ul;\n-- \n2.47.1\n\n"},{"id":"510472","messageId":"20250114021502.41499-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250114021502.41499-1-eric.peijian@gmail.com","subject":"[PATCH v10 2/8] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T02:14:54Z","receivedAt":"2025-01-14T02:15:29Z","isPatch":true,"body":"Some code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\nWhile at it, we also change its type from \"int\" to \"size_t\" where the latter makes more sense.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n fetch-pack.c       |  3 +--\n 2 files changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex b13561cf73..69ea642dc6 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -676,12 +676,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -689,9 +687,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -717,7 +713,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -727,7 +722,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3a227721ed..f5a63f12cd 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1329,9 +1329,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n-- \n2.47.1\n\n"},{"id":"510473","messageId":"20250114021502.41499-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250114021502.41499-1-eric.peijian@gmail.com","subject":"[PATCH v10 3/8] t1006: split test utility functions into new \"lib-cat-file.sh\"","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T02:14:55Z","receivedAt":"2025-01-14T02:15:29Z","isPatch":true,"body":"This refactor extracts utility functions from the cat-file's test\nscript \"t1006-cat-file.sh\" into a new \"lib-cat-file.sh\" dedicated\nlibrary file. The goal is to improve code reuse and readability,\nenabling future tests to leverage these utilities without duplicating\ncode.\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 13 +------------\n 2 files changed, 17 insertions(+), 12 deletions(-)\n create mode 100644 t/lib-cat-file.sh\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..44af232d74\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex ff9bf213aa..5c7d581ea2 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -3,6 +3,7 @@\n test_description='git cat-file'\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -98,18 +99,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     oid=$2\n-- \n2.47.1\n\n"},{"id":"510474","messageId":"20250114021502.41499-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250114021502.41499-1-eric.peijian@gmail.com","subject":"[PATCH v10 5/8] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T02:14:57Z","receivedAt":"2025-01-14T02:15:32Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 78e7d38c47..51de82e414 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1648,18 +1648,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.47.1\n\n"},{"id":"510475","messageId":"20250114021502.41499-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250114021502.41499-1-eric.peijian@gmail.com","subject":"[PATCH v10 4/8] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T02:14:56Z","receivedAt":"2025-01-14T02:15:32Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nRefactor write_fetch_command_and_capabilities() to a more\ngeneral-purpose function, write_command_and_capabilities(), enabling it\nto serve both fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162c\n(object-info: support for retrieving object info, 2021-04-20).\n\nFurthermore, write_command_and_capabilities() is moved to connect.c,\nmaking it accessible to additional commands in the future.\n\nTo move write_command_and_capabilities() to connect.c, we need to\nadjust how `advertise_sid` is managed. Previously,\nin fetch_pack.c, `advertise_sid` was a static variable, modified using\ngit_config_get_bool().\n\nIn connect.c, we now initialize `advertise_sid` at the beginning by\ndirectly using git_config_get_bool(). This change is safe because:\n\nIn the original fetch-pack.c code, there are only two places that\nwrite `advertise_sid` :\n1. In function do_fetch_pack:\n        if (!server_supports(\"session-id\"))\n                advertise_sid = 0;\n2. In function fetch_pack_config():\n        git_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, since do_fetch_pack() is only relevant for protocol v1, this\nassignment can be ignored in our refactor, as\nwrite_command_and_capabilities() is only used in protocol v2.\n\nAbout 2, git_config_get_bool() is from config.h and it is an out-of-box\ndependency of connect.c, so we can reuse it directly.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  8 ++++++++\n fetch-pack.c | 35 ++---------------------------------\n 3 files changed, 44 insertions(+), 33 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 10fad43e98..d89591f043 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -689,6 +689,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\tgit_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t\tthe_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t\tthe_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n enum protocol {\n \tPROTO_LOCAL = 1,\n \tPROTO_FILE,\ndiff --git a/connect.h b/connect.h\nindex 1645126c17..d904c73a85 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -30,4 +30,12 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+/*\n+ * Writes a command along with the requested\n+ * server capabilities/features into a request buffer.\n+ */\n+struct string_list;\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f5a63f12cd..78e7d38c47 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1317,37 +1317,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1358,7 +1327,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2186,7 +2155,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.47.1\n\n"},{"id":"510476","messageId":"20250114021502.41499-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250114021502.41499-1-eric.peijian@gmail.com","subject":"[PATCH v10 6/8] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T02:14:58Z","receivedAt":"2025-01-14T02:15:33Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex c8694e3751..7a388d26d9 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -70,7 +70,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -78,6 +78,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.47.1\n\n"},{"id":"510477","messageId":"20250114021502.41499-8-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250114021502.41499-1-eric.peijian@gmail.com","subject":"[PATCH v10 7/8] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T02:14:59Z","receivedAt":"2025-01-14T02:15:34Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nThis commit introduces client functions to interact with the server.\n\nCurrently, the client supports requesting a list of object IDs with\nthe 'size' feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client will return an error and exit.\n\nNotice that the entire request is written into req_buf before being\nsent to the remote. This approach follows the pattern used in the\n`send_fetch_request()` logic within fetch-pack.c.\nStreaming the request is not addressed in this patch.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Makefile            |  1 +\n fetch-object-info.c | 85 +++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h | 22 ++++++++++++\n fetch-pack.c        |  3 ++\n fetch-pack.h        |  2 ++\n transport-helper.c  | 11 ++++--\n transport.c         | 28 ++++++++++++++-\n transport.h         | 11 ++++++\n 8 files changed, 160 insertions(+), 3 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n\ndiff --git a/Makefile b/Makefile\nindex 97e8385b66..e8c6702b32 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1020,6 +1020,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..b279e06dc8\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,85 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"object-store-ll.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends git-cat-file object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"unsupported protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\tsize_index = i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\n+\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))\n+\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t\t\tobject_info_values.items[0].string,\n+\t\t\t\t\tobject_info_values.items[1 + size_index].string);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..6184d04d72\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"object-store-ll.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 51de82e414..704bc21b47 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1654,6 +1654,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 9d3470366f..119d3369f1 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\ndiff --git a/transport-helper.c b/transport-helper.c\nindex d457b42550..9da1547b2c 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -710,8 +710,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file\n+\t * remote-object-info require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -727,6 +727,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 10d820c333..b6a2052908 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -464,8 +465,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options && transport->smart_options->object_info\n+\t    && transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct packet_reader reader;\n+\t\tstruct object_info_args obj_info_args = { 0 };\n+\n+\t\tobj_info_args.server_options = transport->server_options;\n+\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n+\t\tstring_list_sort(obj_info_args.object_info_options);\n+\n+\t\tconnect_setup(transport, 0);\n+\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\t\tdata->version = discover_version(&reader);\n+\t\ttransport->hash_algo = reader.hash_algo;\n+\n+\t\tret = fetch_object_info(data->version, &obj_info_args, &reader,\n+\t\t\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n+\t\t\t\t\tdata->fd[1]);\n+\t\tgoto cleanup;\n \n-\tif (!data->finished_handshake) {\n+\t} else if (!data->finished_handshake) {\n \t\tint i;\n \t\tint must_list_refs = 0;\n \t\tfor (i = 0; i < nr_heads; i++) {\ndiff --git a/transport.h b/transport.h\nindex 44100fa9b7..e61e931863 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to retrieve only object-info.\n+\t * If object-info is not supported, the operation will error and exit.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.47.1\n\n"},{"id":"510478","messageId":"20250114021502.41499-9-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250114021502.41499-1-eric.peijian@gmail.com","subject":"[PATCH v10 8/8] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-01-14T02:15:00Z","receivedAt":"2025-01-14T02:15:35Z","isPatch":true,"body":"Since the `info` command in `cat-file --batch-command` prints object\ninfo for a given object, it is natural to add another command in\n`cat-file --batch-command` to print object info for a given object\nfrom a remote.\n\nAdd `remote-object-info` to `cat-file --batch-command`.\n\nWhile `info` takes object ids one at a time, this creates\noverhead when making requests to a server. So `remote-object-info`\ninstead can take multiple object ids at once.\n\nThe `cat-file --batch-command` command is generally implemented in\nthe following manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for `remote-object-info` to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Documentation/git-cat-file.txt         |  24 +-\n builtin/cat-file.c                     |  96 ++++\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/t1017-cat-file-remote-object-info.sh | 664 +++++++++++++++++++++++++\n 5 files changed, 794 insertions(+), 4 deletions(-)\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex d5890ae368..4fbb3a077b 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -149,6 +149,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +297,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +306,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +323,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +408,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will raise\n+an error and exit when they appear in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 69ea642dc6..78b16e10bf 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -27,6 +27,9 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -48,6 +51,8 @@ struct batch_options {\n };\n \n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -579,6 +584,61 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT.\n+\t*/\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\tif (!object_info_oids.nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\n+\t\t/* 'objectsize' is the only option currently supported */\n+\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -670,6 +730,41 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\n+\tchar *line_to_split = xstrdup_or_null(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -701,6 +796,7 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1},\n \t{ \"info\", parse_cmd_info, 1},\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1},\n \t{ \"flush\", NULL, 0},\n };\n \ndiff --git a/object-file.c b/object-file.c\nindex 5b792b3dd4..96f204c93a 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -3128,3 +3128,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex cd3bd5bd99..20208e1d4f 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -553,4 +553,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\t\t   void *data, enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..fd6c63cdb9\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,664 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n-- \n2.47.1\n\n"},{"id":"511613","messageId":"20250201020321.GA4081169@coredump.intra.peff.net","threadId":"61695","inReplyTo":"20250114021502.41499-9-eric.peijian@gmail.com","subject":"Re: [PATCH v10 8/8] cat-file: add remote-object-info to batch-command","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-02-01T02:03:21Z","receivedAt":"2025-02-01T02:03:23Z","isPatch":true,"body":"On Mon, Jan 13, 2025 at 09:15:00PM -0500, Eric Ju wrote:\n\n> +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> +\t\t\t\t\t const char *line, struct strbuf *output,\n> +\t\t\t\t\t struct expand_data *data)\n> +{\n> +\tint count;\n> +\tconst char **argv;\n> +\n> +\tchar *line_to_split = xstrdup_or_null(line);\n> +\tcount = split_cmdline(line_to_split, &argv);\n> +\tif (get_remote_info(opt, count, argv))\n> +\t\tgoto cleanup;\n\nCoverity complains that split_cmdline() can return a negative value when\nthe input is malformed, which we then feed to get_remote_info(). If I\nunderstand correctly (from my very brief glance at the series), that\nstring would be under the control of the untrusted client?\n\nI _think_ an attacker can't do anything too bad here, since\nget_remote_info() also takes a signed int, and so iterating from 0 will\njust find no entries. But probably we should explicitly check for error\nand bail.\n\nWhile just looking at this code from a security perspective, two other\nthings occur to me:\n\n  1. Calling xstrdup_or_null() implies that \"line\" may be NULL, which\n     would make \"line_to_split\" also NULL. But I think split_cmdline()\n     would segfault in that case. Should it just be xstrdup()?\n\n  2. Are there any bounds on the size of \"line\"? E.g., is it coming in\n     as a single pkt, or can it be arbitrarily large if an attacker\n     wants (it looks like maybe the latter, since it comes from a strbuf\n     in batch_objects_command(), but I didn't look at how network data\n     gets passed in to that). At any rate, I think we ran into problems\n     before with split_cmdline() and integer overflow, since it returns\n     an int (CVE-2022-39260). I thought we fixed it by rejecting long\n     lines in git-shell, but it looks like we also hardened\n     split_cmdline() in 0ca6ead81e (alias.c: reject too-long cmdline\n     strings in split_cmdline(), 2022-09-28).\n\n     So we are maybe OK, but I wonder if we should punt on absurd lines.\n     Related, can an attacker just flood input into that strbuf, making\n     it grow forever and waste memory? That's just a simple resource\n     attack, but we have tried to avoid those elsewhere in upload-pack,\n     etc.\n\n-Peff\n"},{"id":"511614","messageId":"20250201020806.GA4081995@coredump.intra.peff.net","threadId":"61695","inReplyTo":"20250114021502.41499-8-eric.peijian@gmail.com","subject":"Re: [PATCH v10 7/8] transport: add client support for object-info","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-02-01T02:08:06Z","receivedAt":"2025-02-01T02:08:08Z","isPatch":true,"body":"On Mon, Jan 13, 2025 at 09:14:59PM -0500, Eric Ju wrote:\n\n> @@ -464,8 +465,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \targs.server_options = transport->server_options;\n>  \targs.negotiation_tips = data->options.negotiation_tips;\n>  \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n> +\targs.object_info = transport->smart_options->object_info;\n> +\n> +\tif (transport->smart_options && transport->smart_options->object_info\n\nCoverity complains about the check for a NULL transport->smart_options\nhere. If it's NULL we'd already have segfaulted a few lines above when\nwe look at the reject_shallow flag.\n\nNot sure if that's an existing bug in the earlier code or not. ;) Your\nextra check can't hurt anything, in the sense that it's just being\noverly defensive, but it does make puzzling out the expected value of\nsmart_options harder.\n\n-Peff\n"},{"id":"512784","messageId":"CAN2LT1Dxd6e1q_iE7_M_yf3aAyTwasX4ehRwEK_86YPJa8bkqw@mail.gmail.com","threadId":"61695","inReplyTo":"20250201020806.GA4081995@coredump.intra.peff.net","subject":"Re: [PATCH v10 7/8] transport: add client support for object-info","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-20T22:52:40Z","receivedAt":"2025-02-20T22:52:53Z","isPatch":true,"body":"On Fri, Jan 31, 2025 at 9:08 PM Jeff King <peff@peff.net> wrote:\n>\n> On Mon, Jan 13, 2025 at 09:14:59PM -0500, Eric Ju wrote:\n>\n> > @@ -464,8 +465,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >       args.server_options = transport->server_options;\n> >       args.negotiation_tips = data->options.negotiation_tips;\n> >       args.reject_shallow_remote = transport->smart_options->reject_shallow;\n> > +     args.object_info = transport->smart_options->object_info;\n> > +\n> > +     if (transport->smart_options && transport->smart_options->object_info\n>\n> Coverity complains about the check for a NULL transport->smart_options\n> here. If it's NULL we'd already have segfaulted a few lines above when\n> we look at the reject_shallow flag.\n>\n> Not sure if that's an existing bug in the earlier code or not. ;) Your\n> extra check can't hurt anything, in the sense that it's just being\n> overly defensive, but it does make puzzling out the expected value of\n> smart_options harder.\n>\n> -Peff\n\nThank you Jeff. Sorry for the late response.\n\nI will remove the extra check. transport->smart_options will not be\nNULL when it reaches\n`args.reject_shallow_remote = transport->smart_options->reject_shallow;`\n\nThe call sequence is like this\n\nget_remote_info() in cat-file.c  ==>  transport_fetch_refs() ==>\ntransport->vtable->fetch_refs ==> fetch_refs_via_pack()\n\nin get_remote_info(), we already have a check for NULL:\n\nif (gtransport->smart_options) {\n  ...\n}  else {\n        retval = -1;\n}\n"},{"id":"512831","messageId":"CAN2LT1AFA8AG58NNVvW2nvWR27qisPXDzSTR-tUycWGi96kcaw@mail.gmail.com","threadId":"61695","inReplyTo":"20250201020321.GA4081169@coredump.intra.peff.net","subject":"Re: [PATCH v10 8/8] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-21T15:34:44Z","receivedAt":"2025-02-21T15:34:56Z","isPatch":true,"body":"On Fri, Jan 31, 2025 at 9:03 PM Jeff King <peff@peff.net> wrote:\n>\n> On Mon, Jan 13, 2025 at 09:15:00PM -0500, Eric Ju wrote:\n>\n> > +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> > +                                      const char *line, struct strbuf *output,\n> > +                                      struct expand_data *data)\n> > +{\n> > +     int count;\n> > +     const char **argv;\n> > +\n> > +     char *line_to_split = xstrdup_or_null(line);\n> > +     count = split_cmdline(line_to_split, &argv);\n> > +     if (get_remote_info(opt, count, argv))\n> > +             goto cleanup;\n>\n> Coverity complains that split_cmdline() can return a negative value when\n> the input is malformed, which we then feed to get_remote_info(). If I\n> understand correctly (from my very brief glance at the series), that\n> string would be under the control of the untrusted client?\n>\n> I _think_ an attacker can't do anything too bad here, since\n> get_remote_info() also takes a signed int, and so iterating from 0 will\n> just find no entries. But probably we should explicitly check for error\n> and bail.\n>\n\nAn explicit check is added to make sure if a negative value is returned, we\nwill error and bail.\n\n> While just looking at this code from a security perspective, two other\n> things occur to me:\n>\n>   1. Calling xstrdup_or_null() implies that \"line\" may be NULL, which\n>      would make \"line_to_split\" also NULL. But I think split_cmdline()\n>      would segfault in that case. Should it just be xstrdup()?\n>\n\nThank you. Revised to use xstrdup() in v11.\n\n>   2. Are there any bounds on the size of \"line\"? E.g., is it coming in\n>      as a single pkt, or can it be arbitrarily large if an attacker\n>      wants (it looks like maybe the latter, since it comes from a strbuf\n>      in batch_objects_command(), but I didn't look at how network data\n>      gets passed in to that). At any rate, I think we ran into problems\n>      before with split_cmdline() and integer overflow, since it returns\n>      an int (CVE-2022-39260). I thought we fixed it by rejecting long\n>      lines in git-shell, but it looks like we also hardened\n>      split_cmdline() in 0ca6ead81e (alias.c: reject too-long cmdline\n>      strings in split_cmdline(), 2022-09-28).\n>\n>      So we are maybe OK, but I wonder if we should punt on absurd lines.\n>      Related, can an attacker just flood input into that strbuf, making\n>      it grow forever and waste memory? That's just a simple resource\n>      attack, but we have tried to avoid those elsewhere in upload-pack,\n>      etc.\n>\n\nThank you. Adding a check in v11 for the length of `lines`. Please let\nme know if something like this makes sense:\n\nif (strlen(line) >= INT_MAX) {\n        die(_(\"remote-object-info command input overflow\"));\n}\n\n\n> -Peff\n"},{"id":"512844","messageId":"20250221190451.12536-1-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20240628190503.67389-1-eric.peijian@gmail.com","subject":"[PATCH v11 0/8] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-21T19:04:41Z","receivedAt":"2025-02-21T19:05:15Z","isPatch":true,"body":"This patch series is a continuation of Calvin Wan’s (calvinwan@google.com)\npatch series [PATCH v5 0/6] cat-file: add --batch-command remote-object-info\ncommand at [1].\n\nSometimes it is beneficial to retrieve information about an object without\nhaving to download it completely. The server logic for retrieving size has\nalready been implemented and merged in \"a2ba162cda (object-info: support for\nretrieving object info, 2021-04-20)\"[2]. This patch series implement the client\noption for it.\n\nThis patch series add the `remote-object-info` command to\n`cat-file --batch-command`. This command allows the client to make an\nobject-info command request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info capability,\n`cat-file --batch-command` will die.\n\nIf a user attempts to use `remote-object-info` with protocol v1,,\n`cat-file --batch-command` will die.\n\nCurrently, only the size (%(objectsize)) is supported in this implementation.\nThe type (%(objecttype)) is not included in this patch series, as it is not yet\nsupported on the server side either. The plan is to implement the necessary\nlogic for both the server and client in a subsequent series.\n\nThe default format for remote-object-info is set to %(objectname) %(objectsize).\nOnce %(objecttype) is supported, the default format will be unified accordingly.\n\nIf the batch command format includes unsupported fields such as %(objecttype),\n%(objectsize:disk), or %(deltabase), the command will terminate with an error.\n\nChanges since V10\n================\n- Add a check on command input to prevent overflow.\n- Add other checks to prevent potential abuse.\n\nCalvin Wan (4):\n  fetch-pack: refactor packet writing\n  fetch-pack: move fetch initialization\n  serve: advertise object-info feature\n  transport: add client support for object-info\n\nEric Ju (4):\n  git-compat-util: add strtoul_ul() with error handling\n  cat-file: add declaration of variable i inside its for loop\n  t1006: split test utility functions into new \"lib-cat-file.sh\"\n  cat-file: add remote-object-info to batch-command\n\n Documentation/git-cat-file.adoc        |  24 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 125 ++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    |  85 ++++\n fetch-object-info.h                    |  22 +\n fetch-pack.c                           |  51 +-\n fetch-pack.h                           |   2 +\n git-compat-util.h                      |  20 +\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n serve.c                                |   4 +-\n t/lib-cat-file.sh                      |  16 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 664 +++++++++++++++++++++++++\n transport-helper.c                     |  11 +-\n transport.c                            |  28 +-\n transport.h                            |  11 +\n 19 files changed, 1065 insertions(+), 68 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n create mode 100644 t/lib-cat-file.sh\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\nRange-diff against v10:\n1:  a4a5aefa3e = 1:  814c53b402 git-compat-util: add strtoul_ul() with error handling\n2:  c67e79804e = 2:  04f41100c4 cat-file: add declaration of variable i inside its for loop\n3:  7f0b824714 = 3:  3af67e6648 t1006: split test utility functions into new \"lib-cat-file.sh\"\n4:  0d22d6af6e = 4:  cb1088e436 fetch-pack: refactor packet writing\n5:  34c34c7464 = 5:  614daac4bb fetch-pack: move fetch initialization\n6:  54dd237c45 = 6:  4bc403fa2c serve: advertise object-info feature\n7:  90a3d987d5 ! 7:  adae08d5a8 transport: add client support for object-info\n    @@ transport.c: static int fetch_refs_via_pack(struct transport *transport,\n      \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n     +\targs.object_info = transport->smart_options->object_info;\n     +\n    -+\tif (transport->smart_options && transport->smart_options->object_info\n    ++\tif (transport->smart_options->object_info\n     +\t    && transport->smart_options->object_info_oids->nr > 0) {\n     +\t\tstruct packet_reader reader;\n     +\t\tstruct object_info_args obj_info_args = { 0 };\n8:  9d932c2cb2 ! 8:  975d39cb6a cat-file: add remote-object-info to batch-command\n    @@ builtin/cat-file.c\n     +#include \"alias.h\"\n     +#include \"remote.h\"\n     +#include \"transport.h\"\n    ++\n    ++/* Maximum length for a remote URL. While no universal standard exists,\n    ++ * 8K is assumed to be a reasonable limit.\n    ++ */\n    ++#define MAX_REMOTE_URL_LEN (8*1024)\n    ++/* Maximum number of objects allowed in a single remote-object-info request. */\n    ++#define MAX_ALLOWED_OBJ_LIMIT 10000\n    ++/* Maximum input size permitted for the remote-object-info command. */\n    ++#define MAX_REMOTE_OBJ_INFO_LINE (MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n      \n      enum batch_mode {\n      \tBATCH_MODE_CONTENTS,\n    @@ builtin/cat-file.c: static void parse_cmd_info(struct batch_options *opt,\n     +{\n     +\tint count;\n     +\tconst char **argv;\n    ++\tchar *line_to_split;\n    ++\n    ++\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n    ++\t\tdie(_(\"remote-object-info command input overflow \"\n    ++\t\t\t\"(no more than %d objects are allowed)\"),\n    ++\t\t\tMAX_ALLOWED_OBJ_LIMIT);\n     +\n    -+\tchar *line_to_split = xstrdup_or_null(line);\n    ++\tline_to_split = xstrdup(line);\n     +\tcount = split_cmdline(line_to_split, &argv);\n    ++\tif (count < 0)\n    ++\t\tdie(_(\"split remote-object-info command\"));\n    ++\n     +\tif (get_remote_info(opt, count, argv))\n     +\t\tgoto cleanup;\n     +\n-- \n2.48.1\n\n"},{"id":"512845","messageId":"20250221190451.12536-2-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250221190451.12536-1-eric.peijian@gmail.com","subject":"[PATCH v11 1/8] git-compat-util: add strtoul_ul() with error handling","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-21T19:04:42Z","receivedAt":"2025-02-21T19:05:16Z","isPatch":true,"body":"We already have strtoul_ui() and similar functions that provide proper\nerror handling using strtoul from the standard library. However,\nthere isn't currently a variant that returns an unsigned long.\nThis commit introduces strtoul_ul() to address this gap, enabling the\nreturn of an unsigned long with proper error handling.\n---\n git-compat-util.h | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex e123288e8f..0e9a43351a 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -1353,6 +1353,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n \treturn 0;\n }\n \n+/*\n+ * Convert a string to an unsigned long using the standard library's strtoul,\n+ * with additional error handling to ensure robustness.\n+ */\n+static inline int strtoul_ul(char const *s, int base, unsigned long *result)\n+{\n+\tunsigned long ul;\n+\tchar *p;\n+\n+\terrno = 0;\n+\t/* negative values would be accepted by strtoul */\n+\tif (strchr(s, '-'))\n+\t\treturn -1;\n+\tul = strtoul(s, &p, base);\n+\tif (errno || *p || p == s )\n+\t\treturn -1;\n+\t*result = ul;\n+\treturn 0;\n+}\n+\n static inline int strtol_i(char const *s, int base, int *result)\n {\n \tlong ul;\n-- \n2.48.1\n\n"},{"id":"512846","messageId":"20250221190451.12536-3-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250221190451.12536-1-eric.peijian@gmail.com","subject":"[PATCH v11 2/8] cat-file: add declaration of variable i inside its for loop","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-21T19:04:43Z","receivedAt":"2025-02-21T19:05:17Z","isPatch":true,"body":"Some code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\nWhile at it, we also change its type from \"int\" to \"size_t\" where the latter makes more sense.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n fetch-pack.c       |  3 +--\n 2 files changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex b13561cf73..69ea642dc6 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -676,12 +676,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -689,9 +687,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -717,7 +713,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -727,7 +722,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 1ed5e11dd5..71fb2ca054 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1331,9 +1331,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n-- \n2.48.1\n\n"},{"id":"512847","messageId":"20250221190451.12536-5-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250221190451.12536-1-eric.peijian@gmail.com","subject":"[PATCH v11 4/8] fetch-pack: refactor packet writing","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-21T19:04:45Z","receivedAt":"2025-02-21T19:05:19Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nRefactor write_fetch_command_and_capabilities() to a more\ngeneral-purpose function, write_command_and_capabilities(), enabling it\nto serve both fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162c\n(object-info: support for retrieving object info, 2021-04-20).\n\nFurthermore, write_command_and_capabilities() is moved to connect.c,\nmaking it accessible to additional commands in the future.\n\nTo move write_command_and_capabilities() to connect.c, we need to\nadjust how `advertise_sid` is managed. Previously,\nin fetch_pack.c, `advertise_sid` was a static variable, modified using\ngit_config_get_bool().\n\nIn connect.c, we now initialize `advertise_sid` at the beginning by\ndirectly using git_config_get_bool(). This change is safe because:\n\nIn the original fetch-pack.c code, there are only two places that\nwrite `advertise_sid` :\n1. In function do_fetch_pack:\n        if (!server_supports(\"session-id\"))\n                advertise_sid = 0;\n2. In function fetch_pack_config():\n        git_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, since do_fetch_pack() is only relevant for protocol v1, this\nassignment can be ignored in our refactor, as\nwrite_command_and_capabilities() is only used in protocol v2.\n\nAbout 2, git_config_get_bool() is from config.h and it is an out-of-box\ndependency of connect.c, so we can reuse it directly.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  8 ++++++++\n fetch-pack.c | 35 ++---------------------------------\n 3 files changed, 44 insertions(+), 33 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 91f3990014..6647b4a4b6 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -688,6 +688,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\tgit_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t\tthe_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t\tthe_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n enum protocol {\n \tPROTO_LOCAL = 1,\n \tPROTO_FILE,\ndiff --git a/connect.h b/connect.h\nindex 1645126c17..d904c73a85 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -30,4 +30,12 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+/*\n+ * Writes a command along with the requested\n+ * server capabilities/features into a request buffer.\n+ */\n+struct string_list;\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 71fb2ca054..19b4a092ea 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1319,37 +1319,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n@@ -1360,7 +1329,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2188,7 +2157,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n-- \n2.48.1\n\n"},{"id":"512848","messageId":"20250221190451.12536-4-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250221190451.12536-1-eric.peijian@gmail.com","subject":"[PATCH v11 3/8] t1006: split test utility functions into new \"lib-cat-file.sh\"","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-21T19:04:44Z","receivedAt":"2025-02-21T19:05:19Z","isPatch":true,"body":"This refactor extracts utility functions from the cat-file's test\nscript \"t1006-cat-file.sh\" into a new \"lib-cat-file.sh\" dedicated\nlibrary file. The goal is to improve code reuse and readability,\nenabling future tests to leverage these utilities without duplicating\ncode.\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 13 +------------\n 2 files changed, 17 insertions(+), 12 deletions(-)\n create mode 100644 t/lib-cat-file.sh\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..44af232d74\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 398865d6eb..1c27c10c6f 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -3,6 +3,7 @@\n test_description='git cat-file'\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -98,18 +99,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     oid=$2\n-- \n2.48.1\n\n"},{"id":"512849","messageId":"20250221190451.12536-6-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250221190451.12536-1-eric.peijian@gmail.com","subject":"[PATCH v11 5/8] fetch-pack: move fetch initialization","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-21T19:04:46Z","receivedAt":"2025-02-21T19:05:20Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 19b4a092ea..35dccea073 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1650,18 +1650,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n-- \n2.48.1\n\n"},{"id":"512850","messageId":"20250221190451.12536-7-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250221190451.12536-1-eric.peijian@gmail.com","subject":"[PATCH v11 6/8] serve: advertise object-info feature","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-21T19:04:47Z","receivedAt":"2025-02-21T19:05:21Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n serve.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex f6dfe34a2b..92fd26fd0a 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -68,7 +68,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -76,6 +76,8 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n-- \n2.48.1\n\n"},{"id":"512851","messageId":"20250221190451.12536-8-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250221190451.12536-1-eric.peijian@gmail.com","subject":"[PATCH v11 7/8] transport: add client support for object-info","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-21T19:04:48Z","receivedAt":"2025-02-21T19:05:23Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nThis commit introduces client functions to interact with the server.\n\nCurrently, the client supports requesting a list of object IDs with\nthe 'size' feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client will return an error and exit.\n\nNotice that the entire request is written into req_buf before being\nsent to the remote. This approach follows the pattern used in the\n`send_fetch_request()` logic within fetch-pack.c.\nStreaming the request is not addressed in this patch.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Makefile            |  1 +\n fetch-object-info.c | 85 +++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h | 22 ++++++++++++\n fetch-pack.c        |  3 ++\n fetch-pack.h        |  2 ++\n transport-helper.c  | 11 ++++--\n transport.c         | 28 ++++++++++++++-\n transport.h         | 11 ++++++\n 8 files changed, 160 insertions(+), 3 deletions(-)\n create mode 100644 fetch-object-info.c\n create mode 100644 fetch-object-info.h\n\ndiff --git a/Makefile b/Makefile\nindex bcf5ed3f85..bd6786a3d9 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1030,6 +1030,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..b279e06dc8\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,85 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"object-store-ll.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends git-cat-file object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"unsupported protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\tsize_index = i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++){\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, ' ', -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: not our ref %s\",\n+\t\t\t\t\tobject_info_values.items[0].string);\n+\n+\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))\n+\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t\t\tobject_info_values.items[0].string,\n+\t\t\t\t\tobject_info_values.items[1 + size_index].string);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..6184d04d72\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"object-store-ll.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 35dccea073..92e8a7291c 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1656,6 +1656,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 9d3470366f..119d3369f1 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -42,6 +43,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\ndiff --git a/transport-helper.c b/transport-helper.c\nindex d457b42550..9da1547b2c 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -710,8 +710,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file\n+\t * remote-object-info require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -727,6 +727,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 6c2801bcbd..95be3771a6 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -465,8 +466,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.server_options = transport->server_options;\n \targs.negotiation_tips = data->options.negotiation_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options->object_info\n+\t    && transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct packet_reader reader;\n+\t\tstruct object_info_args obj_info_args = { 0 };\n+\n+\t\tobj_info_args.server_options = transport->server_options;\n+\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n+\t\tstring_list_sort(obj_info_args.object_info_options);\n+\n+\t\tconnect_setup(transport, 0);\n+\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\t\tdata->version = discover_version(&reader);\n+\t\ttransport->hash_algo = reader.hash_algo;\n+\n+\t\tret = fetch_object_info(data->version, &obj_info_args, &reader,\n+\t\t\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n+\t\t\t\t\tdata->fd[1]);\n+\t\tgoto cleanup;\n \n-\tif (!data->finished_handshake) {\n+\t} else if (!data->finished_handshake) {\n \t\tint i;\n \t\tint must_list_refs = 0;\n \t\tfor (i = 0; i < nr_heads; i++) {\ndiff --git a/transport.h b/transport.h\nindex 44100fa9b7..e61e931863 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -5,6 +5,7 @@\n #include \"remote.h\"\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n+#include \"object-store.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -30,6 +31,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to retrieve only object-info.\n+\t * If object-info is not supported, the operation will error and exit.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -53,6 +60,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n-- \n2.48.1\n\n"},{"id":"512852","messageId":"20250221190451.12536-9-eric.peijian@gmail.com","threadId":"61695","inReplyTo":"20250221190451.12536-1-eric.peijian@gmail.com","subject":"[PATCH v11 8/8] cat-file: add remote-object-info to batch-command","fromName":"Eric Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-02-21T19:04:49Z","receivedAt":"2025-02-21T19:05:23Z","isPatch":true,"body":"Since the `info` command in `cat-file --batch-command` prints object\ninfo for a given object, it is natural to add another command in\n`cat-file --batch-command` to print object info for a given object\nfrom a remote.\n\nAdd `remote-object-info` to `cat-file --batch-command`.\n\nWhile `info` takes object ids one at a time, this creates\noverhead when making requests to a server. So `remote-object-info`\ninstead can take multiple object ids at once.\n\nThe `cat-file --batch-command` command is generally implemented in\nthe following manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for `remote-object-info` to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju  <eric.peijian@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  24 +-\n builtin/cat-file.c                     | 114 +++++\n object-file.c                          |  11 +\n object-store-ll.h                      |   3 +\n t/t1017-cat-file-remote-object-info.sh | 664 +++++++++++++++++++++++++\n 5 files changed, 812 insertions(+), 4 deletions(-)\n create mode 100755 t/t1017-cat-file-remote-object-info.sh\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex d5890ae368..4fbb3a077b 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -149,6 +149,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -290,7 +297,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectsize`::\n \tThe size, in bytes, of the object (the same as `cat-file -s`\n@@ -298,13 +306,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -314,7 +323,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -396,6 +408,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will raise\n+an error and exit when they appear in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 69ea642dc6..47fd2a777b 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -27,6 +27,18 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/* Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8*1024)\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE (MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -48,6 +60,8 @@ struct batch_options {\n };\n \n static const char *force_path;\n+static struct object_info *remote_object_info;\n+static struct oid_array object_info_oids = OID_ARRAY_INIT;\n \n static struct string_list mailmap = STRING_LIST_INIT_NODUP;\n static int use_mailmap;\n@@ -579,6 +593,61 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT.\n+\t*/\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(&object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(&object_info_oids, &oid);\n+\t}\n+\tif (!object_info_oids.nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\tif (gtransport->smart_options) {\n+\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n+\t\tgtransport->smart_options->object_info = 1;\n+\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n+\n+\t\t/* 'objectsize' is the only option currently supported */\n+\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\n+\t\tif (object_info_options.nr > 0) {\n+\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n+\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t\t}\n+\t} else {\n+\t\tretval = -1;\n+\t}\n+\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -670,6 +739,50 @@ static void parse_cmd_info(struct batch_options *opt,\n \tbatch_one_object(line, output, opt, data);\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command input overflow \"\n+\t\t\t\"(no more than %d objects are allowed)\"),\n+\t\t\tMAX_ALLOWED_OBJ_LIMIT);\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"split remote-object-info command\"));\n+\n+\tif (get_remote_info(opt, count, argv))\n+\t\tgoto cleanup;\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -701,6 +814,7 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1},\n \t{ \"info\", parse_cmd_info, 1},\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1},\n \t{ \"flush\", NULL, 0},\n };\n \ndiff --git a/object-file.c b/object-file.c\nindex 00c3a4b910..836554437c 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -3161,3 +3161,14 @@ int read_loose_object(const char *path,\n \t\tmunmap(map, mapsize);\n \treturn ret;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+\tfree(object_info->type_name);\n+}\ndiff --git a/object-store-ll.h b/object-store-ll.h\nindex cd3bd5bd99..20208e1d4f 100644\n--- a/object-store-ll.h\n+++ b/object-store-ll.h\n@@ -553,4 +553,7 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\t\t   void *data, enum for_each_object_flags flags);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* OBJECT_STORE_LL_H */\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..fd6c63cdb9\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,664 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: not our ref $object_id\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n-- \n2.48.1\n\n"},{"id":"512929","messageId":"xmqqo6yr3wc4.fsf@gitster.g","threadId":"61695","inReplyTo":"20250221190451.12536-9-eric.peijian@gmail.com","subject":"Re: [PATCH v11 8/8] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-24T20:46:03Z","receivedAt":"2025-02-24T20:46:07Z","isPatch":true,"body":"Eric Ju <eric.peijian@gmail.com> writes:\n\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 69ea642dc6..47fd2a777b 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -27,6 +27,18 @@\n>  #include \"promisor-remote.h\"\n>  #include \"mailmap.h\"\n>  #include \"write-or-die.h\"\n> +#include \"alias.h\"\n> +#include \"remote.h\"\n> +#include \"transport.h\"\n> +\n> +/* Maximum length for a remote URL. While no universal standard exists,\n> + * 8K is assumed to be a reasonable limit.\n> + */\n\nStyle.  Our multi-line comment begins with slash-asterisk and ends\nwith asterisk-slash both on their own line without anything else.\n\n> +#define MAX_REMOTE_URL_LEN (8*1024)\n\nHere and ...\n\n> +/* Maximum number of objects allowed in a single remote-object-info request. */\n> +#define MAX_ALLOWED_OBJ_LIMIT 10000\n\n... here, please have a blank line.\n\n> +/* Maximum input size permitted for the remote-object-info command. */\n> +#define MAX_REMOTE_OBJ_INFO_LINE (MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n\nThis is an overly long line.\n\n> @@ -579,6 +593,61 @@ static void batch_one_object(const char *obj_name,\n>  \tobject_context_release(&ctx);\n>  }\n>  \n> +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> +{\n> +\tint retval = 0;\n> +\tstruct remote *remote = NULL;\n> +\tstruct object_id oid;\n> +\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> +\tstatic struct transport *gtransport;\n> +\n> +\t/*\n> +\t * Change the format to \"%(objectname) %(objectsize)\" when\n> +\t * remote-object-info command is used. Once we start supporting objecttype\n> +\t * the default format should change to DEFAULT_FORMAT.\n> +\t*/\n\nStyle.  Closing asterisk-slash aligns with the asterisk on the\nprevious line.\n\n> +\tif (!opt->format)\n> +\t\topt->format = \"%(objectname) %(objectsize)\";\n> +\n> +\tremote = remote_get(argv[0]);\n> +\tif (!remote)\n> +\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n> +\n> +\toid_array_clear(&object_info_oids);\n> +\tfor (size_t i = 1; i < argc; i++) {\n\nPointless mixing of \"size_t\" and \"int\".  We have declared \"int\nargc\", which is perfectly a sensible type, since we know that the\nvalue of it would not exceed MAX_ALLOWED_OBJ_LIMIT, which is 10000.\n\n> +\t\tif (get_oid_hex(argv[i], &oid))\n> +\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n> +\t\toid_array_append(&object_info_oids, &oid);\n> +\t}\n> +\tif (!object_info_oids.nr)\n> +\t\tdie(_(\"remote-object-info requires objects\"));\n> +\n> +\tgtransport = transport_get(remote, NULL);\n> +\tif (gtransport->smart_options) {\n> +\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> +\t\tgtransport->smart_options->object_info = 1;\n> +\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n> +\n> +\t\t/* 'objectsize' is the only option currently supported */\n> +\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n> +\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n> +\n> +\t\tstring_list_append(&object_info_options, \"size\");\n> +\n> +\t\tif (object_info_options.nr > 0) {\n> +\t\t\tgtransport->smart_options->object_info_options = &object_info_options;\n> +\t\t\tgtransport->smart_options->object_info_data = remote_object_info;\n> +\t\t\tretval = transport_fetch_refs(gtransport, NULL);\n> +\t\t}\n> +\t} else {\n> +\t\tretval = -1;\n> +\t}\n\nMinor style nit, but when everything else is equal, writing the side\nof smaller body first would make it easier to follow if/else, i.e.\n\n\n\tgtransport = transport_get(remote, NULL);\n\tif (!gtransport->smart_options) {\n        \t/* error */\n\t\tretval = -1;\n\t} else {\n\t\t... a lot of real code here ...\n\t}\n\n> +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> +\t\t\t\t\t const char *line, struct strbuf *output,\n> +\t\t\t\t\t struct expand_data *data)\n> +{\n> +\tint count;\n> +\tconst char **argv;\n> +\tchar *line_to_split;\n> +\n> +\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n> +\t\tdie(_(\"remote-object-info command input overflow \"\n> +\t\t\t\"(no more than %d objects are allowed)\"),\n> +\t\t\tMAX_ALLOWED_OBJ_LIMIT);\n\nNobody guarantees this user gave a request for more than 10000\nobjects; after all it may have been an overly long URL that busted\nthe line length limit, no?\n\n> +\tline_to_split = xstrdup(line);\n> +\tcount = split_cmdline(line_to_split, &argv);\n> +\tif (count < 0)\n> +\t\tdie(_(\"split remote-object-info command\"));\n\nHere, the code could check if count busts MAX_ALLOWED_OBJ_LIMIT, but\nit doesn't.\n"},{"id":"512935","messageId":"20250224234525.GB729825@coredump.intra.peff.net","threadId":"61695","inReplyTo":"CAN2LT1AFA8AG58NNVvW2nvWR27qisPXDzSTR-tUycWGi96kcaw@mail.gmail.com","subject":"Re: [PATCH v10 8/8] cat-file: add remote-object-info to batch-command","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-02-24T23:45:25Z","receivedAt":"2025-02-24T23:45:28Z","isPatch":true,"body":"On Fri, Feb 21, 2025 at 10:34:44AM -0500, Peijian Ju wrote:\n\n> Thank you. Revised to use xstrdup() in v11.\n> \n> >   2. Are there any bounds on the size of \"line\"? E.g., is it coming in\n> >      as a single pkt, or can it be arbitrarily large if an attacker\n> >      wants (it looks like maybe the latter, since it comes from a strbuf\n> >      in batch_objects_command(), but I didn't look at how network data\n> >      gets passed in to that). At any rate, I think we ran into problems\n> >      before with split_cmdline() and integer overflow, since it returns\n> >      an int (CVE-2022-39260). I thought we fixed it by rejecting long\n> >      lines in git-shell, but it looks like we also hardened\n> >      split_cmdline() in 0ca6ead81e (alias.c: reject too-long cmdline\n> >      strings in split_cmdline(), 2022-09-28).\n> >\n> >      So we are maybe OK, but I wonder if we should punt on absurd lines.\n> >      Related, can an attacker just flood input into that strbuf, making\n> >      it grow forever and waste memory? That's just a simple resource\n> >      attack, but we have tried to avoid those elsewhere in upload-pack,\n> >      etc.\n> >\n> \n> Thank you. Adding a check in v11 for the length of `lines`. Please let\n> me know if something like this makes sense:\n> \n> if (strlen(line) >= INT_MAX) {\n>         die(_(\"remote-object-info command input overflow\"));\n> }\n\nI took a look at what you ended up with in v11, and...I think I totally\nmisunderstood what was going on in your series, or when this code would\nbe run.\n\nI had thought the cat-file here was running on the server side, and that\nwe needed to protect ourselves against malicious clients. But your new\nparse_cmd_remote_object_info() is purely a client-side function that\nwill then access the server behind the scenes. And its input will be\ncoming from the stdin of cat-file locally.\n\nSo I'm not sure that we need to protect it unless we think there's some\nway that an attacker can automatically trigger arbitrary\nremote-object-info requests.\n\nThat said, I'm not sure why you need split_cmdline() at all. The format\nseems to be:\n\n  remote-object-info <url> <oid>...\n\nThe only thing that _might_ need quoting is the url, but is shell\nquoting a reasonable thing there? I'd think that it would be\nURL-encoded, and thus contain no spaces. The <oid> has to be a real full\noid, I think, because the object-info on the server side insists on\nthat.\n\nSo why not just split on space? Something like this:\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 9de1016acd..aedbcba347 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -597,7 +597,7 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n-static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n+static int get_remote_info(struct batch_options *opt, const char *url, const char *oid_list)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n@@ -613,16 +613,19 @@ static int get_remote_info(struct batch_options *opt, int argc, const char **arg\n \tif (!opt->format)\n \t\topt->format = \"%(objectname) %(objectsize)\";\n \n-\tremote = remote_get(argv[0]);\n+\tremote = remote_get(url);\n \tif (!remote)\n \t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n \n \toid_array_clear(&object_info_oids);\n-\tfor (size_t i = 1; i < argc; i++) {\n-\t\tif (get_oid_hex(argv[i], &oid))\n-\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\twhile (*oid_list) {\n+\t\tif (parse_oid_hex(oid_list, &oid, &oid_list))\n+\t\t\tdie(_(\"Not a valid object name %s\"), oid_list);\n \t\toid_array_append(&object_info_oids, &oid);\n+\t\twhile (*oid_list == ' ')\n+\t\t\toid_list++;\n \t}\n+\n \tif (!object_info_oids.nr)\n \t\tdie(_(\"remote-object-info requires objects\"));\n \n@@ -747,21 +750,15 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n {\n-\tint count;\n-\tconst char **argv;\n-\tchar *line_to_split;\n-\n-\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n-\t\tdie(_(\"remote-object-info command input overflow \"\n-\t\t\t\"(no more than %d objects are allowed)\"),\n-\t\t\tMAX_ALLOWED_OBJ_LIMIT);\n+\tchar *url;\n+\tconst char *space;\n \n-\tline_to_split = xstrdup(line);\n-\tcount = split_cmdline(line_to_split, &argv);\n-\tif (count < 0)\n-\t\tdie(_(\"split remote-object-info command\"));\n+\tspace = strchr(line, ' ');\n+\tif (!space)\n+\t\treturn; /* report error somehow? */\n+\turl = xmemdupz(line, space - line);\n \n-\tif (get_remote_info(opt, count, argv))\n+\tif (get_remote_info(opt, url, space + 1))\n \t\tgoto cleanup;\n \n \tdata->skip_object_info = 1;\n@@ -774,16 +771,15 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n \t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tbatch_object_write(argv[i+1], output, opt, data, NULL, 0);\n+\t\t\tbatch_object_write(oid_to_hex(&data->oid), output, opt, data, NULL, 0);\n \t\t}\n \t}\n \tdata->skip_object_info = 0;\n \n cleanup:\n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n-\tfree(line_to_split);\n-\tfree(argv);\n+\tfree(url);\n \tfree(remote_object_info);\n }\n \n\nYou'd need to adjust t1017 to remote the quotes from the inputs, and I\nthink you'd have to correctly url-encoded the file:// one to avoid\nspaces (but that is technically true already! If the filesystem path has\na \"%\" in it, it would be misinterpreted).\n\n-Peff\n"},{"id":"512936","messageId":"20250224234720.GC729825@coredump.intra.peff.net","threadId":"61695","inReplyTo":"20250221190451.12536-9-eric.peijian@gmail.com","subject":"Re: [PATCH v11 8/8] cat-file: add remote-object-info to batch-command","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-02-24T23:47:20Z","receivedAt":"2025-02-24T23:47:22Z","isPatch":true,"body":"On Fri, Feb 21, 2025 at 02:04:49PM -0500, Eric Ju wrote:\n\n> +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> [...]\n> +\tif (gtransport->smart_options) {\n> +\t\tCALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> +\t\tgtransport->smart_options->object_info = 1;\n> +\t\tgtransport->smart_options->object_info_oids = &object_info_oids;\n> +\n> +\t\t/* 'objectsize' is the only option currently supported */\n> +\t\tif (!strstr(opt->format, \"%(objectsize)\"))\n> +\t\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n\nBTW, this strstr() isn't quite sufficient to prevent problems, as it\nwould not find placeholders which _do_ exist but which aren't handled.\nOne of the first things I tried was:\n\n  git cat-file --batch-command='%(objecttype) %(objectsize)'\n\nand feeding it \"remote-object-info /path/to/repo some-oid\". And it\nsegfaulted.\n\n-Peff\n"},{"id":"514012","messageId":"CAN2LT1Cc-UaUpabqcGascicR3nk6ZdFythamKje33Orhy5WoNA@mail.gmail.com","threadId":"61695","inReplyTo":"xmqqo6yr3wc4.fsf@gitster.g","subject":"Re: [PATCH v11 8/8] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-03-11T23:10:23Z","receivedAt":"2025-03-11T23:10:35Z","isPatch":true,"body":"On Mon, Feb 24, 2025 at 3:46 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Eric Ju <eric.peijian@gmail.com> writes:\n>\n> > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > index 69ea642dc6..47fd2a777b 100644\n> > --- a/builtin/cat-file.c\n> > +++ b/builtin/cat-file.c\n> > @@ -27,6 +27,18 @@\n> >  #include \"promisor-remote.h\"\n> >  #include \"mailmap.h\"\n> >  #include \"write-or-die.h\"\n> > +#include \"alias.h\"\n> > +#include \"remote.h\"\n> > +#include \"transport.h\"\n> > +\n> > +/* Maximum length for a remote URL. While no universal standard exists,\n> > + * 8K is assumed to be a reasonable limit.\n> > + */\n>\n> Style.  Our multi-line comment begins with slash-asterisk and ends\n> with asterisk-slash both on their own line without anything else.\n>\n\nThank you. All the style-related comments will be fixed in the next patch.\n\n> > +#define MAX_REMOTE_URL_LEN (8*1024)\n>\n> Here and ...\n>\n> > +/* Maximum number of objects allowed in a single remote-object-info request. */\n> > +#define MAX_ALLOWED_OBJ_LIMIT 10000\n>\n> ... here, please have a blank line.\n>\n> > +/* Maximum input size permitted for the remote-object-info command. */\n> > +#define MAX_REMOTE_OBJ_INFO_LINE (MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n>\n> This is an overly long line.\n>\n> > @@ -579,6 +593,61 @@ static void batch_one_object(const char *obj_name,\n> >       object_context_release(&ctx);\n> >  }\n> >\n> > +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> > +{\n> > +     int retval = 0;\n> > +     struct remote *remote = NULL;\n> > +     struct object_id oid;\n> > +     struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> > +     static struct transport *gtransport;\n> > +\n> > +     /*\n> > +      * Change the format to \"%(objectname) %(objectsize)\" when\n> > +      * remote-object-info command is used. Once we start supporting objecttype\n> > +      * the default format should change to DEFAULT_FORMAT.\n> > +     */\n>\n> Style.  Closing asterisk-slash aligns with the asterisk on the\n> previous line.\n>\n> > +     if (!opt->format)\n> > +             opt->format = \"%(objectname) %(objectsize)\";\n> > +\n> > +     remote = remote_get(argv[0]);\n> > +     if (!remote)\n> > +             die(_(\"must supply valid remote when using remote-object-info\"));\n> > +\n> > +     oid_array_clear(&object_info_oids);\n> > +     for (size_t i = 1; i < argc; i++) {\n>\n> Pointless mixing of \"size_t\" and \"int\".  We have declared \"int\n> argc\", which is perfectly a sensible type, since we know that the\n> value of it would not exceed MAX_ALLOWED_OBJ_LIMIT, which is 10000.\n>\n\nThank you. Change to \"int\" instead.\n\n> > +             if (get_oid_hex(argv[i], &oid))\n> > +                     die(_(\"Not a valid object name %s\"), argv[i]);\n> > +             oid_array_append(&object_info_oids, &oid);\n> > +     }\n> > +     if (!object_info_oids.nr)\n> > +             die(_(\"remote-object-info requires objects\"));\n> > +\n> > +     gtransport = transport_get(remote, NULL);\n> > +     if (gtransport->smart_options) {\n> > +             CALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> > +             gtransport->smart_options->object_info = 1;\n> > +             gtransport->smart_options->object_info_oids = &object_info_oids;\n> > +\n> > +             /* 'objectsize' is the only option currently supported */\n> > +             if (!strstr(opt->format, \"%(objectsize)\"))\n> > +                     die(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n> > +\n> > +             string_list_append(&object_info_options, \"size\");\n> > +\n> > +             if (object_info_options.nr > 0) {\n> > +                     gtransport->smart_options->object_info_options = &object_info_options;\n> > +                     gtransport->smart_options->object_info_data = remote_object_info;\n> > +                     retval = transport_fetch_refs(gtransport, NULL);\n> > +             }\n> > +     } else {\n> > +             retval = -1;\n> > +     }\n>\n> Minor style nit, but when everything else is equal, writing the side\n> of smaller body first would make it easier to follow if/else, i.e.\n>\n>\n>         gtransport = transport_get(remote, NULL);\n>         if (!gtransport->smart_options) {\n>                 /* error */\n>                 retval = -1;\n>         } else {\n>                 ... a lot of real code here ...\n>         }\n>\n> > +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> > +                                      const char *line, struct strbuf *output,\n> > +                                      struct expand_data *data)\n> > +{\n> > +     int count;\n> > +     const char **argv;\n> > +     char *line_to_split;\n> > +\n> > +     if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n> > +             die(_(\"remote-object-info command input overflow \"\n> > +                     \"(no more than %d objects are allowed)\"),\n> > +                     MAX_ALLOWED_OBJ_LIMIT);\n>\n> Nobody guarantees this user gave a request for more than 10000\n> objects; after all it may have been an overly long URL that busted\n> the line length limit, no?\n\nYes, the error message is indeed misleading here.\nIn the next patch, the behavior will be updated as follows:\n\n1. If strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE, we will die with a clear error:\n\"remote-object-info input too long\".\n\n2. After step 1, when calling get_remote_info(opt, count, argv), we\nwill check object_info_oids.nr.\nIf `object_info_oids.nr > MAX_ALLOWED_OBJ_LIMIT`, we will die with:\n\"no more than %d objects are allowed\", MAX_ALLOWED_OBJ_LIMIT.\n\n>\n> > +     line_to_split = xstrdup(line);\n> > +     count = split_cmdline(line_to_split, &argv);\n> > +     if (count < 0)\n> > +             die(_(\"split remote-object-info command\"));\n>\n> Here, the code could check if count busts MAX_ALLOWED_OBJ_LIMIT, but\n> it doesn't.\n\nYes, a check is needed here. Please see the previous reply.\n"},{"id":"514019","messageId":"CAN2LT1CsjA7V_y-OqDNREkpERHEBcx627JhCrzg9GHfWS=b0NA@mail.gmail.com","threadId":"61695","inReplyTo":"20250224234720.GC729825@coredump.intra.peff.net","subject":"Re: [PATCH v11 8/8] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-03-12T02:19:55Z","receivedAt":"2025-03-12T02:20:12Z","isPatch":true,"body":"On Mon, Feb 24, 2025 at 6:47 PM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Feb 21, 2025 at 02:04:49PM -0500, Eric Ju wrote:\n>\n> > +static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> > [...]\n> > +     if (gtransport->smart_options) {\n> > +             CALLOC_ARRAY(remote_object_info, object_info_oids.nr);\n> > +             gtransport->smart_options->object_info = 1;\n> > +             gtransport->smart_options->object_info_oids = &object_info_oids;\n> > +\n> > +             /* 'objectsize' is the only option currently supported */\n> > +             if (!strstr(opt->format, \"%(objectsize)\"))\n> > +                     die(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n>\n> BTW, this strstr() isn't quite sufficient to prevent problems, as it\n> would not find placeholders which _do_ exist but which aren't handled.\n> One of the first things I tried was:\n>\n>   git cat-file --batch-command='%(objecttype) %(objectsize)'\n>\n> and feeding it \"remote-object-info /path/to/repo some-oid\". And it\n> segfaulted.\n>\n> -Peff\n\nThank you, Peff. Yes, you are right. It is a bug. I am adding a new\nlogic in v12:\n1. Iterating on the `opt->format` to see if there are any unsupported\nplaceholders. If there is,  error with unspported placeholders.\n2. Adding more test cases to cover different formats, e.g., just\n`%(objectsize)`, just `%(objectname)`, mixed usage of supported and\nunsupported placeholders.\n"},{"id":"514100","messageId":"CAN2LT1A97fz+KXTVd+hziNjqJheuJB60nountB9CLiwdmb7D_w@mail.gmail.com","threadId":"61695","inReplyTo":"20250224234525.GB729825@coredump.intra.peff.net","subject":"Re: [PATCH v10 8/8] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-03-12T19:53:44Z","receivedAt":"2025-03-12T19:53:55Z","isPatch":true,"body":"On Mon, Feb 24, 2025 at 6:45 PM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Feb 21, 2025 at 10:34:44AM -0500, Peijian Ju wrote:\n>\n> > Thank you. Revised to use xstrdup() in v11.\n> >\n> > >   2. Are there any bounds on the size of \"line\"? E.g., is it coming in\n> > >      as a single pkt, or can it be arbitrarily large if an attacker\n> > >      wants (it looks like maybe the latter, since it comes from a strbuf\n> > >      in batch_objects_command(), but I didn't look at how network data\n> > >      gets passed in to that). At any rate, I think we ran into problems\n> > >      before with split_cmdline() and integer overflow, since it returns\n> > >      an int (CVE-2022-39260). I thought we fixed it by rejecting long\n> > >      lines in git-shell, but it looks like we also hardened\n> > >      split_cmdline() in 0ca6ead81e (alias.c: reject too-long cmdline\n> > >      strings in split_cmdline(), 2022-09-28).\n> > >\n> > >      So we are maybe OK, but I wonder if we should punt on absurd lines.\n> > >      Related, can an attacker just flood input into that strbuf, making\n> > >      it grow forever and waste memory? That's just a simple resource\n> > >      attack, but we have tried to avoid those elsewhere in upload-pack,\n> > >      etc.\n> > >\n> >\n> > Thank you. Adding a check in v11 for the length of `lines`. Please let\n> > me know if something like this makes sense:\n> >\n> > if (strlen(line) >= INT_MAX) {\n> >         die(_(\"remote-object-info command input overflow\"));\n> > }\n>\n> I took a look at what you ended up with in v11, and...I think I totally\n> misunderstood what was going on in your series, or when this code would\n> be run.\n>\n> I had thought the cat-file here was running on the server side, and that\n> we needed to protect ourselves against malicious clients. But your new\n> parse_cmd_remote_object_info() is purely a client-side function that\n> will then access the server behind the scenes. And its input will be\n> coming from the stdin of cat-file locally.\n>\n> So I'm not sure that we need to protect it unless we think there's some\n> way that an attacker can automatically trigger arbitrary\n> remote-object-info requests.\n>\n\nThank you. Yes, remote-object-info is purely a client-side command.If\nan attacker is able to automatically\ntrigger arbitrary remote-object-info requests, it likely means they\nalready have control over that system.\n\nFrom my understanding, Git generally trusts its clients. So unless\nthere are strong objections,\nI will revert those input length checks.\n\n\n> That said, I'm not sure why you need split_cmdline() at all. The format\n> seems to be:\n>\n>   remote-object-info <url> <oid>...\n>\n> The only thing that _might_ need quoting is the url, but is shell\n> quoting a reasonable thing there? I'd think that it would be\n> URL-encoded, and thus contain no spaces. The <oid> has to be a real full\n> oid, I think, because the object-info on the server side insists on\n> that.\n>\n\nThank you! We hadn’t given much thought to the URL format earlier,\nbut I agree that it’s reasonable to require the URL in\nremote-object-info to be properly URL-encoded.\n\nWith that assumption, splitting on spaces makes sense. I’ll update\nthis in the next patch and\nalso revise the documentation to clarify that URL parameters must be\nURL-encoded.\n\n\n> So why not just split on space? Something like this:\n>\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 9de1016acd..aedbcba347 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -597,7 +597,7 @@ static void batch_one_object(const char *obj_name,\n>         object_context_release(&ctx);\n>  }\n>\n> -static int get_remote_info(struct batch_options *opt, int argc, const char **argv)\n> +static int get_remote_info(struct batch_options *opt, const char *url, const char *oid_list)\n>  {\n>         int retval = 0;\n>         struct remote *remote = NULL;\n> @@ -613,16 +613,19 @@ static int get_remote_info(struct batch_options *opt, int argc, const char **arg\n>         if (!opt->format)\n>                 opt->format = \"%(objectname) %(objectsize)\";\n>\n> -       remote = remote_get(argv[0]);\n> +       remote = remote_get(url);\n>         if (!remote)\n>                 die(_(\"must supply valid remote when using remote-object-info\"));\n>\n>         oid_array_clear(&object_info_oids);\n> -       for (size_t i = 1; i < argc; i++) {\n> -               if (get_oid_hex(argv[i], &oid))\n> -                       die(_(\"Not a valid object name %s\"), argv[i]);\n> +       while (*oid_list) {\n> +               if (parse_oid_hex(oid_list, &oid, &oid_list))\n> +                       die(_(\"Not a valid object name %s\"), oid_list);\n>                 oid_array_append(&object_info_oids, &oid);\n> +               while (*oid_list == ' ')\n> +                       oid_list++;\n>         }\n> +\n>         if (!object_info_oids.nr)\n>                 die(_(\"remote-object-info requires objects\"));\n>\n> @@ -747,21 +750,15 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>                                          const char *line, struct strbuf *output,\n>                                          struct expand_data *data)\n>  {\n> -       int count;\n> -       const char **argv;\n> -       char *line_to_split;\n> -\n> -       if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n> -               die(_(\"remote-object-info command input overflow \"\n> -                       \"(no more than %d objects are allowed)\"),\n> -                       MAX_ALLOWED_OBJ_LIMIT);\n> +       char *url;\n> +       const char *space;\n>\n> -       line_to_split = xstrdup(line);\n> -       count = split_cmdline(line_to_split, &argv);\n> -       if (count < 0)\n> -               die(_(\"split remote-object-info command\"));\n> +       space = strchr(line, ' ');\n> +       if (!space)\n> +               return; /* report error somehow? */\n> +       url = xmemdupz(line, space - line);\n>\n> -       if (get_remote_info(opt, count, argv))\n> +       if (get_remote_info(opt, url, space + 1))\n>                 goto cleanup;\n>\n>         data->skip_object_info = 1;\n> @@ -774,16 +771,15 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>                          */\n>                         data->size = *remote_object_info[i].sizep;\n>                         opt->batch_mode = BATCH_MODE_INFO;\n> -                       batch_object_write(argv[i+1], output, opt, data, NULL, 0);\n> +                       batch_object_write(oid_to_hex(&data->oid), output, opt, data, NULL, 0);\n>                 }\n>         }\n>         data->skip_object_info = 0;\n>\n>  cleanup:\n>         for (size_t i = 0; i < object_info_oids.nr; i++)\n>                 free_object_info_contents(&remote_object_info[i]);\n> -       free(line_to_split);\n> -       free(argv);\n> +       free(url);\n>         free(remote_object_info);\n>  }\n>\n>\n> You'd need to adjust t1017 to remote the quotes from the inputs, and I\n> think you'd have to correctly url-encoded the file:// one to avoid\n> spaces (but that is technically true already! If the filesystem path has\n> a \"%\" in it, it would be misinterpreted).\n>\n\nThank you. Tests will be adjusted.\n> -Peff\n"},{"id":"514161","messageId":"20250313060250.GH94015@coredump.intra.peff.net","threadId":"61695","inReplyTo":"CAN2LT1CsjA7V_y-OqDNREkpERHEBcx627JhCrzg9GHfWS=b0NA@mail.gmail.com","subject":"Re: [PATCH v11 8/8] cat-file: add remote-object-info to batch-command","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-03-13T06:02:50Z","receivedAt":"2025-03-13T06:02:51Z","isPatch":true,"body":"On Tue, Mar 11, 2025 at 10:19:55PM -0400, Peijian Ju wrote:\n\n> > BTW, this strstr() isn't quite sufficient to prevent problems, as it\n> > would not find placeholders which _do_ exist but which aren't handled.\n> > One of the first things I tried was:\n> >\n> >   git cat-file --batch-command='%(objecttype) %(objectsize)'\n> >\n> > and feeding it \"remote-object-info /path/to/repo some-oid\". And it\n> > segfaulted.\n> >\n> > -Peff\n> \n> Thank you, Peff. Yes, you are right. It is a bug. I am adding a new\n> logic in v12:\n> 1. Iterating on the `opt->format` to see if there are any unsupported\n> placeholders. If there is,  error with unspported placeholders.\n> 2. Adding more test cases to cover different formats, e.g., just\n> `%(objectsize)`, just `%(objectname)`, mixed usage of supported and\n> unsupported placeholders.\n\nYes, though it would be nice for step 1 to avoid re-parsing the string.\nI think you could either:\n\n  1. After the mark_query pass in batch_objects(), check for unsupported\n     pointers in expand_data. The downside here is that you'd have to\n     match each one that you _don't_ allow (so if somebody adds a new\n     one and forgets to update your list, it wouldn't be caught).\n\n  2. In expand_atom() or expand_format(), check an allow-list using\n     is_atom(), when remote-mode is in use. The downside here is that I\n     think we'd eventually want to move that parsing and formatting to\n     the shared ref-filter API. But maybe that API could provide some\n     kind of \"check that this atom is allowed\" function pointer.\n\nI do wonder if there might be a way to also just notice that we don't\nhave the requested information and handle it gracefully. I didn't\nreproduce it again just now, but I'd guess the segfault is due to\nfeeding garbage to type_name() in expand_atom().\n\nSo maybe if we initialized expand_data fully (so that data->type is\nalways OBJ_BAD or something) and then checked for a NULL return from\ntype_name(), we could do something sensible in expand_atom(), like\ninsert a blank string or similar. And then it is not an error to ask for\n%(objecttype), but you will just not get useful data for those entries.\nFrom the description of the protocol, it sounds like you could actually\nintermix remote and local object requests?\n\n-Peff\n"},{"id":"514839","messageId":"CAN2LT1D3d=yMYVhBjpj5PvyjfTVjwqcFPNViuCJ=f49YbCZuJg@mail.gmail.com","threadId":"61695","inReplyTo":"20250313060250.GH94015@coredump.intra.peff.net","subject":"Re: [PATCH v11 8/8] cat-file: add remote-object-info to batch-command","fromName":"Peijian Ju","fromEmail":"eric.peijian@gmail.com","sentAt":"2025-03-21T18:24:05Z","receivedAt":"2025-03-21T18:24:17Z","isPatch":true,"body":"On Thu, Mar 13, 2025 at 2:02 AM Jeff King <peff@peff.net> wrote:\n>\n> On Tue, Mar 11, 2025 at 10:19:55PM -0400, Peijian Ju wrote:\n>\n> > > BTW, this strstr() isn't quite sufficient to prevent problems, as it\n> > > would not find placeholders which _do_ exist but which aren't handled.\n> > > One of the first things I tried was:\n> > >\n> > >   git cat-file --batch-command='%(objecttype) %(objectsize)'\n> > >\n> > > and feeding it \"remote-object-info /path/to/repo some-oid\". And it\n> > > segfaulted.\n> > >\n> > > -Peff\n> >\n> > Thank you, Peff. Yes, you are right. It is a bug. I am adding a new\n> > logic in v12:\n> > 1. Iterating on the `opt->format` to see if there are any unsupported\n> > placeholders. If there is,  error with unspported placeholders.\n> > 2. Adding more test cases to cover different formats, e.g., just\n> > `%(objectsize)`, just `%(objectname)`, mixed usage of supported and\n> > unsupported placeholders.\n>\n> Yes, though it would be nice for step 1 to avoid re-parsing the string.\n> I think you could either:\n>\n>   1. After the mark_query pass in batch_objects(), check for unsupported\n>      pointers in expand_data. The downside here is that you'd have to\n>      match each one that you _don't_ allow (so if somebody adds a new\n>      one and forgets to update your list, it wouldn't be caught).\n>\n>   2. In expand_atom() or expand_format(), check an allow-list using\n>      is_atom(), when remote-mode is in use. The downside here is that I\n>      think we'd eventually want to move that parsing and formatting to\n>      the shared ref-filter API. But maybe that API could provide some\n>      kind of \"check that this atom is allowed\" function pointer.\n>\n\nThank you, Peff. I prefer option 2. Maintaining an allow-list of\nsupported placeholders seems more practical than tracking unsupported\nones with a disallow-list. This approach has the added benefit that\nany newly added placeholders would automatically be treated as\nunsupported until explicitly added to the allow-list, reducing the\nchance of oversights.\n\n> I do wonder if there might be a way to also just notice that we don't\n> have the requested information and handle it gracefully. I didn't\n> reproduce it again just now, but I'd guess the segfault is due to\n> feeding garbage to type_name() in expand_atom().\n>\n> So maybe if we initialized expand_data fully (so that data->type is\n> always OBJ_BAD or something) and then checked for a NULL return from\n> type_name(), we could do something sensible in expand_atom(), like\n> insert a blank string or similar. And then it is not an error to ask for\n> %(objecttype), but you will just not get useful data for those entries.\n> From the description of the protocol, it sounds like you could actually\n> intermix remote and local object requests?\n>\n> -Peff\n\nThank you Peff. I like the idea \"... it is not an error to ask for\n%(objecttype), but you will just not get useful data for those\nentries.\"\n\nSo if we do remote-object-info with format \"%(objectname)\n%(objectsize) %(objecttype) %(objectsize:disk)\", the response can be:\n\n4346b22767c07e31d0f9b524fcb377972d957313 199 ??? ???\n\n\nWhere ??? means the placeholder is not yet supported. In this way we\ndon't have to change the default format, and as new support for the\nplaceholders is added, ??? will be replaced by meaningful data.\n\nAbout intermixing remote and local object requests, do you mean what\nhappens when remote-object-info is passed oids of objects that are\navailable locally instead of on a remote? If so, I have these\nscenarios:\n\n1. An object is on remote but not on local. This is what\n`remote-object-info` primarily focuses on: we retrieve info from\nremote without downloading the object.\n2. An object is on remote as well as on local. I think\n`remote-object-info` should still retrieve info from remote instead of\nchecking local data. After all, if the user knows the object is on\nlocal, they can use the `info` command. If remote-object-info is used,\nit means we are interested in the information stored on the remote.\n3. An object is not on remote, but only on local. I think\nremote-object-info should fail in this case, since the remote doesn't\nhave the object. The info command should be used in this case.\n"},{"id":"514896","messageId":"20250324033922.GB690093@coredump.intra.peff.net","threadId":"61695","inReplyTo":"CAN2LT1D3d=yMYVhBjpj5PvyjfTVjwqcFPNViuCJ=f49YbCZuJg@mail.gmail.com","subject":"Re: [PATCH v11 8/8] cat-file: add remote-object-info to batch-command","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-03-24T03:39:22Z","receivedAt":"2025-03-24T03:39:24Z","isPatch":true,"body":"On Fri, Mar 21, 2025 at 02:24:05PM -0400, Peijian Ju wrote:\n\n> \n> Thank you Peff. I like the idea \"... it is not an error to ask for\n> %(objecttype), but you will just not get useful data for those\n> entries.\"\n> \n> So if we do remote-object-info with format \"%(objectname)\n> %(objectsize) %(objecttype) %(objectsize:disk)\", the response can be:\n> \n> 4346b22767c07e31d0f9b524fcb377972d957313 199 ??? ???\n> \n> \n> Where ??? means the placeholder is not yet supported. In this way we\n> don't have to change the default format, and as new support for the\n> placeholders is added, ??? will be replaced by meaningful data.\n\nYes, something like that. I don't know what the placeholder should be.\nIn similar situations for the ref-filter printer, I think we use the\nempty string for unsupported cases. E.g.:\n\n  git for-each-ref --format='%(refname) %(tagger)'\n\nwill show the empty string for %(tagger) of non-tags. That lets you use\nconditionals like %(if) to switch behavior. The cat-file formatter\ndoesn't use ref-filter now, but I think in the long run we'd want to\nunify them. So it probably makes sense to match its behavior.\n\n> About intermixing remote and local object requests, do you mean what\n> happens when remote-object-info is passed oids of objects that are\n> available locally instead of on a remote? If so, I have these\n> scenarios:\n\nNo, I meant that --batch-command takes a single format string, but you\ncan issue both local and remote requests to it. So for example:\n\n  git cat-file --batch-command='%(objectname) %(objecttype) %(objectsize)' <<\\EOF\n  info 683c54c999c301c2cd6f715c411407c413b1d84e\n  remote-object-info c9d3534de317f31915f37e9d9c0d52d4cf901482\n  EOF\n\nwould show the local info for the first object, and remote info for the\nother. If you're only issuing remote-object-info commands, obviously\nit's dumb to include %(objecttype) which cannot be filled. But in the\nexample above, it is possibly useful to get more data on the local\nobjects, and a reduced set of data for the remote ones.\n\n> 1. An object is on remote but not on local. This is what\n> `remote-object-info` primarily focuses on: we retrieve info from\n> remote without downloading the object.\n> 2. An object is on remote as well as on local. I think\n> `remote-object-info` should still retrieve info from remote instead of\n> checking local data. After all, if the user knows the object is on\n> local, they can use the `info` command. If remote-object-info is used,\n> it means we are interested in the information stored on the remote.\n> 3. An object is not on remote, but only on local. I think\n> remote-object-info should fail in this case, since the remote doesn't\n> have the object. The info command should be used in this case.\n\nYeah, agreed. remote-object-info should always predictably ask the\nremote.\n\n-Peff\n"},{"id":"538818","messageId":"20260312214154.89120-1-pabloosabaterr@gmail.com","threadId":"61695","inReplyTo":"20250324033922.GB690093@coredump.intra.peff.net","subject":"[GSoC] Re: [PATCH v11 8/8] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-03-12T21:41:54Z","receivedAt":"2026-03-12T21:42:04Z","isPatch":true,"body":"From: Pablo Sabater Jiménez <pabloosabaterr@gmail.com>\n\nJeff King <peff@peff.net> wrote:\n\n> In similar situations for the ref-filter printer, I think we use the\n> empty string for unsupported cases. E.g.:\n> \n>   git for-each-ref --format='%(refname) %(tagger)'\n> \n> will show the empty string for %(tagger) of non-tags.\n\nwhile playing with Eric's v11 for my proposal I tested some of the mentioned\ncases.\n\nI tested this, and for-each-ref dies on unknown atoms:\n\n  $ git for-each-ref --format='%(test)'\n  fatal: unknown field name: test\n\nSo the empty string behavior is just for known atoms but not applicable to\na given ref type, like %(tagger) on a non-tag. For remote-object-info,\nthe atoms are known to expand_atom() but the remote can't provide data\nfor them, returning an empty string would be the closest match.\n\n> No, I meant that --batch-command takes a single format string, but you\n> can issue both local and remote requests to it. So for example:\n> \n>   git cat-file --batch-command='%(objectname) %(objecttype) %(objectsize)' <<\\EOF\n>   info 683c54c999c301c2cd6f715c411407c413b1d84e\n>   remote-object-info c9d3534de317f31915f37e9d9c0d52d4cf901482\n>   EOF\n\nWhile testing Eric's v11 on this case, I found a bug beyond the segfault:\nwhen a local info query runs before remote-object-info in the same session,\ndata->type retains stale data from the local query, and remote-object-info\nsilently returns the wrong type.\n\nTo reproduce, query a commit locally then a blob remotely:\n\n  git cat-file --batch-command='%(objectname) %(objectsize) %(objecttype)' <<-EOF\n  info <commit-oid>\n  remote-object-info origin <blob-oid>\n  EOF\n\nexample for my test case:\n  git cat-file --batch-command='%(objectname) %(objectsize) %(objecttype)' <<EOF\n  info fede5cd8e8da0ee5d984753286c77f80339ec832\n  remote-object-info file:///home/blopa/tests/CPU16-remote.git 9a455a4325cc0b0ba839f2414395257511ab6213\n  EOF\n  fede5cd8e8da0ee5d984753286c77f80339ec832 237 commit\n  9a455a4325cc0b0ba839f2414395257511ab6213 256 commit\n\n  git cat-file -t 9a455a4325cc0b0ba839f2414395257511ab6213\n  blob\n\nThe blob is being reported as \"commit\" because data->type isnt being cleared\nbetween commands. The size is correct (provided by the\nserver) but the type is wrong.\n\nThis is even worse because user wouldn't even receive any signal that\nthere is an error.\n\nTo reproduce it, this is what I did:\nServer repo needs `transfer.advertiseobjectinfo true`, and both client\nand server must run Eric's v11, I used file://.\n\nIn the meantime I'll keep testing\nEric's v11 and report any issues I find.\n\nPablo\n"},{"id":"544881","messageId":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20250221190451.12536-1-eric.peijian@gmail.com","subject":"[PATCH GSoC RFC v12 00/12] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:23Z","receivedAt":"2026-06-08T10:14:49Z","isPatch":true,"body":"This path series is a continuation of Eric Ju's (eric.peijian@gmail.com) and\nCalvin Wan's (calvinwan@google.com) patch series [1] and [2] respectively.\n\nSometimes it is beneficial to retrieve information about an object without\nhaving to download it completely. The server logic for retrieving size has\nalready been implemented and merged in \"a2ba162cda (object-info: support for\nretrieving object info, 2021-04-20)\"[3]. This patch series implement the client\noption for it.\n\nEric's series adds the `remote-object-info` command to\n`cat-file --batch-command`. This command allows the client to make an\nobject-info command request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info capability,\n`cat-file --batch-command` will die.\n\nIf a user attempts to use `remote-object-info` with protocol v1,\n`cat-file --batch-command` will die.\n\nCurrently, only the size (%(objectsize)) is supported end to end in this\nimplementation. The type (%(objecttype)) is known by the client's allow-list\nand request path but is not supported on the server side nor the response\nparsing. A follow up series will add full end-to-end support for %(objecttype).\n\nThe default format for remote-object-info is set to %(objectname) %(objectsize).\nOnce %(objecttype) is supported, the default format will be unified accordingly.\n\nIf the batch command format includes unsupported fields such as %(objecttype),\n%(objectsize:disk), or %(deltabase), the command will return empty strings for\neach unsupported field.\n\nThis series completes Eric's work mainly with the refactor of the validation\nof the placeholder with an allow-list that filters what the client asks with\nwhat the server is capable of provide following Jeff King's idea [4].\n\nI have a question for the design:\n\n1. If the format includes unsupported fields such as %(objecttype) or\n   %(deltabase) it currently returns an empty string for each unsupported\n   field, this follows what for-each-ref does with known but inapplicable\n   atoms. However future placeholders that will be implemented: %(rest),\n   %(objectmode) can return empty strings. How should we differentiate\n   \"unsupported\" vs \"no data\".\n   Eric proposed to use a placeholder like \"???\" [5].\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n[4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n[5]: https://lore.kernel.org/git/CAN2LT1D3d=yMYVhBjpj5PvyjfTVjwqcFPNViuCJ=f49YbCZuJg@mail.gmail.com/\n\nChanges since v11:\n\n- Rebased onto current master.\n- Added fetch-object-info.c to meson.build.\n- Added t1017-cat-file-remote-object-info.sh to t/meson.build.\n- Fixed style feedback from v10.\n- Replaced strstr() validation with a dynamic allow-list in\n  expand_atom() based on server-advertised capabilities, filtering\n  unsupported options in fetch_object_info() before sending.\n- Unsupported placeholders return empty strings instead of die().\n- Initialize data->type to OBJ_BAD with NULL check on type_name().\n- Moved global variables to local scope in parse_cmd_remote_object_info.\n- Decoupled batch_object_write() from sizep so output is always produced.\n- Added remote_atom_map[] for protocol-to-atom name mapping.\n- Included \"type\" in the client request path for forward-compatibility.\n- Fixed memory leak of data->name in transport-helper disconnect.\n- Changed error message \"not our ref\" to \"server does not recognize object\".\n- Split fetch-pack refactoring into two commits (move + refactor).\n- Updated caveats documentation and tests.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nCalvin Wan (3):\n      fetch-pack: move fetch initialization\n      serve: advertise object-info feature\n      transport: add client support for object-info\n\nEric Ju (4):\n      git-compat-util: add strtoul_ul() with error handling\n      cat-file: add declaration of variable i inside its for loop\n      t1006: split test utility functions into new \"lib-cat-file.sh\"\n      cat-file: add remote-object-info to batch-command\n\nPablo Sabater (5):\n      transport-helper: fix memory leak of helper on disconnect\n      fetch-pack: move function to connect.c\n      connect: refactor packet writing\n      cat-file: validate remote atoms with allow_list\n      cat-file: make remote-object-info allow-list dynamic\n\n Documentation/git-cat-file.adoc        |  25 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 191 ++++++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    |  93 +++++\n fetch-object-info.h                    |  22 ++\n fetch-pack.c                           |  51 +--\n fetch-pack.h                           |   2 +\n git-compat-util.h                      |  20 +\n meson.build                            |   1 +\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n serve.c                                |   5 +-\n t/lib-cat-file.sh                      |  16 +\n t/meson.build                          |   1 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 684 +++++++++++++++++++++++++++++++++\n transport-helper.c                     |  13 +-\n transport.c                            |  28 +-\n transport.h                            |  11 +\n 21 files changed, 1158 insertions(+), 74 deletions(-)\n---\nbase-commit: 9ac3f193c05c2237e2b14ebaa1149e9fc8a1abe0\nchange-id: 20260608-ps-eric-work-rebase-b73ae84ba671\n\nBest regards,\n--  \nPablo Sabater <pabloosabaterr@gmail.com>\n"},{"id":"544882","messageId":"20260608-ps-eric-work-rebase-v12-1-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 01/12] transport-helper: fix memory leak of helper on disconnect","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:24Z","receivedAt":"2026-06-08T10:14:51Z","isPatch":true,"body":"disconnect_helper() only frees data inside of the if(data->helper)\nblock [1]. When the transport is disconnected without the helper\nbeing fully started, data->name allocated in transport_helper_init()\nis never freed.\n\nMove FREE_AND_NULL(data->name) outside the conditional block so it's\nalways freed on disconnect.\n\n[1]: https://lore.kernel.org/git/05fbadbae2184479c87c37675dde7bd79b3e32ab.1716465556.git.ps@pks.im/\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n transport-helper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 04d55572a9..20a6ea8f81 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tres = finish_command(data->helper);\n-\t\tFREE_AND_NULL(data->name);\n \t\tFREE_AND_NULL(data->helper);\n \t}\n+\tFREE_AND_NULL(data->name);\n \treturn res;\n }\n \n\n-- \n2.54.0\n"},{"id":"544883","messageId":"20260608-ps-eric-work-rebase-v12-2-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 02/12] git-compat-util: add strtoul_ul() with error handling","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:25Z","receivedAt":"2026-06-08T10:14:52Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nWe already have strtoul_ui() and similar functions that provide proper\nerror handling using strtoul from the standard library. However,\nthere isn't currently a variant that returns an unsigned long.\n\nThis variant is needed in a subsequent commit.\n\nAdd  strtoul_ul() to address this gap, enabling the\nreturn of an unsigned long with proper error handling.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n git-compat-util.h | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 8809776407..4bf569f35c 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n \treturn 0;\n }\n \n+/*\n+ * Convert a string to an unsigned long using the standard library's strtoul,\n+ * with additional error handling to ensure robustness.\n+ */\n+static inline int strtoul_ul(char const *s, int base, unsigned long *result)\n+{\n+\tunsigned long ul;\n+\tchar *p;\n+\n+\terrno = 0;\n+\t/* negative values would be accepted by strtoul */\n+\tif (strchr(s, '-'))\n+\t\treturn -1;\n+\tul = strtoul(s, &p, base);\n+\tif (errno || *p || p == s)\n+\t\treturn -1;\n+\t*result = ul;\n+\treturn 0;\n+}\n+\n static inline int strtol_i(char const *s, int base, int *result)\n {\n \tlong ul;\n\n-- \n2.54.0\n"},{"id":"544884","messageId":"20260608-ps-eric-work-rebase-v12-3-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 03/12] cat-file: add declaration of variable i inside its for loop","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:26Z","receivedAt":"2026-06-08T10:14:53Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSome code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\nWhile at it, we also change its type from \"int\" to \"size_t\" where the latter makes more sense.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c | 11 +++--------\n fetch-pack.c       |  3 +--\n 2 files changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex fa45f774d7..c060fd4800 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -726,12 +726,10 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct queued_cmd *cmd,\n \t\tint nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -739,9 +737,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -768,7 +764,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -778,7 +773,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 120e01f3cf..f13951d154 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n\n-- \n2.54.0\n"},{"id":"544885","messageId":"20260608-ps-eric-work-rebase-v12-4-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 04/12] t1006: split test utility functions into new \"lib-cat-file.sh\"","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:27Z","receivedAt":"2026-06-08T10:14:54Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nThis refactor extracts utility functions from the cat-file's test\nscript \"t1006-cat-file.sh\" into a new \"lib-cat-file.sh\" dedicated\nlibrary file. The goal is to improve code reuse and readability,\nenabling future tests to leverage these utilities without duplicating\ncode.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 13 +------------\n 2 files changed, 17 insertions(+), 12 deletions(-)\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..44af232d74\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 8e2c52652c..8360f3bbd9 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-loose.sh\"\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     object_name=\"$2\"\n\n-- \n2.54.0\n"},{"id":"544886","messageId":"20260608-ps-eric-work-rebase-v12-5-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 05/12] fetch-pack: move function to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:28Z","receivedAt":"2026-06-08T10:14:55Z","isPatch":true,"body":"write_fetch_command_and_capabilities will be refactored in a subsequent\ncommit where it will become a more general-purpose function, making it\nmore accessible to additional commands in the future.\n\nTo move `write_fetch_command_and_capabilities()` to `connect.c`, we need\nto adjust how `advertise_sid` is managed. Previously in `fetch_pack.c`,\n`advertise_sid` was a static variable, modified using\n`repo_config_get_bool()`.\n\nIn `connect.c`, we now initialize `advertise_sid` at the begining by\ndirectly using `repo_config_get_bool()`. This change is safe because:\n\nIn the original `fetch-pack.c` code, there are only two places that write\n`advertise_sid`:\n\n1. In function `do_fetch_pack()`:\n        if (!sever_supports(\"session_id\"))\n               advertise_sid = 0;\n2. In function `fetch_pack_config()`:\n        repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, since `do_fetch_pack()` is only relevant for protocol v1, this\nassignment can be ignored, as `write_fetch_command_and_capabilities()`\nis only used in v2.\n\nAbout 2, `repo_config_get_bool()` is from `config.h` and it's an out-of-box\ndependency of `connect.c`, so we can reuse it directly.\n\nMove `write_fetch_command_and_capabilities()` to `connect.c`\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 31 -------------------------------\n 3 files changed, 38 insertions(+), 31 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 47e39d2a73..1dced8e632 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,6 +700,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(\"fetch\");\n+\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t    the_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t    the_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n static const char *url_scheme_name(enum url_scheme scheme)\n {\n \tswitch (scheme) {\ndiff --git a/connect.h b/connect.h\nindex aa482a37fb..c4f6ea4b0a 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+struct string_list;\n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f13951d154..4a8a70b5f3 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1376,37 +1376,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n\n-- \n2.54.0\n"},{"id":"544887","messageId":"20260608-ps-eric-work-rebase-v12-6-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 06/12] connect: refactor packet writing","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:29Z","receivedAt":"2026-06-08T10:14:56Z","isPatch":true,"body":"Refactor `write_fetch_command_and_capabilities()`, enabling it to serve\nboth fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162\n(object-info: support for retrieving object info, 2021-04-20).\n\nRefactor the function signature to accept a command instead of the\nhardcoded \"fetch\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 10 +++++-----\n connect.h    |  8 ++++++--\n fetch-pack.c |  4 ++--\n 3 files changed, 13 insertions(+), 9 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 1dced8e632..78c69d4485 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,16 +700,16 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options)\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \tint advertise_sid;\n \n \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n@@ -727,7 +727,7 @@ void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n \t\t\t    the_hash_algo->name, hash_name);\n \t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n \t\tdie(_(\"the server does not support algorithm '%s'\"),\n \t\t    the_hash_algo->name);\n \t}\ndiff --git a/connect.h b/connect.h\nindex c4f6ea4b0a..8f4c523892 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,8 +34,12 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+/*\n+ * Writes a command along with the requested server capabilities/features into a\n+ * request buffer.\n+ */\n struct string_list;\n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options);\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 4a8a70b5f3..3d32114907 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1387,7 +1387,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2255,7 +2255,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n\n-- \n2.54.0\n"},{"id":"544888","messageId":"20260608-ps-eric-work-rebase-v12-7-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 07/12] fetch-pack: move fetch initialization","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:30Z","receivedAt":"2026-06-08T10:14:57Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3d32114907..cdebd3476f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1736,18 +1736,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n\n-- \n2.54.0\n"},{"id":"544889","messageId":"20260608-ps-eric-work-rebase-v12-8-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 08/12] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:31Z","receivedAt":"2026-06-08T10:14:59Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n serve.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex 49a6e39b1d..2b07d922b3 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\t/* Currently only size is supported */\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n\n-- \n2.54.0\n"},{"id":"544890","messageId":"20260608-ps-eric-work-rebase-v12-9-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 09/12] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:32Z","receivedAt":"2026-06-08T10:15:00Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nThis commit introduces client functions to interact with the server.\n\nCurrently, the client supports requesting a list of object IDs with\nthe 'size' feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client will return an error and exit.\n\nNotice that the entire request is written into req_buf before being\nsent to the remote. This approach follows the pattern used in the\n`send_fetch_request()` logic within fetch-pack.c.\nStreaming the request is not addressed in this patch.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Makefile            |  1 +\n fetch-object-info.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h | 22 ++++++++++++++\n fetch-pack.c        |  3 ++\n fetch-pack.h        |  2 ++\n meson.build         |  1 +\n transport-helper.c  | 11 +++++--\n transport.c         | 28 ++++++++++++++++-\n transport.h         | 11 +++++++\n 9 files changed, 163 insertions(+), 3 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex b31ecb0756..45453e03c5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1159,6 +1159,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..51a898430d\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,87 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"odb.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends git-cat-file object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"unsupported protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\tsize_index = i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n+\t\t\t\tdie(\"object-info: server does not recognize object %s\",\n+\t\t\t\t    object_info_values.items[0].string);\n+\n+\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))\n+\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t\t    object_info_values.items[0].string,\n+\t\t\t\t    object_info_values.items[1 + size_index].string);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..d35284bd6b\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"odb.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex cdebd3476f..a86c93fc52 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1742,6 +1742,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 6d0dec7f41..5a428f11ed 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -43,6 +44,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\ndiff --git a/meson.build b/meson.build\nindex 064fe2e2f1..7b7dec5949 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -347,6 +347,7 @@ libgit_sources = [\n   'exec-cmd.c',\n   'fetch-negotiator.c',\n   'fetch-pack.c',\n+  'fetch-object-info.c',\n   'fmt-merge-msg.c',\n   'fsck.c',\n   'fsmonitor.c',\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a6ea8f81..4adb73bfbc 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -727,8 +727,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file\n+\t * remote-object-info require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -744,6 +744,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 0f5ec30247..7d3246e12b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -467,8 +468,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.negotiation_restrict_tips = data->options.negotiation_restrict_tips;\n \targs.negotiation_include_tips = data->options.negotiation_include_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options->object_info\n+\t    && transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct packet_reader reader;\n+\t\tstruct object_info_args obj_info_args = { 0 };\n+\n+\t\tobj_info_args.server_options = transport->server_options;\n+\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n+\t\tstring_list_sort(obj_info_args.object_info_options);\n+\n+\t\tconnect_setup(transport, 0);\n+\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\t\tdata->version = discover_version(&reader);\n+\t\ttransport->hash_algo = reader.hash_algo;\n+\n+\t\tret = fetch_object_info(data->version, &obj_info_args, &reader,\n+\t\t\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n+\t\t\t\t\tdata->fd[1]);\n+\t\tgoto cleanup;\n \n-\tif (!data->finished_handshake) {\n+\t} else if (!data->finished_handshake) {\n \t\tint i;\n \t\tint must_list_refs = 0;\n \t\tfor (i = 0; i < nr_heads; i++) {\ndiff --git a/transport.h b/transport.h\nindex 7e5867cffa..bd60b10af4 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -6,6 +6,7 @@\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n #include \"connect.h\"\n+#include \"odb.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -31,6 +32,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to retrieve only object-info.\n+\t * If object-info is not supported, the operation will error and exit.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -55,6 +62,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n\n-- \n2.54.0\n"},{"id":"544892","messageId":"20260608-ps-eric-work-rebase-v12-10-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 10/12] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:33Z","receivedAt":"2026-06-08T10:15:01Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSince the `info` command in `cat-file --batch-command` prints object\ninfo for a given object, it is natural to add another command in\n`cat-file --batch-command` to print object info for a given object\nfrom a remote.\n\nAdd `remote-object-info` to `cat-file --batch-command`.\n\nWhile `info` takes object ids one at a time, this creates\noverhead when making requests to a server. So `remote-object-info`\ninstead can take multiple object ids at once.\n\nThe `cat-file --batch-command` command is generally implemented in\nthe following manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for `remote-object-info` to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  24 +-\n builtin/cat-file.c                     | 128 ++++++-\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n t/meson.build                          |   1 +\n t/t1017-cat-file-remote-object-info.sh | 664 +++++++++++++++++++++++++++++++++\n transport.c                            |   4 +-\n 7 files changed, 827 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex 86b9181599..aba20eb770 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -169,6 +169,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -312,7 +319,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectmode`::\n \tIf the specified object has mode information (such as a tree or\n@@ -325,13 +333,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -341,7 +350,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -438,6 +450,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will raise\n+an error and exit when they appear in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex c060fd4800..a14cfea815 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -29,6 +29,22 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/*\n+ * Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8 * 1024)\n+\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE \\\n+\t(MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -639,6 +655,67 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt,\n+\t\t\t   int argc,\n+\t\t\t   const char **argv,\n+\t\t\t   struct object_info **remote_object_info,\n+\t\t\t   struct oid_array *object_info_oids)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT.\n+\t */\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(object_info_oids);\n+\tfor (int i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid))\n+\t\t\tdie(_(\"Not a valid object name %s\"), argv[i]);\n+\t\toid_array_append(object_info_oids, &oid);\n+\t}\n+\tif (!object_info_oids->nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\n+\tif (!gtransport->smart_options) {\n+\t\tretval = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n+\tgtransport->smart_options->object_info = 1;\n+\tgtransport->smart_options->object_info_oids = object_info_oids;\n+\n+\t/* 'objectsize' is the only option currently supported */\n+\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\tstring_list_append(&object_info_options, \"size\");\n+\n+\tif (object_info_options.nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t}\n+cleanup:\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -720,6 +797,54 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\tstatic struct object_info *remote_object_info;\n+\tstatic struct oid_array object_info_oids = OID_ARRAY_INIT;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command too long\"));\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"split remote-object-info command\"));\n+\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n+\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n+\t\t    MAX_ALLOWED_OBJ_LIMIT);\n+\n+\tif (get_remote_info(opt, count, argv, &remote_object_info,\n+\t\t\t    &object_info_oids))\n+\t\tgoto cleanup;\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -751,8 +876,9 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1 },\n \t{ \"info\", parse_cmd_info, 1 },\n-\t{ \"flush\", NULL, 0 },\n \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n+\t{ \"flush\", NULL, 0 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex 90f995d000..9608607847 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2356,3 +2356,13 @@ int odb_source_loose_read_object_stream(struct odb_read_stream **out,\n \tfree(st);\n \treturn -1;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+}\ndiff --git a/odb.h b/odb.h\nindex 73553ed5a7..6d643c5cf3 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -573,4 +573,7 @@ void parse_alternates(const char *string,\n \t\t      const char *relative_base,\n \t\t      struct strvec *out);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* ODB_H */\ndiff --git a/t/meson.build b/t/meson.build\nindex 2af8d01279..3473e477bd 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -170,6 +170,7 @@ integration_tests = [\n   't1014-read-tree-confusing.sh',\n   't1015-read-index-unmerged.sh',\n   't1016-compatObjectFormat.sh',\n+  't1017-cat-file-remote-object-info.sh',\n   't1020-subdirectory.sh',\n   't1022-read-tree-partial-clone.sh',\n   't1050-large.sh',\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..5c8c07b0cf\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,664 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unspported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"Not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on missing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit clone \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" missing_oid_repo &&\n+\t\ttest_commit -C missing_oid_repo message1 c.txt &&\n+\t\tcd missing_oid_repo &&\n+\n+\t\tobject_id=$(git rev-parse message1:c.txt) &&\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $object_id\n+\t\tEOF\n+\t\ttest_grep \"object-info: server does not recognize object $object_id\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\ndiff --git a/transport.c b/transport.c\nindex 7d3246e12b..81faf8e748 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -470,8 +470,8 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n \targs.object_info = transport->smart_options->object_info;\n \n-\tif (transport->smart_options->object_info\n-\t    && transport->smart_options->object_info_oids->nr > 0) {\n+\tif (transport->smart_options->object_info &&\n+\t    transport->smart_options->object_info_oids->nr > 0) {\n \t\tstruct packet_reader reader;\n \t\tstruct object_info_args obj_info_args = { 0 };\n \n\n-- \n2.54.0\n"},{"id":"544891","messageId":"20260608-ps-eric-work-rebase-v12-11-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 11/12] cat-file: validate remote atoms with allow_list","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:34Z","receivedAt":"2026-06-08T10:15:02Z","isPatch":true,"body":"strstr() is not enough to validate the format placeholders in\nremote-object-info causing two errors:\n\n- Atoms recognized by expand_atom() but the remote doesn't returns 1, but\n  data->type contains garbage causing segfault.\n- expand_atom() returns 0 for unknown atoms, calling\n  strbuf_expand_bad_format() which ends in die() blocking local queries\n  if the same format is shared.\n\nAdd an allow_list with the supported atoms at the top of expand_atom().\nIn remote mode, unsupported atoms return 1 leaving the sb empty,\nhonoring how for-each-ref handles known but inapplicable atoms.\n\nAs extra safety, initialize data->type to OBJ_BAD and add a NULL check\nfor type_name() so uninitialized data doesn't cause segfault.\n\nUpdate tests that expect previous die() behaviour to expect an empty\nstring and add an explicit test for empty string return on unknown\nplaceholder.\n\nUpdate caveat behaviour documentation.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  5 +++--\n builtin/cat-file.c                     | 41 +++++++++++++++++++++++++++-------\n t/t1017-cat-file-remote-object-info.sh | 28 +++++++++++++++++++----\n 3 files changed, 60 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex aba20eb770..3b7a85b383 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -451,8 +451,9 @@ CAVEATS\n -------\n \n Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n-currently not supported by the `remote-object-info` command, we will raise\n-an error and exit when they appear in the format string.\n+currently not supported by the `remote-object-info` command, they will\n+return an empty string for remote queries, matching how `for-each-ref`\n+behaves for known but inapplicable placeholders.\n \n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex a14cfea815..1166a046b4 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -336,8 +336,18 @@ struct expand_data {\n \t * optimized out.\n \t */\n \tunsigned skip_object_info : 1;\n+\n+\t/*\n+\t * Flags about when an object info is being fetched from remote.\n+\t */\n+\tunsigned is_remote:1;\n+};\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n+\n+static const char *remote_object_info_atoms[] = {\n+\t\"objectname\",\n+\t\"objectsize\",\n };\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -348,14 +358,31 @@ static int is_atom(const char *atom, const char *s, int slen)\n static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n+\tif (data->is_remote) {\n+\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n+\t\tfor (i = 0; i < allowed_nr; i++)\n+\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * On remote, skip unsupported atoms returning an empty sb,\n+\t\t * honoring how for-each-ref handles known but inapplicable\n+\t\t * atoms (e.g. %(tagger)).\n+\t\t */\n+\t\tif (i == allowed_nr)\n+\t\t\treturn 1;\n+\t}\n+\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n \t\t\tstrbuf_addstr(sb, oid_to_hex(&data->oid));\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n-\t\tif (data->mark_query)\n+\t\tif (data->mark_query) {\n \t\t\tdata->info.typep = &data->type;\n-\t\telse\n-\t\t\tstrbuf_addstr(sb, type_name(data->type));\n+\t\t} else {\n+\t\t\tconst char *t = type_name(data->type);\n+\t\t\tstrbuf_addstr(sb, t ? t : \"\");\n+\t\t}\n \t} else if (is_atom(\"objectsize\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.sizep = &data->size;\n@@ -699,10 +726,6 @@ static int get_remote_info(struct batch_options *opt,\n \tgtransport->smart_options->object_info = 1;\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\t/* 'objectsize' is the only option currently supported */\n-\tif (!strstr(opt->format, \"%(objectsize)\"))\n-\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n-\n \tstring_list_append(&object_info_options, \"size\");\n \n \tif (object_info_options.nr > 0) {\n@@ -832,7 +855,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n \t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tdata->is_remote = 1;\n \t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t\tdata->is_remote = 0;\n \t\t}\n \t}\n \tdata->skip_object_info = 0;\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 5c8c07b0cf..0a78aabf3b 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -205,6 +205,22 @@ info $tag_oid\n \t)\n '\n \n+# This tests depends on %(objecttype) not being supported yet, once supported\n+# it needs to be updated.\n+test_expect_success 'unsupported placeholder on remote returns empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n # Test --batch-command remote-object-info with 'git://' and\n # transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n@@ -544,10 +560,12 @@ test_expect_success 'remote-object-info fails on unspported filter option (objec\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\techo \"$hello_oid \" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize:disk)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n@@ -556,10 +574,12 @@ test_expect_success 'remote-object-info fails on unspported filter option (delta\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(deltabase)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n\n-- \n2.54.0\n"},{"id":"544893","messageId":"20260608-ps-eric-work-rebase-v12-12-5338b766e658@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v12 12/12] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-08T10:14:35Z","receivedAt":"2026-06-08T10:15:03Z","isPatch":true,"body":"The static allow-list in expand_atom() is hardcoded to only allow\n\"objectname\" and \"objectsize\" for remote queries. This works because\nup to this point all servers will either support object-info with name\nand size or they do not support them at all, but we cannot expect that\nin a future different servers with different git versions to have the\nsame object-info capabilities. Therefore, the allow_list needs to be\ndynamic depending on what does the server advertise.\n\nThe client will now:\n\n1. Request the protocol option that the placeholder refers to (i.e.\n   \"size\" when \"%(objectsize)\").\n\n2. Filters the request in fetch_object_info() dropping any option that\n   the server does not advertise.\n\n3. After the fetching, the options that haven't been dropped are the ones\n   fetched and supported by the server, these supported options are\n   mapped and remote_allowed_atoms is populated with the placeholders.\n\n4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n   the static allow_list had.\n\nMove object_info_options out of get_remote_info so the caller which has\ndata can select what options will be requested instead of requesting\nalways size.\nMove batch_object_write() out so there will always be an output even if\nall the placeholders are not supported by the server (returns an empty\nline).\n\nInclude \"type\" in the object_info_options so once the server supports\nit, the clients know already how to request it.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c  | 85 ++++++++++++++++++++++++++++++++---------------------\n fetch-object-info.c |  6 ++++\n 2 files changed, 58 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 1166a046b4..055991b5af 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -341,13 +341,10 @@ struct expand_data {\n \t * Flags about when an object info is being fetched from remote.\n \t */\n \tunsigned is_remote:1;\n-};\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n \n-static const char *remote_object_info_atoms[] = {\n-\t\"objectname\",\n-\t\"objectsize\",\n+\tstruct string_list remote_allowed_atoms;\n };\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -359,17 +356,11 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n \tif (data->is_remote) {\n-\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n-\t\tfor (i = 0; i < allowed_nr; i++)\n-\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\tsize_t i;\n+\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n \t\t\t\tbreak;\n-\n-\t\t/*\n-\t\t * On remote, skip unsupported atoms returning an empty sb,\n-\t\t * honoring how for-each-ref handles known but inapplicable\n-\t\t * atoms (e.g. %(tagger)).\n-\t\t */\n-\t\tif (i == allowed_nr)\n+\t\tif (i == data->remote_allowed_atoms.nr)\n \t\t\treturn 1;\n \t}\n \n@@ -686,12 +677,12 @@ static int get_remote_info(struct batch_options *opt,\n \t\t\t   int argc,\n \t\t\t   const char **argv,\n \t\t\t   struct object_info **remote_object_info,\n-\t\t\t   struct oid_array *object_info_oids)\n+\t\t\t   struct oid_array *object_info_oids,\n+\t\t\t   struct string_list *object_info_options)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n \tstruct object_id oid;\n-\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tstatic struct transport *gtransport;\n \n \t/*\n@@ -726,15 +717,12 @@ static int get_remote_info(struct batch_options *opt,\n \tgtransport->smart_options->object_info = 1;\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\tstring_list_append(&object_info_options, \"size\");\n-\n-\tif (object_info_options.nr > 0) {\n-\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tif (object_info_options->nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = object_info_options;\n \t\tgtransport->smart_options->object_info_data = *remote_object_info;\n \t\tretval = transport_fetch_refs(gtransport, NULL);\n \t}\n cleanup:\n-\tstring_list_clear(&object_info_options, 0);\n \ttransport_disconnect(gtransport);\n \treturn retval;\n }\n@@ -820,6 +808,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+struct protocol_placeholder_entry {\n+\tconst char *option;\n+\tconst char *atom;\n+};\n+\n+static const struct protocol_placeholder_entry remote_atom_map[] = {\n+\t{\"size\", \"objectsize\"},\n+\t{\"type\", \"objecttype\"},\n+\t/*\n+\t * Add new protocol options here. Even if the server doesn't support\n+\t * them the allow_list will drop them if the server doesn't advertise\n+\t * them.\n+\t */\n+};\n+\n static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n@@ -829,6 +832,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstatic struct object_info *remote_object_info;\n \tstatic struct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \n \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n \t\tdie(_(\"remote-object-info command too long\"));\n@@ -841,30 +845,44 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n+\tif (data->info.sizep)\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\tif (data->info.typep)\n+\t\tstring_list_append(&object_info_options, \"type\");\n+\n \tif (get_remote_info(opt, count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids))\n+\t\t\t    &object_info_oids, &object_info_options))\n \t\tgoto cleanup;\n \n+\tstring_list_clear(&data->remote_allowed_atoms, 0);\n+\tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n+\tfor (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n+\t\tif (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n+\t\t\tstring_list_append(&data->remote_allowed_atoms,\n+\t\t\t\t\t   remote_atom_map[i].atom);\n+\n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n \t\tdata->oid = object_info_oids.oid[i];\n-\t\tif (remote_object_info[i].sizep) {\n-\t\t\t/*\n-\t\t\t * When reaching here, it means remote-object-info can retrieve\n-\t\t\t * information from server without downloading them.\n-\t\t\t */\n+\t\t/*\n+\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t * information from server without downloading them.\n+\t\t */\n+\t\tif (remote_object_info[i].sizep)\n \t\t\tdata->size = *remote_object_info[i].sizep;\n-\t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tdata->is_remote = 1;\n-\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n-\t\t\tdata->is_remote = 0;\n-\t\t}\n+\t\tif (remote_object_info[i].typep)\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tdata->is_remote = 1;\n+\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n \n cleanup:\n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n+\tstring_list_clear(&object_info_options, 0);\n \tfree(line_to_split);\n \tfree(argv);\n \tfree(remote_object_info);\n@@ -1177,6 +1195,7 @@ static int batch_objects(struct batch_options *opt)\n  cleanup:\n \tstrbuf_release(&input);\n \tstrbuf_release(&output);\n+\tstring_list_clear(&data.remote_allowed_atoms, 0);\n \twarn_on_object_refname_ambiguity = save_warning;\n \treturn retval;\n }\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 51a898430d..425929a269 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -39,6 +39,12 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \tcase protocol_v2:\n \t\tif (!server_supports_v2(\"object-info\"))\n \t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\n+\t\tfor (int i = args->object_info_options->nr - 1; i >= 0; i--)\n+\t\t\tif (!server_supports_feature(\"object-info\",\n+\t\t\t\t\t\t     args->object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(args->object_info_options, i, 0);\n+\n \t\tsend_object_info_request(fd_out, args);\n \t\tbreak;\n \tcase protocol_v1:\n\n-- \n2.54.0\n"},{"id":"544934","messageId":"xmqqzf15w0cz.fsf@gitster.g","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-3-5338b766e658@gmail.com","subject":"Re: [PATCH GSoC RFC v12 03/12] cat-file: add declaration of variable i inside its for loop","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-08T14:52:28Z","receivedAt":"2026-06-08T14:52:30Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> From: Eric Ju <eric.peijian@gmail.com>\n> Subject: Re: [PATCH GSoC RFC v12 03/12] cat-file: add declaration of variable i inside its for loop\n\n\"add\" sounds a bit strange, as the existing code wouldn't have\ncompiled if the variable were never declared.  What the patch did\nwas to move (not add) the declaration of a function scope variable\nthat is used to control for() loops.  Would any of these work?\n\nSubject: [PATCH GSOC v12 03/12] cat-file: narrow scope of loop counter\nSubject: [PATCH GSOC v12 03/12] cat-file: declare loop counter inside for()\n\n> Some code used in this series declares variable i and only uses it\n> in a for loop, not in any other logic outside the loop.\n>\n> Change the declaration of i to be inside the for loop for readability.\n> While at it, we also change its type from \"int\" to \"size_t\" where the latter makes more sense.\n\nCurious single line that is overly long?\n\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  builtin/cat-file.c | 11 +++--------\n>  fetch-pack.c       |  3 +--\n>  2 files changed, 4 insertions(+), 10 deletions(-)\n>\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index fa45f774d7..c060fd4800 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -726,12 +726,10 @@ static void dispatch_calls(struct batch_options *opt,\n>  \t\tstruct queued_cmd *cmd,\n>  \t\tint nr)\n>  {\n> -\tint i;\n> -\n>  \tif (!opt->buffer_output)\n>  \t\tdie(_(\"flush is only for --buffer mode\"));\n>  \n> -\tfor (i = 0; i < nr; i++)\n> +\tfor (size_t i = 0; i < nr; i++)\n>  \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n\nThe loop limit \"nr\" will not become as large as size_t because the\ncaller passes a platform natural \"int\" to the function.  Wouldn't a\nstupid compiler give us warning on comparing unsigned size_t with\nsigned int here?\n\n> @@ -739,9 +737,7 @@ static void dispatch_calls(struct batch_options *opt,\n>  \n>  static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n>  {\n> -\tsize_t i;\n> -\n> -\tfor (i = 0; i < *nr; i++)\n> +\tfor (size_t i = 0; i < *nr; i++)\n>  \t\tFREE_AND_NULL(cmd[i].line);\n\nNo type change, so the result is as safe as the original.\n\n> @@ -768,7 +764,6 @@ static void batch_objects_command(struct batch_options *opt,\n>  \tsize_t alloc = 0, nr = 0;\n>  \n>  \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n> -\t\tint i;\n>  \t\tconst struct parse_cmd *cmd = NULL;\n>  \t\tconst char *p = NULL, *cmd_end;\n>  \t\tstruct queued_cmd call = {0};\n> @@ -778,7 +773,7 @@ static void batch_objects_command(struct batch_options *opt,\n>  \t\tif (isspace(*input.buf))\n>  \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n>  \n> -\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n> +\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n>  \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n>  \t\t\t\tcontinue;\n\nARRAY_SIZE() is some arithmetic over sizeof(*commands) and\nsizeof(commands), which is of type size_t, so this is better than\nthe original.  Use of size_t i of course is a natural way to index\ninto commands[] array, so the result is just fine.\n\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index 120e01f3cf..f13951d154 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>  \tif (advertise_sid && server_supports_v2(\"session-id\"))\n>  \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n>  \tif (server_options && server_options->nr) {\n> -\t\tint i;\n>  \t\tensure_server_supports_v2(\"server-option\");\n> -\t\tfor (i = 0; i < server_options->nr; i++)\n> +\t\tfor (size_t i = 0; i < server_options->nr; i++)\n>  \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n>  \t\t\t\t\t server_options->items[i].string);\n\nserver_options is a string_list whose .nr member is of type size_t,\nso this comparison is perfectly fine.  Ditto for ->items[i].string\nthat is a natural way to index into an array.\n\n>  \t}\n\nv\n"},{"id":"545012","messageId":"CA+J6zkST0Hs-X-2csfnfri4UVdK-yBDdQALkBcRK62a88aZ15Q@mail.gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-2-5338b766e658@gmail.com","subject":"Re: [PATCH GSoC RFC v12 02/12] git-compat-util: add strtoul_ul() with error handling","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-09T06:20:20Z","receivedAt":"2026-06-09T06:20:49Z","isPatch":true,"body":"On Mon, 8 Jun 2026 at 15:44, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> From: Eric Ju <eric.peijian@gmail.com>\n>\n> We already have strtoul_ui() and similar functions that provide proper\n> error handling using strtoul from the standard library. However,\n> there isn't currently a variant that returns an unsigned long.\n>\n> This variant is needed in a subsequent commit.\n>\n> Add  strtoul_ul() to address this gap, enabling the\n\nNit: extra space here. Also, this could be conciser. Maybe something like:\n\n\"This variant is needed in a subsequent commit to enable returning an\nunsigned long with proper error handling.\"\n\n> return of an unsigned long with proper error handling.\n>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  git-compat-util.h | 20 ++++++++++++++++++++\n>  1 file changed, 20 insertions(+)\n>\n> diff --git a/git-compat-util.h b/git-compat-util.h\n> index 8809776407..4bf569f35c 100644\n> --- a/git-compat-util.h\n> +++ b/git-compat-util.h\n> @@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n>         return 0;\n>  }\n>\n> +/*\n> + * Convert a string to an unsigned long using the standard library's strtoul,\n> + * with additional error handling to ensure robustness.\n> + */\n> +static inline int strtoul_ul(char const *s, int base, unsigned long *result)\n> +{\n> +       unsigned long ul;\n> +       char *p;\n> +\n> +       errno = 0;\n> +       /* negative values would be accepted by strtoul */\n> +       if (strchr(s, '-'))\n> +               return -1;\n> +       ul = strtoul(s, &p, base);\n> +       if (errno || *p || p == s)\n> +               return -1;\n> +       *result = ul;\n> +       return 0;\n> +}\n> +\n>  static inline int strtol_i(char const *s, int base, int *result)\n>  {\n>         long ul;\n>\n> --\n> 2.54.0\n"},{"id":"545014","messageId":"CA+J6zkQe=K80QUOH8LwXCRw9nxv3tHBg+FtfDsYedY5xdHW79A@mail.gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-4-5338b766e658@gmail.com","subject":"Re: [PATCH GSoC RFC v12 04/12] t1006: split test utility functions into new \"lib-cat-file.sh\"","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-09T06:28:51Z","receivedAt":"2026-06-09T06:29:20Z","isPatch":true,"body":"On Mon, 8 Jun 2026 at 15:44, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> From: Eric Ju <eric.peijian@gmail.com>\n>\n> This refactor extracts utility functions from the cat-file's test\n> script \"t1006-cat-file.sh\" into a new \"lib-cat-file.sh\" dedicated\n> library file. The goal is to improve code reuse and readability,\n> enabling future tests to leverage these utilities without duplicating\n> code.\n\nHmm, seems like a premature change to me. Do any of the subsequent\ncommits require this refactor? Maybe the follow-up series that enables\n%objecttype support needs it? Did someone request this change in v11's\nfeedback?\n\nIf any of those are true, I think it's worthwhile mentioning it here. That will\nmake it easier to determine whether this change is truly necessary.\n\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  t/lib-cat-file.sh   | 16 ++++++++++++++++\n>  t/t1006-cat-file.sh | 13 +------------\n>  2 files changed, 17 insertions(+), 12 deletions(-)\n>\n> diff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\n> new file mode 100644\n> index 0000000000..44af232d74\n> --- /dev/null\n> +++ b/t/lib-cat-file.sh\n> @@ -0,0 +1,16 @@\n> +# Library of git-cat-file related test functions.\n> +\n> +# Print a string without a trailing newline.\n> +echo_without_newline () {\n> +       printf '%s' \"$*\"\n> +}\n> +\n> +# Print a string without newlines and replace them with a NULL character (\\0).\n> +echo_without_newline_nul () {\n> +       echo_without_newline \"$@\" | tr '\\n' '\\0'\n> +}\n> +\n> +# Calculate the length of a string.\n> +strlen () {\n> +       echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> +}\n> diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\n> index 8e2c52652c..8360f3bbd9 100755\n> --- a/t/t1006-cat-file.sh\n> +++ b/t/t1006-cat-file.sh\n> @@ -4,6 +4,7 @@ test_description='git cat-file'\n>\n>  . ./test-lib.sh\n>  . \"$TEST_DIRECTORY/lib-loose.sh\"\n> +. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n>\n>  test_cmdmode_usage () {\n>         test_expect_code 129 \"$@\" 2>err &&\n> @@ -99,18 +100,6 @@ do\n>         '\n>  done\n>\n> -echo_without_newline () {\n> -    printf '%s' \"$*\"\n> -}\n> -\n> -echo_without_newline_nul () {\n> -       echo_without_newline \"$@\" | tr '\\n' '\\0'\n> -}\n> -\n> -strlen () {\n> -    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> -}\n> -\n>  run_tests () {\n>      type=$1\n>      object_name=\"$2\"\n>\n> --\n> 2.54.0\n"},{"id":"545079","messageId":"CA+J6zkQ22en2HgH03EedKOfC+jLcHH2UbwpH0h_bDEAHR6B2pg@mail.gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-12-5338b766e658@gmail.com","subject":"Re: [PATCH GSoC RFC v12 12/12] cat-file: make remote-object-info allow-list dynamic","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-09T15:32:13Z","receivedAt":"2026-06-09T15:32:42Z","isPatch":true,"body":"On Mon, 8 Jun 2026 at 15:45, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> The static allow-list in expand_atom() is hardcoded to only allow\n> \"objectname\" and \"objectsize\" for remote queries. This works because\n> up to this point all servers will either support object-info with name\n> and size or they do not support them at all, but we cannot expect that\n> in a future different servers with different git versions to have the\n> same object-info capabilities. Therefore, the allow_list needs to be\n> dynamic depending on what does the server advertise.\n>\n> The client will now:\n>\n> 1. Request the protocol option that the placeholder refers to (i.e.\n>    \"size\" when \"%(objectsize)\").\n>\n> 2. Filters the request in fetch_object_info() dropping any option that\n>    the server does not advertise.\n>\n> 3. After the fetching, the options that haven't been dropped are the ones\n>    fetched and supported by the server, these supported options are\n>    mapped and remote_allowed_atoms is populated with the placeholders.\n>\n> 4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n>    the static allow_list had.\n>\n> Move object_info_options out of get_remote_info so the caller which has\n> data can select what options will be requested instead of requesting\n> always size.\n> Move batch_object_write() out so there will always be an output even if\n> all the placeholders are not supported by the server (returns an empty\n> line).\n>\n> Include \"type\" in the object_info_options so once the server supports\n> it, the clients know already how to request it.\n>\n> Mentored-by: Karthik Nayak <karthik.188@gmail.com>\n> Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  builtin/cat-file.c  | 85 ++++++++++++++++++++++++++++++++---------------------\n>  fetch-object-info.c |  6 ++++\n>  2 files changed, 58 insertions(+), 33 deletions(-)\n>\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 1166a046b4..055991b5af 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -341,13 +341,10 @@ struct expand_data {\n>          * Flags about when an object info is being fetched from remote.\n>          */\n>         unsigned is_remote:1;\n> -};\n> -#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n>\n> -static const char *remote_object_info_atoms[] = {\n> -       \"objectname\",\n> -       \"objectsize\",\n> +       struct string_list remote_allowed_atoms;\n>  };\n> +#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n>\n>  static int is_atom(const char *atom, const char *s, int slen)\n>  {\n> @@ -359,17 +356,11 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n>                        struct expand_data *data)\n>  {\n>         if (data->is_remote) {\n> -               size_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n> -               for (i = 0; i < allowed_nr; i++)\n> -                       if (is_atom(remote_object_info_atoms[i], atom, len))\n> +               size_t i;\n> +               for (i = 0; i < data->remote_allowed_atoms.nr; i++)\n> +                       if (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n>                                 break;\n> -\n> -               /*\n> -                * On remote, skip unsupported atoms returning an empty sb,\n> -                * honoring how for-each-ref handles known but inapplicable\n> -                * atoms (e.g. %(tagger)).\n> -                */\n> -               if (i == allowed_nr)\n> +               if (i == data->remote_allowed_atoms.nr)\n>                         return 1;\n>         }\n>\n> @@ -686,12 +677,12 @@ static int get_remote_info(struct batch_options *opt,\n>                            int argc,\n>                            const char **argv,\n>                            struct object_info **remote_object_info,\n> -                          struct oid_array *object_info_oids)\n> +                          struct oid_array *object_info_oids,\n> +                          struct string_list *object_info_options)\n>  {\n>         int retval = 0;\n>         struct remote *remote = NULL;\n>         struct object_id oid;\n> -       struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n>         static struct transport *gtransport;\n>\n>         /*\n> @@ -726,15 +717,12 @@ static int get_remote_info(struct batch_options *opt,\n>         gtransport->smart_options->object_info = 1;\n>         gtransport->smart_options->object_info_oids = object_info_oids;\n>\n> -       string_list_append(&object_info_options, \"size\");\n> -\n> -       if (object_info_options.nr > 0) {\n> -               gtransport->smart_options->object_info_options = &object_info_options;\n> +       if (object_info_options->nr > 0) {\n> +               gtransport->smart_options->object_info_options = object_info_options;\n>                 gtransport->smart_options->object_info_data = *remote_object_info;\n>                 retval = transport_fetch_refs(gtransport, NULL);\n>         }\n>  cleanup:\n> -       string_list_clear(&object_info_options, 0);\n>         transport_disconnect(gtransport);\n>         return retval;\n>  }\n> @@ -820,6 +808,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n>                 load_mailmap();\n>  }\n>\n> +struct protocol_placeholder_entry {\n> +       const char *option;\n> +       const char *atom;\n> +};\n> +\n> +static const struct protocol_placeholder_entry remote_atom_map[] = {\n> +       {\"size\", \"objectsize\"},\n> +       {\"type\", \"objecttype\"},\n> +       /*\n> +        * Add new protocol options here. Even if the server doesn't support\n> +        * them the allow_list will drop them if the server doesn't advertise\n> +        * them.\n> +        */\n> +};\n> +\n>  static void parse_cmd_remote_object_info(struct batch_options *opt,\n>                                          const char *line, struct strbuf *output,\n>                                          struct expand_data *data)\n> @@ -829,6 +832,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>         char *line_to_split;\n>         static struct object_info *remote_object_info;\n>         static struct oid_array object_info_oids = OID_ARRAY_INIT;\n> +       struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n>\n>         if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n>                 die(_(\"remote-object-info command too long\"));\n> @@ -841,30 +845,44 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>                 die(_(\"remote-object-info supports at most %d objects\"),\n>                     MAX_ALLOWED_OBJ_LIMIT);\n>\n> +       if (data->info.sizep)\n> +               string_list_append(&object_info_options, \"size\");\n> +       if (data->info.typep)\n> +               string_list_append(&object_info_options, \"type\");\n> +\n>         if (get_remote_info(opt, count, argv, &remote_object_info,\n> -                           &object_info_oids))\n> +                           &object_info_oids, &object_info_options))\n>                 goto cleanup;\n>\n> +       string_list_clear(&data->remote_allowed_atoms, 0);\n> +       string_list_append(&data->remote_allowed_atoms, \"objectname\");\n> +       for (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n> +               if (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n> +                       string_list_append(&data->remote_allowed_atoms,\n> +                                          remote_atom_map[i].atom);\n> +\n>         data->skip_object_info = 1;\n>         for (size_t i = 0; i < object_info_oids.nr; i++) {\n>                 data->oid = object_info_oids.oid[i];\n> -               if (remote_object_info[i].sizep) {\n> -                       /*\n> -                        * When reaching here, it means remote-object-info can retrieve\n> -                        * information from server without downloading them.\n> -                        */\n> +               /*\n> +                * When reaching here, it means remote-object-info can retrieve\n> +                * information from server without downloading them.\n> +                */\n> +               if (remote_object_info[i].sizep)\n>                         data->size = *remote_object_info[i].sizep;\n> -                       opt->batch_mode = BATCH_MODE_INFO;\n> -                       data->is_remote = 1;\n> -                       batch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n> -                       data->is_remote = 0;\n> -               }\n> +               if (remote_object_info[i].typep)\n> +                       data->type = *remote_object_info[i].typep;\n> +               opt->batch_mode = BATCH_MODE_INFO;\n> +               data->is_remote = 1;\n> +               batch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n> +               data->is_remote = 0;\n>         }\n>         data->skip_object_info = 0;\n>\n>  cleanup:\n>         for (size_t i = 0; i < object_info_oids.nr; i++)\n>                 free_object_info_contents(&remote_object_info[i]);\n> +       string_list_clear(&object_info_options, 0);\n>         free(line_to_split);\n>         free(argv);\n>         free(remote_object_info);\n> @@ -1177,6 +1195,7 @@ static int batch_objects(struct batch_options *opt)\n>   cleanup:\n>         strbuf_release(&input);\n>         strbuf_release(&output);\n> +       string_list_clear(&data.remote_allowed_atoms, 0);\n>         warn_on_object_refname_ambiguity = save_warning;\n>         return retval;\n>  }\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> index 51a898430d..425929a269 100644\n> --- a/fetch-object-info.c\n> +++ b/fetch-object-info.c\n> @@ -39,6 +39,12 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>         case protocol_v2:\n>                 if (!server_supports_v2(\"object-info\"))\n>                         die(_(\"object-info capability is not enabled on the server\"));\n> +\n> +               for (int i = args->object_info_options->nr - 1; i >= 0; i--)\n\nIsn't args->object_info_options->nr of type size_t? We should probably\ndo something\nlike:\n\nfor (size_t i = 0; i < args->args->object_info_options->nr; i++)\n\ninstead.\n\n> +                       if (!server_supports_feature(\"object-info\",\n> +                                                    args->object_info_options->items[i].string, 0))\n> +                               unsorted_string_list_delete_item(args->object_info_options, i, 0);\n> +\n>                 send_object_info_request(fd_out, args);\n>                 break;\n>         case protocol_v1:\n>\n> --\n> 2.54.0\n\nOther than these, the patch series LGTM for now.\n\nThanks,\nChandra.\n"},{"id":"545084","messageId":"CAN5EUNQHSd=0z26iG0gk24TEtgg1n8CC+H9bkqRACyErNgLxEA@mail.gmail.com","threadId":"61695","inReplyTo":"CA+J6zkQ22en2HgH03EedKOfC+jLcHH2UbwpH0h_bDEAHR6B2pg@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v12 12/12] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-09T17:34:29Z","receivedAt":"2026-06-09T17:34:42Z","isPatch":true,"body":"El mar, 9 jun 2026 a las 17:32, Chandra Pratap\n(<chandrapratap3519@gmail.com>) escribió:\n>\n> On Mon, 8 Jun 2026 at 15:45, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n> >\n> > The static allow-list in expand_atom() is hardcoded to only allow\n> > \"objectname\" and \"objectsize\" for remote queries. This works because\n> > up to this point all servers will either support object-info with name\n> > and size or they do not support them at all, but we cannot expect that\n> > in a future different servers with different git versions to have the\n> > same object-info capabilities. Therefore, the allow_list needs to be\n> > dynamic depending on what does the server advertise.\n> >\n> > The client will now:\n> >\n> > 1. Request the protocol option that the placeholder refers to (i.e.\n> >    \"size\" when \"%(objectsize)\").\n> >\n> > 2. Filters the request in fetch_object_info() dropping any option that\n> >    the server does not advertise.\n> >\n> > 3. After the fetching, the options that haven't been dropped are the ones\n> >    fetched and supported by the server, these supported options are\n> >    mapped and remote_allowed_atoms is populated with the placeholders.\n> >\n> > 4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n> >    the static allow_list had.\n> >\n> > Move object_info_options out of get_remote_info so the caller which has\n> > data can select what options will be requested instead of requesting\n> > always size.\n> > Move batch_object_write() out so there will always be an output even if\n> > all the placeholders are not supported by the server (returns an empty\n> > line).\n> >\n> > Include \"type\" in the object_info_options so once the server supports\n> > it, the clients know already how to request it.\n> >\n> > Mentored-by: Karthik Nayak <karthik.188@gmail.com>\n> > Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\n> > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> > ---\n> >  builtin/cat-file.c  | 85 ++++++++++++++++++++++++++++++++---------------------\n> >  fetch-object-info.c |  6 ++++\n> >  2 files changed, 58 insertions(+), 33 deletions(-)\n> >\n[snip]\n> > diff --git a/fetch-object-info.c b/fetch-object-info.c\n> > index 51a898430d..425929a269 100644\n> > --- a/fetch-object-info.c\n> > +++ b/fetch-object-info.c\n> > @@ -39,6 +39,12 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n> >         case protocol_v2:\n> >                 if (!server_supports_v2(\"object-info\"))\n> >                         die(_(\"object-info capability is not enabled on the server\"));\n> > +\n> > +               for (int i = args->object_info_options->nr - 1; i >= 0; i--)\n>\n> Isn't args->object_info_options->nr of type size_t? We should probably\n> do something\n> like:\n>\n> for (size_t i = 0; i < args->args->object_info_options->nr; i++)\n>\n> instead.\n\nHi!\n\nvoid unsorted_string_list_delete_item(struct string_list *list, int i,\nint free_util)\n{\n        if (list->strdup_strings)\n                free(list->items[i].string);\n        if (free_util)\n                free(list->items[i].util);\n        list->items[i] = list->items[list->nr-1];\n        list->nr--;\n}\n\n\nI made it backwards because of \"list->items[i] = list->items[list->nr\n- 1];\" If we made it from 0..nr and we delete the first element, for\nthe next iteration, the last element is at [0] but we are on [1] and\nthat swapped element never gets evaluated.\n\nAbout size_t, yes, it is size_t but because we go backwards 0 - 1\nwould fail, also unsorted_string_list_delete_item() signature has \"int\ni\". The options that can be on that list will be a small number so\nthere should be no problem, should I cast it explicitly?\n\n>\n> > +                       if (!server_supports_feature(\"object-info\",\n> > +                                                    args->object_info_options->items[i].string, 0))\n> > +                               unsorted_string_list_delete_item(args->object_info_options, i, 0);\n> > +\n> >                 send_object_info_request(fd_out, args);\n> >                 break;\n> >         case protocol_v1:\n> >\n> > --\n> > 2.54.0\n>\n> Other than these, the patch series LGTM for now.\n>\n> Thanks,\n> Chandra.\n\nThanks,\n\nPablo.\n"},{"id":"545085","messageId":"CAN5EUNQs5aJb54JmvGfKB=NrLFOQzx=3C-CA9m0PJAFRNWsU2A@mail.gmail.com","threadId":"61695","inReplyTo":"CA+J6zkQe=K80QUOH8LwXCRw9nxv3tHBg+FtfDsYedY5xdHW79A@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v12 04/12] t1006: split test utility functions into new \"lib-cat-file.sh\"","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-09T17:44:13Z","receivedAt":"2026-06-09T17:44:25Z","isPatch":true,"body":"El mar, 9 jun 2026 a las 8:29, Chandra Pratap\n(<chandrapratap3519@gmail.com>) escribió:\n>\n> On Mon, 8 Jun 2026 at 15:44, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n> >\n> > From: Eric Ju <eric.peijian@gmail.com>\n> >\n> > This refactor extracts utility functions from the cat-file's test\n> > script \"t1006-cat-file.sh\" into a new \"lib-cat-file.sh\" dedicated\n> > library file. The goal is to improve code reuse and readability,\n> > enabling future tests to leverage these utilities without duplicating\n> > code.\n>\n> Hmm, seems like a premature change to me. Do any of the subsequent\n> commits require this refactor? Maybe the follow-up series that enables\n> %objecttype support needs it? Did someone request this change in v11's\n> feedback?\n>\n> If any of those are true, I think it's worthwhile mentioning it here. That will\n> make it easier to determine whether this change is truly necessary.\n\nYes, these functions are needed for \"t1017\" which is created later in\nthe series [1] for the remote object info tests, so they are both used\nin \"t1006\" (where they were originally) and \"t1017\".\n\n>\n> > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> > ---\n> >  t/lib-cat-file.sh   | 16 ++++++++++++++++\n> >  t/t1006-cat-file.sh | 13 +------------\n> >  2 files changed, 17 insertions(+), 12 deletions(-)\n> >\n> > diff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\n> > new file mode 100644\n> > index 0000000000..44af232d74\n> > --- /dev/null\n> > +++ b/t/lib-cat-file.sh\n> > @@ -0,0 +1,16 @@\n> > +# Library of git-cat-file related test functions.\n> > +\n> > +# Print a string without a trailing newline.\n> > +echo_without_newline () {\n> > +       printf '%s' \"$*\"\n> > +}\n> > +\n> > +# Print a string without newlines and replace them with a NULL character (\\0).\n> > +echo_without_newline_nul () {\n> > +       echo_without_newline \"$@\" | tr '\\n' '\\0'\n> > +}\n> > +\n> > +# Calculate the length of a string.\n> > +strlen () {\n> > +       echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> > +}\n> > diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\n> > index 8e2c52652c..8360f3bbd9 100755\n> > --- a/t/t1006-cat-file.sh\n> > +++ b/t/t1006-cat-file.sh\n> > @@ -4,6 +4,7 @@ test_description='git cat-file'\n> >\n> >  . ./test-lib.sh\n> >  . \"$TEST_DIRECTORY/lib-loose.sh\"\n> > +. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n> >\n> >  test_cmdmode_usage () {\n> >         test_expect_code 129 \"$@\" 2>err &&\n> > @@ -99,18 +100,6 @@ do\n> >         '\n> >  done\n> >\n> > -echo_without_newline () {\n> > -    printf '%s' \"$*\"\n> > -}\n> > -\n> > -echo_without_newline_nul () {\n> > -       echo_without_newline \"$@\" | tr '\\n' '\\0'\n> > -}\n> > -\n> > -strlen () {\n> > -    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n> > -}\n> > -\n> >  run_tests () {\n> >      type=$1\n> >      object_name=\"$2\"\n> >\n> > --\n> > 2.54.0\n\n[1]: https://lore.kernel.org/git/20260608-ps-eric-work-rebase-v12-10-5338b766e658@gmail.com/\n\nThanks,\n\nPablo.\n"},{"id":"545086","messageId":"CAN5EUNQQBRoHUbZtkhLoBX-K7_4Carsxws_fyh1Ac7Lmd_FjKg@mail.gmail.com","threadId":"61695","inReplyTo":"xmqqzf15w0cz.fsf@gitster.g","subject":"Re: [PATCH GSoC RFC v12 03/12] cat-file: add declaration of variable i inside its for loop","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-09T17:55:08Z","receivedAt":"2026-06-09T17:55:20Z","isPatch":true,"body":"El lun, 8 jun 2026 a las 16:52, Junio C Hamano (<gitster@pobox.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> > From: Eric Ju <eric.peijian@gmail.com>\n> > Subject: Re: [PATCH GSoC RFC v12 03/12] cat-file: add declaration of variable i inside its for loop\n>\n> \"add\" sounds a bit strange, as the existing code wouldn't have\n> compiled if the variable were never declared.  What the patch did\n> was to move (not add) the declaration of a function scope variable\n> that is used to control for() loops.  Would any of these work?\n>\n> Subject: [PATCH GSOC v12 03/12] cat-file: narrow scope of loop counter\n> Subject: [PATCH GSOC v12 03/12] cat-file: declare loop counter inside for()\n>\n\nHi!\nTrue, it's better to write \"move\" and about the title, works as well,\nI'll change them for the next version.\n\n> > Some code used in this series declares variable i and only uses it\n> > in a for loop, not in any other logic outside the loop.\n> >\n> > Change the declaration of i to be inside the for loop for readability.\n> > While at it, we also change its type from \"int\" to \"size_t\" where the latter makes more sense.\n>\n> Curious single line that is overly long?\n\nTrue, I'll wrap it up correctly in the next version.\n\n>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> > Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> > ---\n> >  builtin/cat-file.c | 11 +++--------\n> >  fetch-pack.c       |  3 +--\n> >  2 files changed, 4 insertions(+), 10 deletions(-)\n> >\n> > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > index fa45f774d7..c060fd4800 100644\n> > --- a/builtin/cat-file.c\n> > +++ b/builtin/cat-file.c\n> > @@ -726,12 +726,10 @@ static void dispatch_calls(struct batch_options *opt,\n> >               struct queued_cmd *cmd,\n> >               int nr)\n> >  {\n> > -     int i;\n> > -\n> >       if (!opt->buffer_output)\n> >               die(_(\"flush is only for --buffer mode\"));\n> >\n> > -     for (i = 0; i < nr; i++)\n> > +     for (size_t i = 0; i < nr; i++)\n> >               cmd[i].fn(opt, cmd[i].line, output, data);\n>\n> The loop limit \"nr\" will not become as large as size_t because the\n> caller passes a platform natural \"int\" to the function.  Wouldn't a\n> stupid compiler give us warning on comparing unsigned size_t with\n> signed int here?\n\nYes, I'll change \"i\" to be int.\n\n>\n> > @@ -739,9 +737,7 @@ static void dispatch_calls(struct batch_options *opt,\n> >\n> >  static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n> >  {\n> > -     size_t i;\n> > -\n> > -     for (i = 0; i < *nr; i++)\n> > +     for (size_t i = 0; i < *nr; i++)\n> >               FREE_AND_NULL(cmd[i].line);\n>\n> No type change, so the result is as safe as the original.\n>\n> > @@ -768,7 +764,6 @@ static void batch_objects_command(struct batch_options *opt,\n> >       size_t alloc = 0, nr = 0;\n> >\n> >       while (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n> > -             int i;\n> >               const struct parse_cmd *cmd = NULL;\n> >               const char *p = NULL, *cmd_end;\n> >               struct queued_cmd call = {0};\n> > @@ -778,7 +773,7 @@ static void batch_objects_command(struct batch_options *opt,\n> >               if (isspace(*input.buf))\n> >                       die(_(\"whitespace before command: '%s'\"), input.buf);\n> >\n> > -             for (i = 0; i < ARRAY_SIZE(commands); i++) {\n> > +             for (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n> >                       if (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n> >                               continue;\n>\n> ARRAY_SIZE() is some arithmetic over sizeof(*commands) and\n> sizeof(commands), which is of type size_t, so this is better than\n> the original.  Use of size_t i of course is a natural way to index\n> into commands[] array, so the result is just fine.\n>\n> > diff --git a/fetch-pack.c b/fetch-pack.c\n> > index 120e01f3cf..f13951d154 100644\n> > --- a/fetch-pack.c\n> > +++ b/fetch-pack.c\n> > @@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> >       if (advertise_sid && server_supports_v2(\"session-id\"))\n> >               packet_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n> >       if (server_options && server_options->nr) {\n> > -             int i;\n> >               ensure_server_supports_v2(\"server-option\");\n> > -             for (i = 0; i < server_options->nr; i++)\n> > +             for (size_t i = 0; i < server_options->nr; i++)\n> >                       packet_buf_write(req_buf, \"server-option=%s\",\n> >                                        server_options->items[i].string);\n>\n> server_options is a string_list whose .nr member is of type size_t,\n> so this comparison is perfectly fine.  Ditto for ->items[i].string\n> that is a natural way to index into an array.\n>\n> >       }\n>\n> v\n\nThanks,\n\nPablo.\n"},{"id":"545088","messageId":"xmqqfr2v1r4l.fsf@gitster.g","threadId":"61695","inReplyTo":"CA+J6zkQ22en2HgH03EedKOfC+jLcHH2UbwpH0h_bDEAHR6B2pg@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v12 12/12] cat-file: make remote-object-info allow-list dynamic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-09T18:54:34Z","receivedAt":"2026-06-09T18:54:37Z","isPatch":true,"body":"Chandra Pratap <chandrapratap3519@gmail.com> writes:\n\n> On Mon, 8 Jun 2026 at 15:45, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>>\n>> The static allow-list in expand_atom() is hardcoded to only allow\n>> \"objectname\" and \"objectsize\" for remote queries. This works because\n>>  ...\n>>  }\n\nYou just forced readers to skip over 200+ lines of quoted material,\nlooking for something interesting you have said in response to\ncomment on the patch in vain.\n\n>> diff --git a/fetch-object-info.c b/fetch-object-info.c\n>> index 51a898430d..425929a269 100644\n>> --- a/fetch-object-info.c\n>> +++ b/fetch-object-info.c\n>> @@ -39,6 +39,12 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>>         case protocol_v2:\n>>                 if (!server_supports_v2(\"object-info\"))\n>>                         die(_(\"object-info capability is not enabled on the server\"));\n>> +\n>> +               for (int i = args->object_info_options->nr - 1; i >= 0; i--)\n>\n> Isn't args->object_info_options->nr of type size_t? We should probably\n> do something\n> like:\n>\n> for (size_t i = 0; i < args->args->object_info_options->nr; i++)\n>\n> instead.\n\nThis is a valid observation and a careful reading like this is very\nmuch appreciated.  It is unfortunate that it was buried by 200+\nlines of irrelevant material before we find it.\n\nThanks.\n\n\n>> +                       if (!server_supports_feature(\"object-info\",\n>> +                                                    args->object_info_options->items[i].string, 0))\n>> +                               unsorted_string_list_delete_item(args->object_info_options, i, 0);\n>> +\n>>                 send_object_info_request(fd_out, args);\n>>                 break;\n>>         case protocol_v1:\n>>\n>> --\n>> 2.54.0\n>\n> Other than these, the patch series LGTM for now.\n>\n> Thanks,\n> Chandra.\n"},{"id":"545687","messageId":"xmqq1pe62pgo.fsf@gitster.g","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-9-5338b766e658@gmail.com","subject":"Re: [PATCH GSoC RFC v12 09/12] transport: add client support for object-info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-16T20:35:35Z","receivedAt":"2026-06-16T20:35:38Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n[jc: removed recipients from Cc: list whose addresses bounce]\n\n> From: Calvin Wan <calvinwan@google.com>\n>\n> Sometimes, it is beneficial to retrieve information about an object\n> without downloading it entirely. The server-side logic for this\n> functionality was implemented in commit \"a2ba162cda (object-info:\n> ...\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> ...\n> +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n> +\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n> +\t\t      const int stateless_rpc, const int fd_out)\n> +{\n> ...\n> +\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n> +\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n> +\n> +\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n> +\t\tif (0 <= size_index) {\n> +\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n> +\t\t\t\tdie(\"object-info: server does not recognize object %s\",\n> +\t\t\t\t    object_info_values.items[0].string);\n> +\n> +\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))\n\n\nOverly long lines need to be fixed, by using a shorter and crisper\nvariable name in such a short scope, and line wrapping if needed.\n\nMore importantly, on this line (wrapped):\n\n\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string,\n\t\t\t\t       10, object_info_data[i].sizep))\n\nwe notice object_info_data[i] is of type \"struct object_info\", which\nis \n\n    struct object_info {\n            /* Request */\n            enum object_type *typep;\n            size_t *sizep;\n            off_t *disk_sizep;\n\t    ...\n\nbut the last parameter strtoul_ul() takes is unsurprisingly a\npointer to \"unsigned long\", not a pointer to \"size_t\".\n\nWhich will break on 32-bit boxes where size_t is \"unsigned int\"\nthat is 32-bit and different from \"unsigned long\".\n\nPerhaps something along this line?\n\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 425929a269..5210e7d954 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -75,14 +75,17 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \n \t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n \t\tif (0 <= size_index) {\n+\t\t\tunsigned long sz;\n \t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n \t\t\t\tdie(\"object-info: server does not recognize object %s\",\n \t\t\t\t    object_info_values.items[0].string);\n \n-\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))\n+\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string,\n+\t\t\t\t       10, &sz))\n \t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n \t\t\t\t    object_info_values.items[0].string,\n \t\t\t\t    object_info_values.items[1 + size_index].string);\n+\t\t\t*object_info_data[i].sizep = sz;\n \t\t}\n \n \t\tstring_list_clear(&object_info_values, 0);\n"},{"id":"545692","messageId":"xmqqse6m18be.fsf@gitster.g","threadId":"61695","inReplyTo":"xmqq1pe62pgo.fsf@gitster.g","subject":"Re: [PATCH GSoC RFC v12 09/12] transport: add client support for object-info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-16T21:31:17Z","receivedAt":"2026-06-16T21:31:20Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> [jc: removed recipients from Cc: list whose addresses bounce]\n>\n>> From: Calvin Wan <calvinwan@google.com>\n>>\n>> Sometimes, it is beneficial to retrieve information about an object\n>> without downloading it entirely. The server-side logic for this\n>> functionality was implemented in commit \"a2ba162cda (object-info:\n>> ...\n>> diff --git a/fetch-object-info.c b/fetch-object-info.c\n>> ...\n>> +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n>> +\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n>> +\t\t      const int stateless_rpc, const int fd_out)\n>> +{\n>> ...\n>> +\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n>> +\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n>> +\n>> +\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n>> +\t\tif (0 <= size_index) {\n>> +\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n>> +\t\t\t\tdie(\"object-info: server does not recognize object %s\",\n>> +\t\t\t\t    object_info_values.items[0].string);\n>> +\n>> +\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))\n>\n>\n> Overly long lines need to be fixed, by using a shorter and crisper\n> variable name in such a short scope, and line wrapping if needed.\n>\n> More importantly, on this line (wrapped):\n>\n> \t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string,\n> \t\t\t\t       10, object_info_data[i].sizep))\n>\n> we notice object_info_data[i] is of type \"struct object_info\", which\n> is \n>\n>     struct object_info {\n>             /* Request */\n>             enum object_type *typep;\n>             size_t *sizep;\n>             off_t *disk_sizep;\n> \t    ...\n>\n> but the last parameter strtoul_ul() takes is unsurprisingly a\n> pointer to \"unsigned long\", not a pointer to \"size_t\".\n>\n> Which will break on 32-bit boxes where size_t is \"unsigned int\"\n> that is 32-bit and different from \"unsigned long\".\n>\n> Perhaps something along this line?\n\n\nNot quite.  This \"size_t *sizep\" has been very recently introduced\nby Dscho in a topic that is in-flight.\n\nThe ps/cat-file-remote-object-info topic alone does not have this\ntype-mismatch problem.  Below needs to be addressed as an evil merge\nat the integration side, so you have nothing to do.  I'll have to\ntweak the merges.\n\nSorry for a false alarm.\n\n\n\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> index 425929a269..5210e7d954 100644\n> --- a/fetch-object-info.c\n> +++ b/fetch-object-info.c\n> @@ -75,14 +75,17 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>  \n>  \t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n>  \t\tif (0 <= size_index) {\n> +\t\t\tunsigned long sz;\n>  \t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\"))\n>  \t\t\t\tdie(\"object-info: server does not recognize object %s\",\n>  \t\t\t\t    object_info_values.items[0].string);\n>  \n> -\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string, 10, object_info_data[i].sizep))\n> +\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string,\n> +\t\t\t\t       10, &sz))\n>  \t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n>  \t\t\t\t    object_info_values.items[0].string,\n>  \t\t\t\t    object_info_values.items[1 + size_index].string);\n> +\t\t\t*object_info_data[i].sizep = sz;\n>  \t\t}\n>  \n>  \t\tstring_list_clear(&object_info_values, 0);\n"},{"id":"545752","messageId":"CA+J6zkTrBO9paxkMtnR1cDtD=LQT8dzbVNxgzzYNz_bpzrvcwQ@mail.gmail.com","threadId":"61695","inReplyTo":"CAN5EUNQHSd=0z26iG0gk24TEtgg1n8CC+H9bkqRACyErNgLxEA@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v12 12/12] cat-file: make remote-object-info allow-list dynamic","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-17T10:16:20Z","receivedAt":"2026-06-17T10:16:48Z","isPatch":true,"body":"On Tue, 9 Jun 2026 at 23:04, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n> [snip]\n> > > diff --git a/fetch-object-info.c b/fetch-object-info.c\n> > > index 51a898430d..425929a269 100644\n> > > --- a/fetch-object-info.c\n> > > +++ b/fetch-object-info.c\n> > > @@ -39,6 +39,12 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n> > >         case protocol_v2:\n> > >                 if (!server_supports_v2(\"object-info\"))\n> > >                         die(_(\"object-info capability is not enabled on the server\"));\n> > > +\n> > > +               for (int i = args->object_info_options->nr - 1; i >= 0; i--)\n> >\n> > Isn't args->object_info_options->nr of type size_t? We should probably\n> > do something\n> > like:\n> >\n> > for (size_t i = 0; i < args->args->object_info_options->nr; i++)\n> >\n> > instead.\n>\n> Hi!\n>\n> void unsorted_string_list_delete_item(struct string_list *list, int i,\n> int free_util)\n> {\n>         if (list->strdup_strings)\n>                 free(list->items[i].string);\n>         if (free_util)\n>                 free(list->items[i].util);\n>         list->items[i] = list->items[list->nr-1];\n>         list->nr--;\n> }\n>\n>\n> I made it backwards because of \"list->items[i] = list->items[list->nr\n> - 1];\" If we made it from 0..nr and we delete the first element, for\n> the next iteration, the last element is at [0] but we are on [1] and\n> that swapped element never gets evaluated.\n\nMakes sense now.\n\n> About size_t, yes, it is size_t but because we go backwards 0 - 1\n> would fail, also unsorted_string_list_delete_item() signature has \"int\n> i\". The options that can be on that list will be a small number so\n> there should be no problem, should I cast it explicitly?\n\nYes, I think explicit casting with a short comment explaining why it is\nfine to do so will be much better.\n\nThanks,\nChandra.\n"},{"id":"545964","messageId":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260608-ps-eric-work-rebase-v12-0-5338b766e658@gmail.com","subject":"[PATCH GSoC RFC v13 00/12] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:29Z","receivedAt":"2026-06-19T14:56:48Z","isPatch":true,"body":"This path series is a continuation of Eric Ju's (eric.peijian@gmail.com) and\nCalvin Wan's (calvinwan@google.com) patch series [1] and [2] respectively.\n\nSometimes it is beneficial to retrieve information about an object without\nhaving to download it completely. The server logic for retrieving size has\nalready been implemented and merged in \"a2ba162cda (object-info: support for\nretrieving object info, 2021-04-20)\"[3]. This patch series implement the client\noption for it.\n\nEric's series adds the `remote-object-info` command to\n`cat-file --batch-command`. This command allows the client to make an\nobject-info command request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info capability,\n`cat-file --batch-command` will die.\n\nIf a user attempts to use `remote-object-info` with protocol v1,\n`cat-file --batch-command` will die.\n\nCurrently, only the size (%(objectsize)) is supported end to end in this\nimplementation. The type (%(objecttype)) is known by the client's allow-list\nand request path but is not supported on the server side nor the response\nparsing. A follow up series will add full end-to-end support for %(objecttype).\n\nThe default format for remote-object-info is set to %(objectname) %(objectsize).\nOnce %(objecttype) is supported, the default format will be unified accordingly.\n\nIf the batch command format includes unsupported fields such as %(objecttype),\n%(objectsize:disk), or %(deltabase), the command will return empty strings for\neach unsupported field.\n\nThis series completes Eric's work mainly with the refactor of the validation\nof the placeholder with an allow-list that filters what the client asks with\nwhat the server is capable of provide following Jeff King's idea [4].\n\nI have a question for the design:\n\n1. If the format includes unsupported fields such as %(objecttype) or\n   %(deltabase) it currently returns an empty string for each unsupported\n   field, this follows what for-each-ref does with known but inapplicable\n   atoms. However future placeholders that will be implemented: %(rest),\n   %(objectmode) can return empty strings. How should we differentiate\n   \"unsupported\" vs \"no data\".\n   Eric proposed to use a placeholder like \"???\" [5].\n   Should a placeholder be used?\n\n2. _tangent/not related with this series_\n   'a2ba162cda' is designed to only work with full OIDs, which is\n   inconsistent with local `info` that does support short OIDs and in\n   case of being ambiguous returns a list of what possibly the user meant.\n\n   Because V2 protocol is thought to be stateless supporting short OIDs\n   could become more inconsistent with other remote commands that do not\n   support short OIDs. Maybe a --pick-first option? That does accept\n   short oids and picks the first match.\n\n   Alternatively, would sending a list of possible OIDs to the client so\n   it can re-request with the correct one be ok?\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n[4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n[5]: https://lore.kernel.org/git/CAN2LT1D3d=yMYVhBjpj5PvyjfTVjwqcFPNViuCJ=f49YbCZuJg@mail.gmail.com/\n\nChanges since v12:\n\n- Remote-object-info no longer dies when the server doesn't recognize\n  the object, printing \"<oid> missing\" like `info` does.\n- On 12th commit explicitly cast to int and add a comment explaining why\n  the backward iteration of the list.\n- Renamed 3rd commit and in the commit, change the signature of\n  dispatch_calls() as it is only called with size_t instead of ints.\n- Because remote-object-info does not support short oids add a check to\n  improve the error report if the oid passed is valid but not long\n  enough or if it is an invalid oid.\n- Fixed overly long lines.\n- Reworded 4th commit.\n- Avoid unnecessary request to the server when no placeholder is supported.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nCalvin Wan (3):\n      fetch-pack: move fetch initialization\n      serve: advertise object-info feature\n      transport: add client support for object-info\n\nEric Ju (4):\n      git-compat-util: add strtoul_ul() with error handling\n      cat-file: declare loop counter inside for()\n      t1006: split test utility functions into new \"lib-cat-file.sh\"\n      cat-file: add remote-object-info to batch-command\n\nPablo Sabater (5):\n      transport-helper: fix memory leak of helper on disconnect\n      fetch-pack: move function to connect.c\n      connect: refactor packet writing\n      cat-file: validate remote atoms with allow_list\n      cat-file: make remote-object-info allow-list dynamic\n\n Documentation/git-cat-file.adoc        |  25 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 221 ++++++++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    | 106 +++++\n fetch-object-info.h                    |  22 ++\n fetch-pack.c                           |  51 +--\n fetch-pack.h                           |   2 +\n git-compat-util.h                      |  20 +\n meson.build                            |   1 +\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n serve.c                                |   5 +-\n t/lib-cat-file.sh                      |  16 +\n t/meson.build                          |   1 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 699 +++++++++++++++++++++++++++++++++\n transport-helper.c                     |  13 +-\n transport.c                            |  28 +-\n transport.h                            |  11 +\n 21 files changed, 1215 insertions(+), 75 deletions(-)\n---\nbase-commit: 4621f8ce5e9b97aa2e8d0d9ffe9d25df2471074d\nchange-id: 20260608-ps-eric-work-rebase-b73ae84ba671\n\nBest regards,\n--  \nPablo Sabater <pabloosabaterr@gmail.com>\n"},{"id":"545963","messageId":"20260619-ps-eric-work-rebase-v13-1-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 01/12] transport-helper: fix memory leak of helper on disconnect","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:30Z","receivedAt":"2026-06-19T14:56:49Z","isPatch":true,"body":"disconnect_helper() only frees data inside of the if(data->helper)\nblock [1]. When the transport is disconnected without the helper\nbeing fully started, data->name allocated in transport_helper_init()\nis never freed.\n\nMove FREE_AND_NULL(data->name) outside the conditional block so it's\nalways freed on disconnect.\n\n[1]: https://lore.kernel.org/git/05fbadbae2184479c87c37675dde7bd79b3e32ab.1716465556.git.ps@pks.im/\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n transport-helper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 0fa0eb2d72..8a71354d50 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tres = finish_command(data->helper);\n-\t\tFREE_AND_NULL(data->name);\n \t\tFREE_AND_NULL(data->helper);\n \t}\n+\tFREE_AND_NULL(data->name);\n \treturn res;\n }\n \n\n-- \n2.54.0\n"},{"id":"545965","messageId":"20260619-ps-eric-work-rebase-v13-2-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 02/12] git-compat-util: add strtoul_ul() with error handling","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:31Z","receivedAt":"2026-06-19T14:56:50Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nWe already have strtoul_ui() and similar functions that provide proper\nerror handling using strtoul from the standard library. However,\nthere isn't currently a variant that returns an unsigned long.\n\nThis variant is needed in a subsequent commit.\n\nThis variant is needed in a subsequent commit to enable returning an\nunsigned long with proper error handling.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n git-compat-util.h | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 8809776407..4bf569f35c 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n \treturn 0;\n }\n \n+/*\n+ * Convert a string to an unsigned long using the standard library's strtoul,\n+ * with additional error handling to ensure robustness.\n+ */\n+static inline int strtoul_ul(char const *s, int base, unsigned long *result)\n+{\n+\tunsigned long ul;\n+\tchar *p;\n+\n+\terrno = 0;\n+\t/* negative values would be accepted by strtoul */\n+\tif (strchr(s, '-'))\n+\t\treturn -1;\n+\tul = strtoul(s, &p, base);\n+\tif (errno || *p || p == s)\n+\t\treturn -1;\n+\t*result = ul;\n+\treturn 0;\n+}\n+\n static inline int strtol_i(char const *s, int base, int *result)\n {\n \tlong ul;\n\n-- \n2.54.0\n"},{"id":"545966","messageId":"20260619-ps-eric-work-rebase-v13-3-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 03/12] cat-file: declare loop counter inside for()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:32Z","receivedAt":"2026-06-19T14:56:52Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSome code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\nWhile at it, we also change its type from \"int\" to \"size_t\" where the\nlatter makes more sense.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c | 13 ++++---------\n fetch-pack.c       |  3 +--\n 2 files changed, 5 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 446d649904..fab55c11de 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -723,14 +723,12 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n \t\tstruct queued_cmd *cmd,\n-\t\tint nr)\n+\t\tsize_t nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -738,9 +736,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -767,7 +763,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -777,7 +772,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 120e01f3cf..f13951d154 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n\n-- \n2.54.0\n"},{"id":"545967","messageId":"20260619-ps-eric-work-rebase-v13-4-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 04/12] t1006: split test utility functions into new \"lib-cat-file.sh\"","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:33Z","receivedAt":"2026-06-19T14:56:53Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nThis refactor extracts utility functions from the cat-file's test\nscript \"t1006-cat-file.sh\" into a new \"lib-cat-file.sh\" dedicated\nlibrary file. The goal is to improve code reuse and readability,\nenabling future tests to leverage these utilities without duplicating\ncode.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 13 +------------\n 2 files changed, 17 insertions(+), 12 deletions(-)\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..44af232d74\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 8e2c52652c..8360f3bbd9 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-loose.sh\"\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     object_name=\"$2\"\n\n-- \n2.54.0\n"},{"id":"545968","messageId":"20260619-ps-eric-work-rebase-v13-5-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 05/12] fetch-pack: move function to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:34Z","receivedAt":"2026-06-19T14:56:56Z","isPatch":true,"body":"write_fetch_command_and_capabilities will be refactored in a subsequent\ncommit where it will become a more general-purpose function, making it\nmore accessible to additional commands in the future.\n\nTo move `write_fetch_command_and_capabilities()` to `connect.c`, we need\nto adjust how `advertise_sid` is managed. Previously in `fetch_pack.c`,\n`advertise_sid` was a static variable, modified using\n`repo_config_get_bool()`.\n\nIn `connect.c`, we now initialize `advertise_sid` at the begining by\ndirectly using `repo_config_get_bool()`. This change is safe because:\n\nIn the original `fetch-pack.c` code, there are only two places that write\n`advertise_sid`:\n\n1. In function `do_fetch_pack()`:\n        if (!sever_supports(\"session_id\"))\n               advertise_sid = 0;\n2. In function `fetch_pack_config()`:\n        repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, since `do_fetch_pack()` is only relevant for protocol v1, this\nassignment can be ignored, as `write_fetch_command_and_capabilities()`\nis only used in v2.\n\nAbout 2, `repo_config_get_bool()` is from `config.h` and it's an out-of-box\ndependency of `connect.c`, so we can reuse it directly.\n\nMove `write_fetch_command_and_capabilities()` to `connect.c`\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 31 -------------------------------\n 3 files changed, 38 insertions(+), 31 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 47e39d2a73..1dced8e632 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,6 +700,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(\"fetch\");\n+\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t    the_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t    the_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n static const char *url_scheme_name(enum url_scheme scheme)\n {\n \tswitch (scheme) {\ndiff --git a/connect.h b/connect.h\nindex aa482a37fb..c4f6ea4b0a 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+struct string_list;\n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f13951d154..4a8a70b5f3 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1376,37 +1376,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n\n-- \n2.54.0\n"},{"id":"545969","messageId":"20260619-ps-eric-work-rebase-v13-6-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 06/12] connect: refactor packet writing","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:35Z","receivedAt":"2026-06-19T14:56:58Z","isPatch":true,"body":"Refactor `write_fetch_command_and_capabilities()`, enabling it to serve\nboth fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162\n(object-info: support for retrieving object info, 2021-04-20).\n\nRefactor the function signature to accept a command instead of the\nhardcoded \"fetch\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 10 +++++-----\n connect.h    |  8 ++++++--\n fetch-pack.c |  4 ++--\n 3 files changed, 13 insertions(+), 9 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 1dced8e632..78c69d4485 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,16 +700,16 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options)\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \tint advertise_sid;\n \n \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n@@ -727,7 +727,7 @@ void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n \t\t\t    the_hash_algo->name, hash_name);\n \t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n \t\tdie(_(\"the server does not support algorithm '%s'\"),\n \t\t    the_hash_algo->name);\n \t}\ndiff --git a/connect.h b/connect.h\nindex c4f6ea4b0a..8f4c523892 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,8 +34,12 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+/*\n+ * Writes a command along with the requested server capabilities/features into a\n+ * request buffer.\n+ */\n struct string_list;\n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options);\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 4a8a70b5f3..3d32114907 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1387,7 +1387,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2255,7 +2255,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n\n-- \n2.54.0\n"},{"id":"545970","messageId":"20260619-ps-eric-work-rebase-v13-7-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 07/12] fetch-pack: move fetch initialization","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:36Z","receivedAt":"2026-06-19T14:57:01Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3d32114907..cdebd3476f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1736,18 +1736,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n\n-- \n2.54.0\n"},{"id":"545971","messageId":"20260619-ps-eric-work-rebase-v13-8-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 08/12] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:37Z","receivedAt":"2026-06-19T14:57:02Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n serve.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex 49a6e39b1d..2b07d922b3 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\t/* Currently only size is supported */\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n\n-- \n2.54.0\n"},{"id":"545972","messageId":"20260619-ps-eric-work-rebase-v13-9-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 09/12] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:38Z","receivedAt":"2026-06-19T14:57:04Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nThis commit introduces client functions to interact with the server.\n\nCurrently, the client supports requesting a list of object IDs with\nthe 'size' feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client will return an error and exit.\n\nNotice that the entire request is written into req_buf before being\nsent to the remote. This approach follows the pattern used in the\n`send_fetch_request()` logic within fetch-pack.c.\nStreaming the request is not addressed in this patch.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Makefile            |  1 +\n fetch-object-info.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h | 22 +++++++++++++\n fetch-pack.c        |  3 ++\n fetch-pack.h        |  2 ++\n meson.build         |  1 +\n transport-helper.c  | 11 +++++--\n transport.c         | 28 ++++++++++++++++-\n transport.h         | 11 +++++++\n 9 files changed, 166 insertions(+), 3 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 1cec251f43..ec4df39a6b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1159,6 +1159,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..ae035c9598\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,90 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"odb.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends git-cat-file object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"unsupported protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader,\n+\t\t\t\t\t\t  \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\tsize_index = i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\")) {\n+\t\t\t\tFREE_AND_NULL(object_info_data[i].sizep);\n+\t\t\t\tstring_list_clear(&object_info_values, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (strtoul_ul(object_info_values.items[1 + size_index].string,\n+\t\t\t\t       10, object_info_data[i].sizep))\n+\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t\t    object_info_values.items[0].string,\n+\t\t\t\t    object_info_values.items[1 + size_index].string);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..d35284bd6b\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"odb.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex cdebd3476f..a86c93fc52 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1742,6 +1742,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 6d0dec7f41..5a428f11ed 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -43,6 +44,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\ndiff --git a/meson.build b/meson.build\nindex 3247697f74..145c6882eb 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -347,6 +347,7 @@ libgit_sources = [\n   'exec-cmd.c',\n   'fetch-negotiator.c',\n   'fetch-pack.c',\n+  'fetch-object-info.c',\n   'fmt-merge-msg.c',\n   'fsck.c',\n   'fsmonitor.c',\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 8a71354d50..fdb0590417 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -727,8 +727,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file\n+\t * remote-object-info require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -744,6 +744,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 0f5ec30247..7d3246e12b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -467,8 +468,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.negotiation_restrict_tips = data->options.negotiation_restrict_tips;\n \targs.negotiation_include_tips = data->options.negotiation_include_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options->object_info\n+\t    && transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct packet_reader reader;\n+\t\tstruct object_info_args obj_info_args = { 0 };\n+\n+\t\tobj_info_args.server_options = transport->server_options;\n+\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n+\t\tstring_list_sort(obj_info_args.object_info_options);\n+\n+\t\tconnect_setup(transport, 0);\n+\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\t\tdata->version = discover_version(&reader);\n+\t\ttransport->hash_algo = reader.hash_algo;\n+\n+\t\tret = fetch_object_info(data->version, &obj_info_args, &reader,\n+\t\t\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n+\t\t\t\t\tdata->fd[1]);\n+\t\tgoto cleanup;\n \n-\tif (!data->finished_handshake) {\n+\t} else if (!data->finished_handshake) {\n \t\tint i;\n \t\tint must_list_refs = 0;\n \t\tfor (i = 0; i < nr_heads; i++) {\ndiff --git a/transport.h b/transport.h\nindex 7e5867cffa..bd60b10af4 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -6,6 +6,7 @@\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n #include \"connect.h\"\n+#include \"odb.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -31,6 +32,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to retrieve only object-info.\n+\t * If object-info is not supported, the operation will error and exit.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -55,6 +62,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n\n-- \n2.54.0\n"},{"id":"545973","messageId":"20260619-ps-eric-work-rebase-v13-10-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 10/12] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:39Z","receivedAt":"2026-06-19T14:57:06Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSince the `info` command in `cat-file --batch-command` prints object\ninfo for a given object, it is natural to add another command in\n`cat-file --batch-command` to print object info for a given object\nfrom a remote.\n\nAdd `remote-object-info` to `cat-file --batch-command`.\n\nWhile `info` takes object ids one at a time, this creates\noverhead when making requests to a server. So `remote-object-info`\ninstead can take multiple object ids at once.\n\nThe `cat-file --batch-command` command is generally implemented in\nthe following manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for `remote-object-info` to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  24 +-\n builtin/cat-file.c                     | 144 ++++++-\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n t/meson.build                          |   1 +\n t/t1017-cat-file-remote-object-info.sh | 680 +++++++++++++++++++++++++++++++++\n transport.c                            |   4 +-\n 7 files changed, 859 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex 86b9181599..aba20eb770 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -169,6 +169,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -312,7 +319,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectmode`::\n \tIf the specified object has mode information (such as a tree or\n@@ -325,13 +333,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -341,7 +350,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -438,6 +450,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will raise\n+an error and exit when they appear in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex fab55c11de..402b2c31a1 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -29,6 +29,22 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/*\n+ * Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8 * 1024)\n+\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE \\\n+\t(MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -638,6 +654,81 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt,\n+\t\t\t   int argc,\n+\t\t\t   const char **argv,\n+\t\t\t   struct object_info **remote_object_info,\n+\t\t\t   struct oid_array *object_info_oids)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstatic struct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT.\n+\t */\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid)) {\n+\t\t\tsize_t len = strlen(argv[i]);\n+\n+\t\t\tif (len < the_hash_algo->hexsz && len >= 4) {\n+\t\t\t\tsize_t j;\n+\t\t\t\tfor (j = 0; j < len; j++)\n+\t\t\t\t\tif (!isxdigit(argv[i][j]))\n+\t\t\t\t\t\tbreak;\n+\t\t\t\tif (j == len)\n+\t\t\t\t\tdie(_(\"remote-object-info does not support \"\n+\t\t\t\t\t      \"short oids, %d characters required\"),\n+\t\t\t\t\t    (int)the_hash_algo->hexsz);\n+\t\t\t}\n+\t\t\tdie(_(\"not a valid object name '%s'\"), argv[i]);\n+\t\t}\n+\t\toid_array_append(object_info_oids, &oid);\n+\t}\n+\n+\tif (!object_info_oids->nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\n+\tif (!gtransport->smart_options) {\n+\t\tretval = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n+\tgtransport->smart_options->object_info = 1;\n+\tgtransport->smart_options->object_info_oids = object_info_oids;\n+\n+\t/* 'objectsize' is the only option currently supported */\n+\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\tstring_list_append(&object_info_options, \"size\");\n+\n+\tif (object_info_options.nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t}\n+cleanup:\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -719,6 +810,56 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\tstatic struct object_info *remote_object_info;\n+\tstatic struct oid_array object_info_oids = OID_ARRAY_INIT;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command too long\"));\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"split remote-object-info command\"));\n+\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n+\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n+\t\t    MAX_ALLOWED_OBJ_LIMIT);\n+\n+\tif (get_remote_info(opt, count, argv, &remote_object_info,\n+\t\t\t    &object_info_oids))\n+\t\tgoto cleanup;\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t} else {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -750,8 +891,9 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1 },\n \t{ \"info\", parse_cmd_info, 1 },\n-\t{ \"flush\", NULL, 0 },\n \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n+\t{ \"flush\", NULL, 0 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex 9afa842da2..ef31a47939 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1694,3 +1694,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)\n \n \treturn &transaction->base;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+}\ndiff --git a/odb.h b/odb.h\nindex 0030467a52..168ea12da7 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -573,4 +573,7 @@ void parse_alternates(const char *string,\n \t\t      const char *relative_base,\n \t\t      struct strvec *out);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* ODB_H */\ndiff --git a/t/meson.build b/t/meson.build\nindex c5832fee05..33327dd1df 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -170,6 +170,7 @@ integration_tests = [\n   't1014-read-tree-confusing.sh',\n   't1015-read-index-unmerged.sh',\n   't1016-compatObjectFormat.sh',\n+  't1017-cat-file-remote-object-info.sh',\n   't1020-subdirectory.sh',\n   't1022-read-tree-partial-clone.sh',\n   't1050-large.sh',\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..b744e81701\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,680 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n+\n+unstored_content=\"Hello Git\"\n+unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not support short oids' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_short_oid\n+\t\tEOF\n+\t\ttest_grep \"does not support short oids\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not die on missing oid like info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tgit cat-file --batch-command >local <<-EOF &&\n+\t\tinfo $unstored_oid\n+\t\tEOF\n+\t\tgit cat-file --batch-command >remote <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $unstored_oid\n+\t\tEOF\n+\t\ttest_cmp local remote\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\ndiff --git a/transport.c b/transport.c\nindex 7d3246e12b..81faf8e748 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -470,8 +470,8 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n \targs.object_info = transport->smart_options->object_info;\n \n-\tif (transport->smart_options->object_info\n-\t    && transport->smart_options->object_info_oids->nr > 0) {\n+\tif (transport->smart_options->object_info &&\n+\t    transport->smart_options->object_info_oids->nr > 0) {\n \t\tstruct packet_reader reader;\n \t\tstruct object_info_args obj_info_args = { 0 };\n \n\n-- \n2.54.0\n"},{"id":"545974","messageId":"20260619-ps-eric-work-rebase-v13-11-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 11/12] cat-file: validate remote atoms with allow_list","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:40Z","receivedAt":"2026-06-19T14:57:08Z","isPatch":true,"body":"strstr() is not enough to validate the format placeholders in\nremote-object-info causing two errors:\n\n- Atoms recognized by expand_atom() but the remote doesn't returns 1, but\n  data->type contains garbage causing segfault.\n- expand_atom() returns 0 for unknown atoms, calling\n  strbuf_expand_bad_format() which ends in die() blocking local queries\n  if the same format is shared.\n\nAdd an allow_list with the supported atoms at the top of expand_atom().\nIn remote mode, unsupported atoms return 1 leaving the sb empty,\nhonoring how for-each-ref handles known but inapplicable atoms.\n\nAs extra safety, initialize data->type to OBJ_BAD and add a NULL check\nfor type_name() so uninitialized data doesn't cause segfault.\n\nUpdate tests that expect previous die() behaviour to expect an empty\nstring and add an explicit test for empty string return on unknown\nplaceholder.\n\nUpdate caveat behaviour documentation.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  5 +++--\n builtin/cat-file.c                     | 41 +++++++++++++++++++++++++++-------\n t/t1017-cat-file-remote-object-info.sh | 27 ++++++++++++++++++----\n 3 files changed, 59 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex aba20eb770..3b7a85b383 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -451,8 +451,9 @@ CAVEATS\n -------\n \n Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n-currently not supported by the `remote-object-info` command, we will raise\n-an error and exit when they appear in the format string.\n+currently not supported by the `remote-object-info` command, they will\n+return an empty string for remote queries, matching how `for-each-ref`\n+behaves for known but inapplicable placeholders.\n \n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 402b2c31a1..7ad6165032 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -336,8 +336,18 @@ struct expand_data {\n \t * optimized out.\n \t */\n \tunsigned skip_object_info : 1;\n+\n+\t/*\n+\t * Flags about when an object info is being fetched from remote.\n+\t */\n+\tunsigned is_remote:1;\n+};\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n+\n+static const char *remote_object_info_atoms[] = {\n+\t\"objectname\",\n+\t\"objectsize\",\n };\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -348,14 +358,31 @@ static int is_atom(const char *atom, const char *s, int slen)\n static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n+\tif (data->is_remote) {\n+\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n+\t\tfor (i = 0; i < allowed_nr; i++)\n+\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * On remote, skip unsupported atoms returning an empty sb,\n+\t\t * honoring how for-each-ref handles known but inapplicable\n+\t\t * atoms (e.g. %(tagger)).\n+\t\t */\n+\t\tif (i == allowed_nr)\n+\t\t\treturn 1;\n+\t}\n+\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n \t\t\tstrbuf_add_oid_hex(sb, &data->oid);\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n-\t\tif (data->mark_query)\n+\t\tif (data->mark_query) {\n \t\t\tdata->info.typep = &data->type;\n-\t\telse\n-\t\t\tstrbuf_addstr(sb, type_name(data->type));\n+\t\t} else {\n+\t\t\tconst char *t = type_name(data->type);\n+\t\t\tstrbuf_addstr(sb, t ? t : \"\");\n+\t\t}\n \t} else if (is_atom(\"objectsize\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.sizep = &data->size;\n@@ -712,10 +739,6 @@ static int get_remote_info(struct batch_options *opt,\n \tgtransport->smart_options->object_info = 1;\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\t/* 'objectsize' is the only option currently supported */\n-\tif (!strstr(opt->format, \"%(objectsize)\"))\n-\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n-\n \tstring_list_append(&object_info_options, \"size\");\n \n \tif (object_info_options.nr > 0) {\n@@ -845,7 +868,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n \t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tdata->is_remote = 1;\n \t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t\tdata->is_remote = 0;\n \t\t} else {\n \t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n \t\t}\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex b744e81701..9d8f114b72 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -236,6 +236,21 @@ test_expect_success 'remote-object-info does not die on missing oid like info' '\n \t)\n '\n \n+# This tests depends on %(objecttype) not being supported yet, once supported\n+# it needs to be updated.\n+test_expect_success 'unsupported placeholder on remote returns empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"\" >expect &&\n+\t\tgit cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n # Test --batch-command remote-object-info with 'git://' and\n # transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n@@ -575,10 +590,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (obje\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\techo \"$hello_oid \" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize:disk)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n@@ -587,10 +604,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (delt\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(deltabase)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n\n-- \n2.54.0\n"},{"id":"545975","messageId":"20260619-ps-eric-work-rebase-v13-12-3d4c7315d2f8@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC RFC v13 12/12] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T14:56:41Z","receivedAt":"2026-06-19T14:57:09Z","isPatch":true,"body":"The static allow-list in expand_atom() is hardcoded to only allow\n\"objectname\" and \"objectsize\" for remote queries. This works because\nup to this point all servers will either support object-info with name\nand size or they do not support them at all, but we cannot expect that\nin a future different servers with different git versions to have the\nsame object-info capabilities. Therefore, the allow_list needs to be\ndynamic depending on what does the server advertise.\n\nThe client will now:\n\n1. Request the protocol option that the placeholder refers to (i.e.\n   \"size\" when \"%(objectsize)\").\n\n2. Filters the request in fetch_object_info() dropping any option that\n   the server does not advertise.\n\n3. After the fetching, the options that haven't been dropped are the ones\n   fetched and supported by the server, these supported options are\n   mapped and remote_allowed_atoms is populated with the placeholders.\n\n4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n   the static allow_list had.\n\nMove object_info_options out of get_remote_info so the caller which has\ndata can select what options will be requested instead of requesting\nalways size.\nMove batch_object_write() out so there will always be an output even if\nall the placeholders are not supported by the server (returns an empty\nline).\n\nInclude \"type\" in the object_info_options so once the server supports\nit, the clients know already how to request it.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c  | 97 +++++++++++++++++++++++++++++++++++------------------\n fetch-object-info.c | 16 +++++++++\n 2 files changed, 80 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 7ad6165032..4c7b2781da 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -341,13 +341,11 @@ struct expand_data {\n \t * Flags about when an object info is being fetched from remote.\n \t */\n \tunsigned is_remote:1;\n-};\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n \n-static const char *remote_object_info_atoms[] = {\n-\t\"objectname\",\n-\t\"objectsize\",\n+\tstruct string_list remote_allowed_atoms;\n };\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -359,17 +357,11 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n \tif (data->is_remote) {\n-\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n-\t\tfor (i = 0; i < allowed_nr; i++)\n-\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\tsize_t i;\n+\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n \t\t\t\tbreak;\n-\n-\t\t/*\n-\t\t * On remote, skip unsupported atoms returning an empty sb,\n-\t\t * honoring how for-each-ref handles known but inapplicable\n-\t\t * atoms (e.g. %(tagger)).\n-\t\t */\n-\t\tif (i == allowed_nr)\n+\t\tif (i == data->remote_allowed_atoms.nr)\n \t\t\treturn 1;\n \t}\n \n@@ -685,12 +677,12 @@ static int get_remote_info(struct batch_options *opt,\n \t\t\t   int argc,\n \t\t\t   const char **argv,\n \t\t\t   struct object_info **remote_object_info,\n-\t\t\t   struct oid_array *object_info_oids)\n+\t\t\t   struct oid_array *object_info_oids,\n+\t\t\t   struct string_list *object_info_options)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n \tstruct object_id oid;\n-\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tstatic struct transport *gtransport;\n \n \t/*\n@@ -739,15 +731,12 @@ static int get_remote_info(struct batch_options *opt,\n \tgtransport->smart_options->object_info = 1;\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\tstring_list_append(&object_info_options, \"size\");\n-\n-\tif (object_info_options.nr > 0) {\n-\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tif (object_info_options->nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = object_info_options;\n \t\tgtransport->smart_options->object_info_data = *remote_object_info;\n \t\tretval = transport_fetch_refs(gtransport, NULL);\n \t}\n cleanup:\n-\tstring_list_clear(&object_info_options, 0);\n \ttransport_disconnect(gtransport);\n \treturn retval;\n }\n@@ -833,6 +822,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+struct protocol_placeholder_entry {\n+\tconst char *option;\n+\tconst char *atom;\n+};\n+\n+static const struct protocol_placeholder_entry remote_atom_map[] = {\n+\t{\"size\", \"objectsize\"},\n+\t{\"type\", \"objecttype\"},\n+\t/*\n+\t * Add new protocol options here. Even if the server doesn't support\n+\t * them the allow_list will drop them if the server doesn't advertise\n+\t * them.\n+\t */\n+};\n+\n static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n@@ -842,6 +846,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstatic struct object_info *remote_object_info;\n \tstatic struct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \n \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n \t\tdie(_(\"remote-object-info command too long\"));\n@@ -854,32 +859,57 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n+\tif (data->info.sizep)\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\tif (data->info.typep)\n+\t\tstring_list_append(&object_info_options, \"type\");\n+\n \tif (get_remote_info(opt, count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids))\n+\t\t\t    &object_info_oids, &object_info_options))\n \t\tgoto cleanup;\n \n+\tstring_list_clear(&data->remote_allowed_atoms, 0);\n+\tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n+\tfor (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n+\t\tif (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n+\t\t\tstring_list_append(&data->remote_allowed_atoms,\n+\t\t\t\t\t   remote_atom_map[i].atom);\n+\n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tint found = 0;\n \t\tdata->oid = object_info_oids.oid[i];\n+\t\t/*\n+\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t * information from server without downloading them.\n+\t\t */\n \t\tif (remote_object_info[i].sizep) {\n-\t\t\t/*\n-\t\t\t * When reaching here, it means remote-object-info can retrieve\n-\t\t\t * information from server without downloading them.\n-\t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n-\t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tdata->is_remote = 1;\n-\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n-\t\t\tdata->is_remote = 0;\n-\t\t} else {\n-\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t\tfound = 1;\n \t\t}\n+\n+\t\tif (remote_object_info[i].typep) {\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\t\t\tfound = 1;\n+\t\t}\n+\n+\t\tif (!found && object_info_options.nr > 0) {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid),\n+\t\t\t\t\t     &data->oid, \"missing\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tdata->is_remote = 1;\n+\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n \n cleanup:\n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n+\tstring_list_clear(&object_info_options, 0);\n \tfree(line_to_split);\n \tfree(argv);\n \tfree(remote_object_info);\n@@ -1194,6 +1224,7 @@ static int batch_objects(struct batch_options *opt)\n  cleanup:\n \tstrbuf_release(&input);\n \tstrbuf_release(&output);\n+\tstring_list_clear(&data.remote_allowed_atoms, 0);\n \tcfg->warn_on_object_refname_ambiguity = save_warning;\n \treturn retval;\n }\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex ae035c9598..de927ecd48 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -39,6 +39,22 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \tcase protocol_v2:\n \t\tif (!server_supports_v2(\"object-info\"))\n \t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\t/*\n+\t\t * When removing an element from the list it gets swapped by the\n+\t\t * last element, iterate backwards to prevent elements skipping\n+\t\t * evaluation.\n+\t\t */\n+\t\tfor (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)\n+\t\t\tif (!server_supports_feature(\"object-info\",\n+\t\t\t\t\t\t     args->object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(args->object_info_options, i, 0);\n+\t\t/*\n+\t\t * If no options are left after the filtering, avoid unnecessary\n+\t\t * request to the server.\n+\t\t */\n+\t\tif (!args->object_info_options->nr)\n+\t\t\treturn 0;\n+\n \t\tsend_object_info_request(fd_out, args);\n \t\tbreak;\n \tcase protocol_v1:\n\n-- \n2.54.0\n"},{"id":"545976","messageId":"CAN5EUNSNFbcQRG5UbQv_jx28iwjoV3R43U9Rt8wW2Us7z-5UBg@mail.gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-4-3d4c7315d2f8@gmail.com","subject":"Re: [PATCH GSoC RFC v13 04/12] t1006: split test utility functions into new \"lib-cat-file.sh\"","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T15:02:17Z","receivedAt":"2026-06-19T15:02:29Z","isPatch":true,"body":"El vie, 19 jun 2026 a las 16:56, Pablo Sabater\n(<pabloosabaterr@gmail.com>) escribió:\n>\n> From: Eric Ju <eric.peijian@gmail.com>\n>\n> This refactor extracts utility functions from the cat-file's test\n> script \"t1006-cat-file.sh\" into a new \"lib-cat-file.sh\" dedicated\n> library file. The goal is to improve code reuse and readability,\n> enabling future tests to leverage these utilities without duplicating\n> code.\n>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n\nI forgot to mention that it is needed in a subsequent commit, sorry.\nIt will be in the next version.\n"},{"id":"545977","messageId":"CAN5EUNQy_sZx5-iVjr7YB_4kBPnEcDyVr_o_OhVD1Kff_Gjurw@mail.gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-2-3d4c7315d2f8@gmail.com","subject":"Re: [PATCH GSoC RFC v13 02/12] git-compat-util: add strtoul_ul() with error handling","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-19T15:06:07Z","receivedAt":"2026-06-19T15:06:20Z","isPatch":true,"body":"El vie, 19 jun 2026 a las 16:56, Pablo Sabater\n(<pabloosabaterr@gmail.com>) escribió:\n>\n> From: Eric Ju <eric.peijian@gmail.com>\n>\n> We already have strtoul_ui() and similar functions that provide proper\n> error handling using strtoul from the standard library. However,\n> there isn't currently a variant that returns an unsigned long.\n>\n> This variant is needed in a subsequent commit.\n>\n> This variant is needed in a subsequent commit to enable returning an\n> unsigned long with proper error handling.\n>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n\nI should have removed the second paragraph which is duplicated, I\nquickly fixed the last paragraph but didn't realize it contained the\nsame as above, sorry.\n"},{"id":"546051","messageId":"CA+J6zkRam3hPutyFnQ+RVrPczT+O6cM+e-aZL0m0t3a5ABo8VQ@mail.gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"Re: [PATCH GSoC RFC v13 00/12] cat-file: add remote-object-info to batch-command","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-21T05:25:03Z","receivedAt":"2026-06-21T05:25:32Z","isPatch":true,"body":"On Fri, 19 Jun 2026 at 20:26, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> This path series is a continuation of Eric Ju's (eric.peijian@gmail.com) and\n\ns/path/patch\n\n> Calvin Wan's (calvinwan@google.com) patch series [1] and [2] respectively.\n>\n> Sometimes it is beneficial to retrieve information about an object without\n> having to download it completely. The server logic for retrieving size has\n> already been implemented and merged in \"a2ba162cda (object-info: support for\n> retrieving object info, 2021-04-20)\"[3]. This patch series implement the client\n> option for it.\n>\n> Eric's series adds the `remote-object-info` command to\n> `cat-file --batch-command`. This command allows the client to make an\n> object-info command request to a server that supports protocol v2.\n>\n> If the server uses protocol v2 but does not support the object-info capability,\n> `cat-file --batch-command` will die.\n>\n> If a user attempts to use `remote-object-info` with protocol v1,\n> `cat-file --batch-command` will die.\n>\n> Currently, only the size (%(objectsize)) is supported end to end in this\n> implementation. The type (%(objecttype)) is known by the client's allow-list\n> and request path but is not supported on the server side nor the response\n> parsing. A follow up series will add full end-to-end support for %(objecttype).\n>\n> The default format for remote-object-info is set to %(objectname) %(objectsize).\n> Once %(objecttype) is supported, the default format will be unified accordingly.\n>\n> If the batch command format includes unsupported fields such as %(objecttype),\n> %(objectsize:disk), or %(deltabase), the command will return empty strings for\n> each unsupported field.\n>\n> This series completes Eric's work mainly with the refactor of the validation\n> of the placeholder with an allow-list that filters what the client asks with\n> what the server is capable of provide following Jeff King's idea [4].\n>\n> I have a question for the design:\n>\n> 1. If the format includes unsupported fields such as %(objecttype) or\n>    %(deltabase) it currently returns an empty string for each unsupported\n>    field, this follows what for-each-ref does with known but inapplicable\n>    atoms. However future placeholders that will be implemented: %(rest),\n>    %(objectmode) can return empty strings. How should we differentiate\n>    \"unsupported\" vs \"no data\".\n>    Eric proposed to use a placeholder like \"???\" [5].\n>    Should a placeholder be used?\n\nMaybe it's best to fail cleanly if the user requests an unsupported atom?\nI don't really like the placeholder idea though. If a placeholder like \"???\"\nis introduced, any script/test parsing the output must add explicit logic\nto check for literal question marks, and that sounds flaky. Not to\nmention some atom's response may legitimately contain \"???\".\n\n> 2. _tangent/not related with this series_\n>    'a2ba162cda' is designed to only work with full OIDs, which is\n>    inconsistent with local `info` that does support short OIDs and in\n>    case of being ambiguous returns a list of what possibly the user meant.\n>\n>    Because V2 protocol is thought to be stateless supporting short OIDs\n>    could become more inconsistent with other remote commands that do not\n>    support short OIDs. Maybe a --pick-first option? That does accept\n>    short oids and picks the first match.\n\nWe might return the wrong object's info if we do this. With the server\ngiving us no information to verify whether the returned value _really_\ncorresponds to our intended object, I'd say this isn't the right choice.\n\n>    Alternatively, would sending a list of possible OIDs to the client so\n>    it can re-request with the correct one be ok?\n\nAs far as I know, disambiguation like this is treated purely as a local\nUI convenience in Git, never a network-level operation.\n\n`fetch` already requires users to input exact, full OIDs for their `want`\nlines (obtained via a prior `ls-remote` or ref advertisement), and dies\nif one isn't provided. Thus, I think erroring out is fine here.\n"},{"id":"546052","messageId":"CA+J6zkQEqTeNWkHJWDD6MmK4hesKofBVobDt9OcQ-FSVLC28pw@mail.gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-5-3d4c7315d2f8@gmail.com","subject":"Re: [PATCH GSoC RFC v13 05/12] fetch-pack: move function to connect.c","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-21T05:37:47Z","receivedAt":"2026-06-21T05:38:15Z","isPatch":true,"body":"On Fri, 19 Jun 2026 at 20:26, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> write_fetch_command_and_capabilities will be refactored in a subsequent\n\nNit: the rest of this patch's body referes to this function as:\n`write_fetch_command_and_capabilities()`\n\nLet's use that here as well.\n\n> commit where it will become a more general-purpose function, making it\n> more accessible to additional commands in the future.\n>\n> To move `write_fetch_command_and_capabilities()` to `connect.c`, we need\n> to adjust how `advertise_sid` is managed. Previously in `fetch_pack.c`,\n> `advertise_sid` was a static variable, modified using\n> `repo_config_get_bool()`.\n>\n> In `connect.c`, we now initialize `advertise_sid` at the begining by\n> directly using `repo_config_get_bool()`. This change is safe because:\n>\n> In the original `fetch-pack.c` code, there are only two places that write\n> `advertise_sid`:\n>\n> 1. In function `do_fetch_pack()`:\n>         if (!sever_supports(\"session_id\"))\n\ns/sever/server\n\n>                advertise_sid = 0;\n> 2. In function `fetch_pack_config()`:\n>         repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n>\n> About 1, since `do_fetch_pack()` is only relevant for protocol v1, this\n> assignment can be ignored, as `write_fetch_command_and_capabilities()`\n> is only used in v2.\n>\n> About 2, `repo_config_get_bool()` is from `config.h` and it's an out-of-box\n> dependency of `connect.c`, so we can reuse it directly.\n>\n> Move `write_fetch_command_and_capabilities()` to `connect.c`\n\nNit: this is a better patch header than \"move function to connect.c\",\nsince it better describes the exact change we intend to make.\n\nLet's use it instead.\n"},{"id":"546062","messageId":"CA+J6zkTjgHAWtJwxY8jo0i9zDtxwj9uUsKAtLS3z1=WxZfr8Zw@mail.gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-10-3d4c7315d2f8@gmail.com","subject":"Re: [PATCH GSoC RFC v13 10/12] cat-file: add remote-object-info to batch-command","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-21T06:01:16Z","receivedAt":"2026-06-21T06:01:45Z","isPatch":true,"body":"[snip]\n> +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> +                                        const char *line, struct strbuf *output,\n> +                                        struct expand_data *data)\n> +{\n> +       int count;\n> +       const char **argv;\n> +       char *line_to_split;\n> +       static struct object_info *remote_object_info;\n> +       static struct oid_array object_info_oids = OID_ARRAY_INIT;\n\nI don't get the point of remote_object_info and object_info_oids\nbeing static here? These variables are allocated, utilized, and\ncompletely freed/disconnected within a single command cycle.\n\nMaking them static gives me the false impression that state\nneeds to persist between calls.\n\n> +       if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n> +               die(_(\"remote-object-info command too long\"));\n> +\n> +       line_to_split = xstrdup(line);\n> +       count = split_cmdline(line_to_split, &argv);\n> +       if (count < 0)\n> +               die(_(\"split remote-object-info command\"));\n> +       if (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n> +               die(_(\"remote-object-info supports at most %d objects\"),\n> +                   MAX_ALLOWED_OBJ_LIMIT);\n> +\n> +       if (get_remote_info(opt, count, argv, &remote_object_info,\n> +                           &object_info_oids))\n> +               goto cleanup;\n> +\n> +       data->skip_object_info = 1;\n> +       for (size_t i = 0; i < object_info_oids.nr; i++) {\n> +               data->oid = object_info_oids.oid[i];\n> +               if (remote_object_info[i].sizep) {\n> +                       /*\n> +                        * When reaching here, it means remote-object-info can retrieve\n> +                        * information from server without downloading them.\n> +                        */\n> +                       data->size = *remote_object_info[i].sizep;\n> +                       opt->batch_mode = BATCH_MODE_INFO;\n> +                       batch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n> +               } else {\n> +                       report_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n> +               }\n> +       }\n> +       data->skip_object_info = 0;\n> +\n> +cleanup:\n> +       for (size_t i = 0; i < object_info_oids.nr; i++)\n> +               free_object_info_contents(&remote_object_info[i]);\n> +       free(line_to_split);\n> +       free(argv);\n> +       free(remote_object_info);\n> +}\n> +\n[snip]\n"},{"id":"546063","messageId":"CA+J6zkRoS5uZFkW1jJv1JO7jPMPO-ZANOYerbUxn4WPaApPV6g@mail.gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-12-3d4c7315d2f8@gmail.com","subject":"Re: [PATCH GSoC RFC v13 12/12] cat-file: make remote-object-info allow-list dynamic","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-21T06:19:45Z","receivedAt":"2026-06-21T06:20:15Z","isPatch":true,"body":"On Fri, 19 Jun 2026 at 20:27, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> The static allow-list in expand_atom() is hardcoded to only allow\n> \"objectname\" and \"objectsize\" for remote queries. This works because\n> up to this point all servers will either support object-info with name\n> and size or they do not support them at all, but we cannot expect that\n> in a future different servers with different git versions to have the\n> same object-info capabilities. Therefore, the allow_list needs to be\n> dynamic depending on what does the server advertise.\n\nNit: \"...depending on what does the server advertise.\" ->\n\"...depending on what the server advertises.\"\n"},{"id":"546064","messageId":"CA+J6zkRojd3KVfna05SuRwwHruYJmenr1NZ03DY-AtwT4TDf6Q@mail.gmail.com","threadId":"61695","inReplyTo":"CA+J6zkRoS5uZFkW1jJv1JO7jPMPO-ZANOYerbUxn4WPaApPV6g@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v13 12/12] cat-file: make remote-object-info allow-list dynamic","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-21T06:27:52Z","receivedAt":"2026-06-21T06:28:20Z","isPatch":true,"body":"Forgot to mention this in the earlier email, but the comment explaining\nwhy the backward iteration of the list is needed should also explain why\nit is fine to cast `args->object_info_options->nr` to `int` from `size_t`\n(it will always be a small number, so no risk of overflow).\n"},{"id":"546085","messageId":"xmqqo6h37jbk.fsf@gitster.g","threadId":"61695","inReplyTo":"CA+J6zkTjgHAWtJwxY8jo0i9zDtxwj9uUsKAtLS3z1=WxZfr8Zw@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v13 10/12] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-21T20:02:55Z","receivedAt":"2026-06-21T20:02:58Z","isPatch":true,"body":"Chandra Pratap <chandrapratap3519@gmail.com> writes:\n\n> [snip]\n>> +static void parse_cmd_remote_object_info(struct batch_options *opt,\n>> +                                        const char *line, struct strbuf *output,\n>> +                                        struct expand_data *data)\n>> +{\n>> +       int count;\n>> +       const char **argv;\n>> +       char *line_to_split;\n>> +       static struct object_info *remote_object_info;\n>> +       static struct oid_array object_info_oids = OID_ARRAY_INIT;\n>\n> I don't get the point of remote_object_info and object_info_oids\n> being static here? These variables are allocated, utilized, and\n> completely freed/disconnected within a single command cycle.\n\nGreat observation.\n\n> Making them static gives me the false impression that state\n> needs to persist between calls.\n\nYes, and makes it thread-unsafe, even though if is questionable if\nthis particular function has to be thread safe ;-)\n\n"},{"id":"546160","messageId":"CAOLa=ZT_CGe8JHeRS+hv+i=Xipo4Lz-kYqQu4zwvo-u+BmakVQ@mail.gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-3-3d4c7315d2f8@gmail.com","subject":"Re: [PATCH GSoC RFC v13 03/12] cat-file: declare loop counter inside for()","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-22T10:08:29Z","receivedAt":"2026-06-22T10:08:31Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> From: Eric Ju <eric.peijian@gmail.com>\n>\n> Some code used in this series declares variable i and only uses it\n> in a for loop, not in any other logic outside the loop.\n>\n> Change the declaration of i to be inside the for loop for readability.\n> While at it, we also change its type from \"int\" to \"size_t\" where the\n> latter makes more sense.\n>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  builtin/cat-file.c | 13 ++++---------\n>  fetch-pack.c       |  3 +--\n>  2 files changed, 5 insertions(+), 11 deletions(-)\n>\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 446d649904..fab55c11de 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -723,14 +723,12 @@ static void dispatch_calls(struct batch_options *opt,\n>  \t\tstruct strbuf *output,\n>  \t\tstruct expand_data *data,\n>  \t\tstruct queued_cmd *cmd,\n> -\t\tint nr)\n> +\t\tsize_t nr)\n>\n\nThis is fine since both the locations that dispatch_calls() are called\nwith, supply a size_t.\n\nThe rest seems to have been already validated by Junio.\n\nLooks good\n\n[snip]\n"},{"id":"546162","messageId":"CAOLa=ZRUoBKPAjh6He0qgdZdzAzMxmeS9RMRi-czpHEfKG6EKw@mail.gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-5-3d4c7315d2f8@gmail.com","subject":"Re: [PATCH GSoC RFC v13 05/12] fetch-pack: move function to connect.c","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-22T10:30:13Z","receivedAt":"2026-06-22T10:30:14Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> write_fetch_command_and_capabilities will be refactored in a subsequent\n> commit where it will become a more general-purpose function, making it\n> more accessible to additional commands in the future.\n\nOkay.\n\n> To move `write_fetch_command_and_capabilities()` to `connect.c`, we need\n> to adjust how `advertise_sid` is managed. Previously in `fetch_pack.c`,\n> `advertise_sid` was a static variable, modified using\n> `repo_config_get_bool()`.\n\nNit: What's missing is why do we need to move it to 'connect.c', I\nassume this is because it being generic means its better placed in\nconnect.c over 'fetch-pack.c'. Would be nice to explicitly mention that\nperhaps?\n\n>\n> In `connect.c`, we now initialize `advertise_sid` at the begining by\n> directly using `repo_config_get_bool()`. This change is safe because:\n>\n> In the original `fetch-pack.c` code, there are only two places that write\n> `advertise_sid`:\n>\n> 1. In function `do_fetch_pack()`:\n>         if (!sever_supports(\"session_id\"))\n>                advertise_sid = 0;\n> 2. In function `fetch_pack_config()`:\n>         repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n>\n> About 1, since `do_fetch_pack()` is only relevant for protocol v1, this\n> assignment can be ignored, as `write_fetch_command_and_capabilities()`\n> is only used in v2.\n>\n> About 2, `repo_config_get_bool()` is from `config.h` and it's an out-of-box\n> dependency of `connect.c`, so we can reuse it directly.\n>\n> Move `write_fetch_command_and_capabilities()` to `connect.c`\n>\n\nNit: Wouldn't it then make sense to split this into two?\n1. Drop usage of the static `advertise_sid` within\n`write_fetch_command_and_capabilities()`.\n2. Move `write_fetch_command_and_capabilities()` to `connect.c`\n\nThat way the second patch is simply a move?\n\n[snip]\n"},{"id":"546216","messageId":"CAOLa=ZSvxXuf_bSzKMvViNQ5MuDAqxnQdo4asF9vfMhJaDQcVw@mail.gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-6-3d4c7315d2f8@gmail.com","subject":"Re: [PATCH GSoC RFC v13 06/12] connect: refactor packet writing","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-22T20:43:01Z","receivedAt":"2026-06-22T20:43:03Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n[snip]\n\n> diff --git a/connect.c b/connect.c\n> index 1dced8e632..78c69d4485 100644\n> --- a/connect.c\n> +++ b/connect.c\n> @@ -700,16 +700,16 @@ int server_supports(const char *feature)\n>  \treturn !!server_feature_value(feature, NULL);\n>  }\n>\n> -void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> -\t\t\t\t\t  const struct string_list *server_options)\n> +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n> +\t\t\t\t    const struct string_list *server_options)\n>  {\n>  \tconst char *hash_name;\n>  \tint advertise_sid;\n>\n>  \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>\n> -\tensure_server_supports_v2(\"fetch\");\n> -\tpacket_buf_write(req_buf, \"command=fetch\");\n> +\tensure_server_supports_v2(command);\n> +\tpacket_buf_write(req_buf, \"command=%s\", command);\n>  \tif (server_supports_v2(\"agent\"))\n>  \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n>  \tif (advertise_sid && server_supports_v2(\"session-id\"))\n> @@ -727,7 +727,7 @@ void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>  \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n>  \t\t\t    the_hash_algo->name, hash_name);\n>  \t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n> -\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n> +\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n>  \t\tdie(_(\"the server does not support algorithm '%s'\"),\n>  \t\t    the_hash_algo->name);\n>  \t}\n\nWhy did we make this change? If the server doesn't support v2, then the\nobject format should be `GIT_HASH_SHA1_LEGACY`. While the value of it is\nindeed `GIT_HASH_SHA1`, it indicates a scenario where there was no\noption to select object hash, which is the scenario here.\n\nIf there is a reason to make such a change, perhaps we should highlight\nthis in the commit message.\n\n> diff --git a/connect.h b/connect.h\n> index c4f6ea4b0a..8f4c523892 100644\n> --- a/connect.h\n> +++ b/connect.h\n> @@ -34,8 +34,12 @@ void check_stateless_delimiter(int stateless_rpc,\n>  \t\t\t       struct packet_reader *reader,\n>  \t\t\t       const char *error);\n>\n> +/*\n> + * Writes a command along with the requested server capabilities/features into a\n> + * request buffer.\n> + */\n>  struct string_list;\n\nThe comment should be above the function and not the forward\ndeclaration.\n\nWhile we're here, why not `#include \"string-list.h\"` and remove the\nforward declaration, is there a circular dependency?\n\n> -void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> -\t\t\t\t\t  const struct string_list *server_options);\n> +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n> +\t\t\t\t    const struct string_list *server_options);\n>\n>  #endif\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index 4a8a70b5f3..3d32114907 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1387,7 +1387,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n>  \tint done_sent = 0;\n>  \tstruct strbuf req_buf = STRBUF_INIT;\n>\n> -\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n> +\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n>\n>  \tif (args->use_thin_pack)\n>  \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n> @@ -2255,7 +2255,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n>  \t\t\t\t\t   the_repository, \"%d\",\n>  \t\t\t\t\t   negotiation_round);\n>  \t\tstrbuf_reset(&req_buf);\n> -\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n> +\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n>\n>  \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n>\n>\n> --\n> 2.54.0\n"},{"id":"546299","messageId":"CAN5EUNRN4_5PS3cbQtQfpyRuwByvV=qvAVKnVbgT-pirKGnnTg@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZSvxXuf_bSzKMvViNQ5MuDAqxnQdo4asF9vfMhJaDQcVw@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v13 06/12] connect: refactor packet writing","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-24T12:08:29Z","receivedAt":"2026-06-24T12:08:41Z","isPatch":true,"body":"El lun, 22 jun 2026 a las 22:43, Karthik Nayak\n(<karthik.188@gmail.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> [snip]\n>\n> > diff --git a/connect.c b/connect.c\n> > index 1dced8e632..78c69d4485 100644\n> > --- a/connect.c\n> > +++ b/connect.c\n> > @@ -700,16 +700,16 @@ int server_supports(const char *feature)\n> >       return !!server_feature_value(feature, NULL);\n> >  }\n> >\n> > -void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> > -                                       const struct string_list *server_options)\n> > +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n> > +                                 const struct string_list *server_options)\n> >  {\n> >       const char *hash_name;\n> >       int advertise_sid;\n> >\n> >       repo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n> >\n> > -     ensure_server_supports_v2(\"fetch\");\n> > -     packet_buf_write(req_buf, \"command=fetch\");\n> > +     ensure_server_supports_v2(command);\n> > +     packet_buf_write(req_buf, \"command=%s\", command);\n> >       if (server_supports_v2(\"agent\"))\n> >               packet_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n> >       if (advertise_sid && server_supports_v2(\"session-id\"))\n> > @@ -727,7 +727,7 @@ void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> >                       die(_(\"mismatched algorithms: client %s; server %s\"),\n> >                           the_hash_algo->name, hash_name);\n> >               packet_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n> > -     } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n> > +     } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n> >               die(_(\"the server does not support algorithm '%s'\"),\n> >                   the_hash_algo->name);\n> >       }\n>\n> Why did we make this change? If the server doesn't support v2, then the\n> object format should be `GIT_HASH_SHA1_LEGACY`. While the value of it is\n> indeed `GIT_HASH_SHA1`, it indicates a scenario where there was no\n> option to select object hash, which is the scenario here.\n>\n> If there is a reason to make such a change, perhaps we should highlight\n> this in the commit message.\n\nHi!\nThere should be no diff related to that line, In some point between\nEric's last version (v11) and mine's firs (v12) the original code\nchanged. On the diff from v11 [1] the object format is the same, i\ndidn't notice this change and it's wrong, I'll fix it for v14, Thanks!\n\n>\n> > diff --git a/connect.h b/connect.h\n> > index c4f6ea4b0a..8f4c523892 100644\n> > --- a/connect.h\n> > +++ b/connect.h\n> > @@ -34,8 +34,12 @@ void check_stateless_delimiter(int stateless_rpc,\n> >                              struct packet_reader *reader,\n> >                              const char *error);\n> >\n> > +/*\n> > + * Writes a command along with the requested server capabilities/features into a\n> > + * request buffer.\n> > + */\n> >  struct string_list;\n>\n> The comment should be above the function and not the forward\n> declaration.\n\nTrue, I'll fix it for v14.\n\n>\n> While we're here, why not `#include \"string-list.h\"` and remove the\n> forward declaration, is there a circular dependency?\n\nI believe this was right because from what I know forward declarations\nare prefered in headers when in this case, the struct is only used as\na pointer. Investigating, this came from a review from patrick [2].\n\n[snip]\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-5-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n\nThanks for the review,\nPablo.\n"},{"id":"546315","messageId":"CAN5EUNRMZd+NoiAHd-f0Gx4CqRPs7759a4UQh=GDeUgsFKbdJg@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZRUoBKPAjh6He0qgdZdzAzMxmeS9RMRi-czpHEfKG6EKw@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v13 05/12] fetch-pack: move function to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-24T12:21:11Z","receivedAt":"2026-06-24T12:21:23Z","isPatch":true,"body":"El lun, 22 jun 2026 a las 12:30, Karthik Nayak\n(<karthik.188@gmail.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> > write_fetch_command_and_capabilities will be refactored in a subsequent\n> > commit where it will become a more general-purpose function, making it\n> > more accessible to additional commands in the future.\n>\n> Okay.\n>\n> > To move `write_fetch_command_and_capabilities()` to `connect.c`, we need\n> > to adjust how `advertise_sid` is managed. Previously in `fetch_pack.c`,\n> > `advertise_sid` was a static variable, modified using\n> > `repo_config_get_bool()`.\n>\n> Nit: What's missing is why do we need to move it to 'connect.c', I\n> assume this is because it being generic means its better placed in\n> connect.c over 'fetch-pack.c'. Would be nice to explicitly mention that\n> perhaps?\n\nTrue, it is for that reason, I'll write it explicitly in the next\nversion, thanks!\n\n>\n> >\n> > In `connect.c`, we now initialize `advertise_sid` at the begining by\n> > directly using `repo_config_get_bool()`. This change is safe because:\n> >\n> > In the original `fetch-pack.c` code, there are only two places that write\n> > `advertise_sid`:\n> >\n> > 1. In function `do_fetch_pack()`:\n> >         if (!sever_supports(\"session_id\"))\n> >                advertise_sid = 0;\n> > 2. In function `fetch_pack_config()`:\n> >         repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n> >\n> > About 1, since `do_fetch_pack()` is only relevant for protocol v1, this\n> > assignment can be ignored, as `write_fetch_command_and_capabilities()`\n> > is only used in v2.\n> >\n> > About 2, `repo_config_get_bool()` is from `config.h` and it's an out-of-box\n> > dependency of `connect.c`, so we can reuse it directly.\n> >\n> > Move `write_fetch_command_and_capabilities()` to `connect.c`\n> >\n>\n> Nit: Wouldn't it then make sense to split this into two?\n> 1. Drop usage of the static `advertise_sid` within\n> `write_fetch_command_and_capabilities()`.\n> 2. Move `write_fetch_command_and_capabilities()` to `connect.c`\n>\n> That way the second patch is simply a move?\n\nOkay, seems fair, I'll do that, thanks.\n\n>\n> [snip]\n\nThanks for the review,\nPablo\n"},{"id":"546316","messageId":"CAN5EUNRO-HaXwq+XLTvR_DzumGws1Rv4yej9GngQDLQ36XyZ2g@mail.gmail.com","threadId":"61695","inReplyTo":"CA+J6zkQEqTeNWkHJWDD6MmK4hesKofBVobDt9OcQ-FSVLC28pw@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v13 05/12] fetch-pack: move function to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-24T12:24:37Z","receivedAt":"2026-06-24T12:24:49Z","isPatch":true,"body":"El dom, 21 jun 2026 a las 7:38, Chandra Pratap\n(<chandrapratap3519@gmail.com>) escribió:\n>\n> On Fri, 19 Jun 2026 at 20:26, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n> >\n> > write_fetch_command_and_capabilities will be refactored in a subsequent\n>\n> Nit: the rest of this patch's body referes to this function as:\n> `write_fetch_command_and_capabilities()`\n>\n> Let's use that here as well.\n\nI'll do that, thanks.\n\n>\n> > commit where it will become a more general-purpose function, making it\n> > more accessible to additional commands in the future.\n> >\n> > To move `write_fetch_command_and_capabilities()` to `connect.c`, we need\n> > to adjust how `advertise_sid` is managed. Previously in `fetch_pack.c`,\n> > `advertise_sid` was a static variable, modified using\n> > `repo_config_get_bool()`.\n> >\n> > In `connect.c`, we now initialize `advertise_sid` at the begining by\n> > directly using `repo_config_get_bool()`. This change is safe because:\n> >\n> > In the original `fetch-pack.c` code, there are only two places that write\n> > `advertise_sid`:\n> >\n> > 1. In function `do_fetch_pack()`:\n> >         if (!sever_supports(\"session_id\"))\n>\n> s/sever/server\n\nTrue, thanks.\n\n>\n> >                advertise_sid = 0;\n> > 2. In function `fetch_pack_config()`:\n> >         repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n> >\n> > About 1, since `do_fetch_pack()` is only relevant for protocol v1, this\n> > assignment can be ignored, as `write_fetch_command_and_capabilities()`\n> > is only used in v2.\n> >\n> > About 2, `repo_config_get_bool()` is from `config.h` and it's an out-of-box\n> > dependency of `connect.c`, so we can reuse it directly.\n> >\n> > Move `write_fetch_command_and_capabilities()` to `connect.c`\n>\n> Nit: this is a better patch header than \"move function to connect.c\",\n> since it better describes the exact change we intend to make.\n>\n> Let's use it instead.\n\nOkay, I'll use it.\n\nThanks for the review,\nPablo\n"},{"id":"546319","messageId":"CAOLa=ZQUCrAgnyoUCpd_G8PEdYunN7on=unR-TYcUe=yURyrtA@mail.gmail.com","threadId":"61695","inReplyTo":"CAN5EUNRN4_5PS3cbQtQfpyRuwByvV=qvAVKnVbgT-pirKGnnTg@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v13 06/12] connect: refactor packet writing","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-24T13:25:09Z","receivedAt":"2026-06-24T13:25:11Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> El lun, 22 jun 2026 a las 22:43, Karthik Nayak\n> (<karthik.188@gmail.com>) escribió:\n>>\n>> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>>\n>> [snip]\n>>\n>> > diff --git a/connect.c b/connect.c\n>> > index 1dced8e632..78c69d4485 100644\n>> > --- a/connect.c\n>> > +++ b/connect.c\n>> > @@ -700,16 +700,16 @@ int server_supports(const char *feature)\n>> >       return !!server_feature_value(feature, NULL);\n>> >  }\n>> >\n>> > -void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>> > -                                       const struct string_list *server_options)\n>> > +void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n>> > +                                 const struct string_list *server_options)\n>> >  {\n>> >       const char *hash_name;\n>> >       int advertise_sid;\n>> >\n>> >       repo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>> >\n>> > -     ensure_server_supports_v2(\"fetch\");\n>> > -     packet_buf_write(req_buf, \"command=fetch\");\n>> > +     ensure_server_supports_v2(command);\n>> > +     packet_buf_write(req_buf, \"command=%s\", command);\n>> >       if (server_supports_v2(\"agent\"))\n>> >               packet_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n>> >       if (advertise_sid && server_supports_v2(\"session-id\"))\n>> > @@ -727,7 +727,7 @@ void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>> >                       die(_(\"mismatched algorithms: client %s; server %s\"),\n>> >                           the_hash_algo->name, hash_name);\n>> >               packet_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n>> > -     } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n>> > +     } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) {\n>> >               die(_(\"the server does not support algorithm '%s'\"),\n>> >                   the_hash_algo->name);\n>> >       }\n>>\n>> Why did we make this change? If the server doesn't support v2, then the\n>> object format should be `GIT_HASH_SHA1_LEGACY`. While the value of it is\n>> indeed `GIT_HASH_SHA1`, it indicates a scenario where there was no\n>> option to select object hash, which is the scenario here.\n>>\n>> If there is a reason to make such a change, perhaps we should highlight\n>> this in the commit message.\n>\n> Hi!\n> There should be no diff related to that line, In some point between\n> Eric's last version (v11) and mine's firs (v12) the original code\n> changed. On the diff from v11 [1] the object format is the same, i\n> didn't notice this change and it's wrong, I'll fix it for v14, Thanks!\n>\n>>\n>> > diff --git a/connect.h b/connect.h\n>> > index c4f6ea4b0a..8f4c523892 100644\n>> > --- a/connect.h\n>> > +++ b/connect.h\n>> > @@ -34,8 +34,12 @@ void check_stateless_delimiter(int stateless_rpc,\n>> >                              struct packet_reader *reader,\n>> >                              const char *error);\n>> >\n>> > +/*\n>> > + * Writes a command along with the requested server capabilities/features into a\n>> > + * request buffer.\n>> > + */\n>> >  struct string_list;\n>>\n>> The comment should be above the function and not the forward\n>> declaration.\n>\n> True, I'll fix it for v14.\n>\n>>\n>> While we're here, why not `#include \"string-list.h\"` and remove the\n>> forward declaration, is there a circular dependency?\n>\n> I believe this was right because from what I know forward declarations\n> are prefered in headers when in this case, the struct is only used as\n> a pointer. Investigating, this came from a review from patrick [2].\n>\n\nThat's fair.\n\nNit: Maybe add this context to the commit message?\n\nThanks\n\n> [snip]\n>\n> [1]: https://lore.kernel.org/git/20250221190451.12536-5-eric.peijian@gmail.com/\n> [2]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n>\n> Thanks for the review,\n> Pablo.\n"},{"id":"546344","messageId":"xmqqpl1fhesc.fsf@gitster.g","threadId":"61695","inReplyTo":"CAOLa=ZSvxXuf_bSzKMvViNQ5MuDAqxnQdo4asF9vfMhJaDQcVw@mail.gmail.com","subject":"Re: [PATCH GSoC RFC v13 06/12] connect: refactor packet writing","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-24T20:19:47Z","receivedAt":"2026-06-24T20:19:50Z","isPatch":true,"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n>> +/*\n>> + * Writes a command along with the requested server capabilities/features into a\n>> + * request buffer.\n>> + */\n>>  struct string_list;\n>\n> The comment should be above the function and not the forward\n> declaration.\n>\n> While we're here, why not `#include \"string-list.h\"` and remove the\n> forward declaration, is there a circular dependency?\n\nIsn't it to avoid unnecessary include?  When the header itself only\nneeds to know about the presence of the type, and not the concrete\nshape of the type (e.g., because it only uses a pointer to that\ntype), it may be overkill to include the entire header file.\n\n"},{"id":"546402","messageId":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com","subject":"[PATCH GSoC v14 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:22Z","receivedAt":"2026-06-25T12:13:41Z","isPatch":true,"body":"This patch series is a continuation of Eric Ju's (eric.peijian@gmail.com) and\nCalvin Wan's (calvinwan@google.com) patch series [1] and [2] respectively.\n\nSometimes it is beneficial to retrieve information about an object without\nhaving to download it completely. The server logic for retrieving size has\nalready been implemented and merged in \"a2ba162cda (object-info: support for\nretrieving object info, 2021-04-20)\"[3]. This patch series implement the client\noption for it.\n\nEric's series adds the `remote-object-info` command to\n`cat-file --batch-command`. This command allows the client to make an\nobject-info command request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info capability,\n`cat-file --batch-command` will die.\n\nIf a user attempts to use `remote-object-info` with protocol v1,\n`cat-file --batch-command` will die.\n\nCurrently, only the size (%(objectsize)) is supported end to end in this\nimplementation. The type (%(objecttype)) is known by the client's allow-list\nand request path but is not supported on the server side nor the response\nparsing. A follow up series will add full end-to-end support for %(objecttype).\n\nThe default format for remote-object-info is set to %(objectname) %(objectsize).\nOnce %(objecttype) is supported, the default format will be unified accordingly.\n\nIf the batch command format includes unsupported fields such as %(objecttype),\n%(objectsize:disk), or %(deltabase), the command will return empty strings for\neach unsupported field.\n\nThis series completes Eric's work mainly with the refactor of the validation\nof the placeholder with an allow-list that filters what the client asks with\nwhat the server is capable of provide following Jeff King's idea [4].\n\nGitHub CI: https://github.com/pabloosabaterr/git/actions/runs/28159024232\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n[4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n[5]: https://lore.kernel.org/git/CAN2LT1D3d=yMYVhBjpj5PvyjfTVjwqcFPNViuCJ=f49YbCZuJg@mail.gmail.com/\n\nChanges since v13:\n- Changed the introduced strtoul_ul to strtoul_szt as a recent patch\n  changed the type of object_info.sizep to size_t.\n  <37d030d8675e94caee2eecb8398691d385d444bd.1781524349.git.gitgitgadget@gmail.com>\n- Fixed commit typos and style.\n- \"fetch-pack: move function to connect.c\" is now 2 different commits:\n  first a cleanup and a second to only move the function.\n- Fixed old code brought from the rebase to match upstream: use\n  SHA1_LEGACY instead of SHA1\n- Remove static declarations from cmd_remote_remote_info() and\n  get_remote_info().\n- Add a comment justifying why there will be no overflow on explicit\n  cast.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nCalvin Wan (3):\n      fetch-pack: move fetch initialization\n      serve: advertise object-info feature\n      transport: add client support for object-info\n\nEric Ju (4):\n      git-compat-util: add strtoul_szt() with error handling\n      cat-file: declare loop counter inside for()\n      t1006: split test utility functions into new \"lib-cat-file.sh\"\n      cat-file: add remote-object-info to batch-command\n\nPablo Sabater (6):\n      transport-helper: fix memory leak of helper on disconnect\n      fetch-pack: prepare function to be moved\n      fetch-pack: move function to connect.c\n      connect: refactor packet writing\n      cat-file: validate remote atoms with allow_list\n      cat-file: make remote-object-info allow-list dynamic\n\n Documentation/git-cat-file.adoc        |  25 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 222 ++++++++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    | 110 ++++++\n fetch-object-info.h                    |  22 ++\n fetch-pack.c                           |  51 +--\n fetch-pack.h                           |   2 +\n git-compat-util.h                      |  20 +\n meson.build                            |   1 +\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n serve.c                                |   5 +-\n t/lib-cat-file.sh                      |  16 +\n t/meson.build                          |   1 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 699 +++++++++++++++++++++++++++++++++\n transport-helper.c                     |  13 +-\n transport.c                            |  28 +-\n transport.h                            |  11 +\n 21 files changed, 1220 insertions(+), 75 deletions(-)\n---\nbase-commit: ab776a62a78576513ee121424adb19597fbb7613\nchange-id: 20260608-ps-eric-work-rebase-b73ae84ba671\n\nBest regards,\n--  \nPablo Sabater <pabloosabaterr@gmail.com>\n"},{"id":"546403","messageId":"20260625-ps-eric-work-rebase-v14-1-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 01/13] transport-helper: fix memory leak of helper on disconnect","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:23Z","receivedAt":"2026-06-25T12:13:42Z","isPatch":true,"body":"disconnect_helper() only frees data inside of the if(data->helper)\nblock [1]. When the transport is disconnected without the helper\nbeing fully started, data->name allocated in transport_helper_init()\nis never freed.\n\nMove FREE_AND_NULL(data->name) outside the conditional block so it's\nalways freed on disconnect.\n\n[1]: https://lore.kernel.org/git/05fbadbae2184479c87c37675dde7bd79b3e32ab.1716465556.git.ps@pks.im/\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n transport-helper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 80f90eb7ba..f195070788 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tres = finish_command(data->helper);\n-\t\tFREE_AND_NULL(data->name);\n \t\tFREE_AND_NULL(data->helper);\n \t}\n+\tFREE_AND_NULL(data->name);\n \treturn res;\n }\n \n\n-- \n2.54.0\n"},{"id":"546404","messageId":"20260625-ps-eric-work-rebase-v14-2-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 02/13] git-compat-util: add strtoul_szt() with error handling","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:24Z","receivedAt":"2026-06-25T12:13:43Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nWe already have strtoul_ui() and similar functions that provide proper\nerror handling using strtoul from the standard library. However,\nthere isn't currently a variant that returns an unsigned long.\n\nThis variant is needed in a subsequent commit to enable returning an\nsize_t with proper error handling.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n git-compat-util.h | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 8809776407..7f417f1acf 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n \treturn 0;\n }\n \n+/*\n+ * Convert a string to a size_t using the standard library's strtoul, with\n+ * additional error handling to ensure robustness.\n+ */\n+static inline int strtoul_szt(char const *s, int base, size_t *result)\n+{\n+\tunsigned long ul;\n+\tchar *p;\n+\n+\terrno = 0;\n+\t/* negative values would be accepted by strtoul */\n+\tif (strchr(s, '-'))\n+\t\treturn -1;\n+\tul = strtoul(s, &p, base);\n+\tif (errno || *p || p == s)\n+\t\treturn -1;\n+\t*result = ul;\n+\treturn 0;\n+}\n+\n static inline int strtol_i(char const *s, int base, int *result)\n {\n \tlong ul;\n\n-- \n2.54.0\n"},{"id":"546405","messageId":"20260625-ps-eric-work-rebase-v14-3-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 03/13] cat-file: declare loop counter inside for()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:25Z","receivedAt":"2026-06-25T12:13:44Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSome code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\nWhile at it, we also change its type from \"int\" to \"size_t\" where the\nlatter makes more sense.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c | 13 ++++---------\n fetch-pack.c       |  3 +--\n 2 files changed, 5 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex d6ef8414ee..1e5473ab70 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -718,14 +718,12 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n \t\tstruct queued_cmd *cmd,\n-\t\tint nr)\n+\t\tsize_t nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -733,9 +731,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -762,7 +758,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -772,7 +767,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 120e01f3cf..f13951d154 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n\n-- \n2.54.0\n"},{"id":"546406","messageId":"20260625-ps-eric-work-rebase-v14-4-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 04/13] t1006: split test utility functions into new \"lib-cat-file.sh\"","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:26Z","receivedAt":"2026-06-25T12:13:46Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nThis refactor extracts utility functions from the cat-file's test\nscript \"t1006-cat-file.sh\" into a new \"lib-cat-file.sh\" dedicated\nlibrary file.\n\nA subsequent commit will need this functions, the goal is to improve\ncode reuse and readability,enabling future tests to leverage these\nutilities without duplicating code.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 13 +------------\n 2 files changed, 17 insertions(+), 12 deletions(-)\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..44af232d74\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 8e2c52652c..8360f3bbd9 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-loose.sh\"\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     object_name=\"$2\"\n\n-- \n2.54.0\n"},{"id":"546407","messageId":"20260625-ps-eric-work-rebase-v14-5-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 05/13] fetch-pack: prepare function to be moved","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:27Z","receivedAt":"2026-06-25T12:13:47Z","isPatch":true,"body":"`write_fetch_command_and_capabilities()` will be refactored and moved in\nsubsequent commits where it will become a more general-purpose function,\nmaking it more accessible to additional commands in the future.\n\nTo move `write_fetch_command_and_capabilities()` to `connect.c`, we\npreviously need to adjust how `advertise_sid` is managed. Currently in\n`fetch_pack.c`, `advertise_sid` is a static variable, modified using\n`repo_config_get_bool()`.\n\nInitialize `advertise_sid` at the begining by directly using\n`repo_config_get_bool()`. This change is safe because:\n\nIn the original `fetch-pack.c` code, there are only two places that write\n`advertise_sid`:\n\n1. In function `do_fetch_pack()`:\n        if (!server_supports(\"session_id\"))\n               advertise_sid = 0;\n2. In function `fetch_pack_config()`:\n        repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, since `do_fetch_pack()` is only relevant for protocol v1, this\nassignment can be ignored, as `write_fetch_command_and_capabilities()`\nis only used in v2.\n\nAbout 2, `repo_config_get_bool()` is from `config.h` and it's an\nout-of-box dependency of `connect.c`, so we can reuse it directly.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f13951d154..ad07603755 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1380,6 +1380,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t\t\t\t\t\t const struct string_list *server_options)\n {\n \tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tensure_server_supports_v2(\"fetch\");\n \tpacket_buf_write(req_buf, \"command=fetch\");\n@@ -1395,7 +1398,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t}\n \n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n \t\t\t    the_hash_algo->name, hash_name);\n\n-- \n2.54.0\n"},{"id":"546408","messageId":"20260625-ps-eric-work-rebase-v14-6-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 06/13] fetch-pack: move function to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:28Z","receivedAt":"2026-06-25T12:13:48Z","isPatch":true,"body":"write_fetch_command_and_capabilities will be refactored in a subsequent\ncommit where it will become a more general-purpose function, making it\nmore accessible to additional commands in the future.\n\nMove `write_fetch_command_and_capabilities()` to `connect.c`, where\nthere are similar purpose functions.\n\nBecause string_list is only used as a pointer, use a forward\ndeclaration [1].\n\n[1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 34 ----------------------------------\n 3 files changed, 38 insertions(+), 34 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 47e39d2a73..1dced8e632 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,6 +700,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(\"fetch\");\n+\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t    the_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t    the_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n static const char *url_scheme_name(enum url_scheme scheme)\n {\n \tswitch (scheme) {\ndiff --git a/connect.h b/connect.h\nindex aa482a37fb..c4f6ea4b0a 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+struct string_list;\n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex ad07603755..4a8a70b5f3 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1376,40 +1376,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\tint advertise_sid;\n-\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n\n-- \n2.54.0\n"},{"id":"546409","messageId":"20260625-ps-eric-work-rebase-v14-7-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 07/13] connect: refactor packet writing","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:29Z","receivedAt":"2026-06-25T12:13:49Z","isPatch":true,"body":"Refactor `write_fetch_command_and_capabilities()`, enabling it to serve\nboth fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162\n(object-info: support for retrieving object info, 2021-04-20).\n\nRefactor the function signature to accept a command instead of the\nhardcoded \"fetch\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 8 ++++----\n connect.h    | 8 ++++++--\n fetch-pack.c | 4 ++--\n 3 files changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 1dced8e632..7b472f8e5f 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,16 +700,16 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options)\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \tint advertise_sid;\n \n \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\ndiff --git a/connect.h b/connect.h\nindex c4f6ea4b0a..c2bf492ed9 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -35,7 +35,11 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       const char *error);\n \n struct string_list;\n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options);\n+/*\n+ * Writes a command along with the requested server capabilities/features into a\n+ * request buffer.\n+ */\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 4a8a70b5f3..3d32114907 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1387,7 +1387,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2255,7 +2255,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n\n-- \n2.54.0\n"},{"id":"546410","messageId":"20260625-ps-eric-work-rebase-v14-8-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 08/13] fetch-pack: move fetch initialization","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:30Z","receivedAt":"2026-06-25T12:13:50Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized\nin FETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3d32114907..cdebd3476f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1736,18 +1736,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n\n-- \n2.54.0\n"},{"id":"546411","messageId":"20260625-ps-eric-work-rebase-v14-9-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 09/13] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:31Z","receivedAt":"2026-06-25T12:13:51Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This will allow a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n serve.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex 49a6e39b1d..2b07d922b3 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\t/* Currently only size is supported */\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n\n-- \n2.54.0\n"},{"id":"546412","messageId":"20260625-ps-eric-work-rebase-v14-10-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 10/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:32Z","receivedAt":"2026-06-25T12:13:53Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nThis commit introduces client functions to interact with the server.\n\nCurrently, the client supports requesting a list of object IDs with\nthe 'size' feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client will return an error and exit.\n\nNotice that the entire request is written into req_buf before being\nsent to the remote. This approach follows the pattern used in the\n`send_fetch_request()` logic within fetch-pack.c.\nStreaming the request is not addressed in this patch.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Makefile            |  1 +\n fetch-object-info.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h | 22 +++++++++++++\n fetch-pack.c        |  3 ++\n fetch-pack.h        |  2 ++\n meson.build         |  1 +\n transport-helper.c  | 11 +++++--\n transport.c         | 28 ++++++++++++++++-\n transport.h         | 11 +++++++\n 9 files changed, 166 insertions(+), 3 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 1cec251f43..ec4df39a6b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1159,6 +1159,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..9c4ae9bd11\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,90 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"odb.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends git-cat-file object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"unsupported protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader,\n+\t\t\t\t\t\t  \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\tsize_index = i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n+\t\tif (0 <= size_index) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\")) {\n+\t\t\t\tFREE_AND_NULL(object_info_data[i].sizep);\n+\t\t\t\tstring_list_clear(&object_info_values, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (strtoul_szt(object_info_values.items[1 + size_index].string,\n+\t\t\t\t       10, object_info_data[i].sizep))\n+\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t\t    object_info_values.items[0].string,\n+\t\t\t\t    object_info_values.items[1 + size_index].string);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..d35284bd6b\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"odb.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex cdebd3476f..a86c93fc52 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1742,6 +1742,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n \t\targs->deepen = 1;\n \n+\tif (args->object_info)\n+\t\tstate = FETCH_SEND_REQUEST;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 6d0dec7f41..5a428f11ed 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n@@ -43,6 +44,7 @@ struct fetch_pack_args {\n \tunsigned reject_shallow_remote:1;\n \tunsigned deepen:1;\n \tunsigned refetch:1;\n+\tunsigned object_info:1;\n \n \t/*\n \t * Indicate that the remote of this request is a promisor remote. The\ndiff --git a/meson.build b/meson.build\nindex 3247697f74..145c6882eb 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -347,6 +347,7 @@ libgit_sources = [\n   'exec-cmd.c',\n   'fetch-negotiator.c',\n   'fetch-pack.c',\n+  'fetch-object-info.c',\n   'fmt-merge-msg.c',\n   'fsck.c',\n   'fsmonitor.c',\ndiff --git a/transport-helper.c b/transport-helper.c\nindex f195070788..c77599f6fb 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -727,8 +727,8 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only and cat-file\n+\t * remote-object-info require protocol v2.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -744,6 +744,13 @@ static int fetch_refs(struct transport *transport,\n \t\tfree_refs(dummy);\n \t}\n \n+\t/* fail the command explicitly to avoid further commands input. */\n+\tif (transport->smart_options->object_info)\n+\t\tdie(_(\"remote-object-info requires protocol v2\"));\n+\n+\tif (!data->get_refs_list_called)\n+\t\tget_refs_list_using_list(transport, 0);\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\ndiff --git a/transport.c b/transport.c\nindex 0f5ec30247..7d3246e12b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -467,8 +468,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.negotiation_restrict_tips = data->options.negotiation_restrict_tips;\n \targs.negotiation_include_tips = data->options.negotiation_include_tips;\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n+\targs.object_info = transport->smart_options->object_info;\n+\n+\tif (transport->smart_options->object_info\n+\t    && transport->smart_options->object_info_oids->nr > 0) {\n+\t\tstruct packet_reader reader;\n+\t\tstruct object_info_args obj_info_args = { 0 };\n+\n+\t\tobj_info_args.server_options = transport->server_options;\n+\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n+\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n+\t\tstring_list_sort(obj_info_args.object_info_options);\n+\n+\t\tconnect_setup(transport, 0);\n+\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n+\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n+\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\t\tdata->version = discover_version(&reader);\n+\t\ttransport->hash_algo = reader.hash_algo;\n+\n+\t\tret = fetch_object_info(data->version, &obj_info_args, &reader,\n+\t\t\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n+\t\t\t\t\tdata->fd[1]);\n+\t\tgoto cleanup;\n \n-\tif (!data->finished_handshake) {\n+\t} else if (!data->finished_handshake) {\n \t\tint i;\n \t\tint must_list_refs = 0;\n \t\tfor (i = 0; i < nr_heads; i++) {\ndiff --git a/transport.h b/transport.h\nindex 7e5867cffa..bd60b10af4 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -6,6 +6,7 @@\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n #include \"connect.h\"\n+#include \"odb.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -31,6 +32,12 @@ struct git_transport_options {\n \t */\n \tunsigned connectivity_checked:1;\n \n+\t/*\n+\t * Transport will attempt to retrieve only object-info.\n+\t * If object-info is not supported, the operation will error and exit.\n+\t */\n+\tunsigned object_info : 1;\n+\n \tint depth;\n \tconst char *deepen_since;\n \tconst struct string_list *deepen_not;\n@@ -55,6 +62,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n\n-- \n2.54.0\n"},{"id":"546414","messageId":"20260625-ps-eric-work-rebase-v14-11-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 11/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:33Z","receivedAt":"2026-06-25T12:13:54Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSince the `info` command in `cat-file --batch-command` prints object\ninfo for a given object, it is natural to add another command in\n`cat-file --batch-command` to print object info for a given object\nfrom a remote.\n\nAdd `remote-object-info` to `cat-file --batch-command`.\n\nWhile `info` takes object ids one at a time, this creates\noverhead when making requests to a server. So `remote-object-info`\ninstead can take multiple object ids at once.\n\nThe `cat-file --batch-command` command is generally implemented in\nthe following manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for `remote-object-info` to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  24 +-\n builtin/cat-file.c                     | 145 ++++++-\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n t/meson.build                          |   1 +\n t/t1017-cat-file-remote-object-info.sh | 680 +++++++++++++++++++++++++++++++++\n transport.c                            |   4 +-\n 7 files changed, 860 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex 86b9181599..aba20eb770 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -169,6 +169,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -312,7 +319,8 @@ newline. The available atoms are:\n \tThe full hex representation of the object name.\n \n `objecttype`::\n-\tThe type of the object (the same as `cat-file -t` reports).\n+\tThe type of the object (the same as `cat-file -t` reports). See\n+\t`CAVEATS` below. Not supported by `remote-object-info`.\n \n `objectmode`::\n \tIf the specified object has mode information (such as a tree or\n@@ -325,13 +333,14 @@ newline. The available atoms are:\n \n `objectsize:disk`::\n \tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tnote about on-disk sizes in the `CAVEATS` section below. Not\n+\tsupported by `remote-object-info`.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n \tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tbelow. Not supported by `remote-object-info`.\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -341,7 +350,10 @@ newline. The available atoms are:\n \tline) are output in place of the `%(rest)` atom.\n \n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n+`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified, so\n+DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -438,6 +450,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n+currently not supported by the `remote-object-info` command, we will raise\n+an error and exit when they appear in the format string.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 1e5473ab70..49bd62ba3f 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -29,6 +29,22 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/*\n+ * Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8 * 1024)\n+\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE \\\n+\t(MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -633,6 +649,81 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt,\n+\t\t\t   int argc,\n+\t\t\t   const char **argv,\n+\t\t\t   struct object_info **remote_object_info,\n+\t\t\t   struct oid_array *object_info_oids)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstruct transport *gtransport;\n+\n+\t/*\n+\t * Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT.\n+\t */\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid)) {\n+\t\t\tsize_t len = strlen(argv[i]);\n+\n+\t\t\tif (len < the_hash_algo->hexsz && len >= 4) {\n+\t\t\t\tsize_t j;\n+\t\t\t\tfor (j = 0; j < len; j++)\n+\t\t\t\t\tif (!isxdigit(argv[i][j]))\n+\t\t\t\t\t\tbreak;\n+\t\t\t\tif (j == len)\n+\t\t\t\t\tdie(_(\"remote-object-info does not support \"\n+\t\t\t\t\t      \"short oids, %d characters required\"),\n+\t\t\t\t\t    (int)the_hash_algo->hexsz);\n+\t\t\t}\n+\t\t\tdie(_(\"not a valid object name '%s'\"), argv[i]);\n+\t\t}\n+\t\toid_array_append(object_info_oids, &oid);\n+\t}\n+\n+\tif (!object_info_oids->nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\n+\tif (!gtransport->smart_options) {\n+\t\tretval = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n+\tgtransport->smart_options->object_info = 1;\n+\tgtransport->smart_options->object_info_oids = object_info_oids;\n+\n+\t/* 'objectsize' is the only option currently supported */\n+\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\tstring_list_append(&object_info_options, \"size\");\n+\n+\tif (object_info_options.nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\t\tretval = transport_fetch_refs(gtransport, NULL);\n+\t}\n+cleanup:\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -714,6 +805,57 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\tstruct object_info *remote_object_info = NULL;\n+\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command too long\"));\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"split remote-object-info command\"));\n+\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n+\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n+\t\t    MAX_ALLOWED_OBJ_LIMIT);\n+\n+\tif (get_remote_info(opt, count, argv, &remote_object_info,\n+\t\t\t    &object_info_oids))\n+\t\tgoto cleanup;\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t} else {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+\toid_array_clear(&object_info_oids);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -745,8 +887,9 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1 },\n \t{ \"info\", parse_cmd_info, 1 },\n-\t{ \"flush\", NULL, 0 },\n \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n+\t{ \"flush\", NULL, 0 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex e3d92bbda2..9928e82e0b 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1694,3 +1694,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)\n \n \treturn &transaction->base;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+}\ndiff --git a/odb.h b/odb.h\nindex 3834a0dcbf..42e3934035 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -573,4 +573,7 @@ void parse_alternates(const char *string,\n \t\t      const char *relative_base,\n \t\t      struct strvec *out);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* ODB_H */\ndiff --git a/t/meson.build b/t/meson.build\nindex 3219264fe7..54d21111a3 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -170,6 +170,7 @@ integration_tests = [\n   't1014-read-tree-confusing.sh',\n   't1015-read-index-unmerged.sh',\n   't1016-compatObjectFormat.sh',\n+  't1017-cat-file-remote-object-info.sh',\n   't1020-subdirectory.sh',\n   't1022-read-tree-partial-clone.sh',\n   't1050-large.sh',\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..b744e81701\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,680 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n+\n+unstored_content=\"Hello Git\"\n+unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not support short oids' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_short_oid\n+\t\tEOF\n+\t\ttest_grep \"does not support short oids\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not die on missing oid like info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tgit cat-file --batch-command >local <<-EOF &&\n+\t\tinfo $unstored_oid\n+\t\tEOF\n+\t\tgit cat-file --batch-command >remote <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $unstored_oid\n+\t\tEOF\n+\t\ttest_cmp local remote\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\ndiff --git a/transport.c b/transport.c\nindex 7d3246e12b..81faf8e748 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -470,8 +470,8 @@ static int fetch_refs_via_pack(struct transport *transport,\n \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n \targs.object_info = transport->smart_options->object_info;\n \n-\tif (transport->smart_options->object_info\n-\t    && transport->smart_options->object_info_oids->nr > 0) {\n+\tif (transport->smart_options->object_info &&\n+\t    transport->smart_options->object_info_oids->nr > 0) {\n \t\tstruct packet_reader reader;\n \t\tstruct object_info_args obj_info_args = { 0 };\n \n\n-- \n2.54.0\n"},{"id":"546413","messageId":"20260625-ps-eric-work-rebase-v14-12-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 12/13] cat-file: validate remote atoms with allow_list","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:34Z","receivedAt":"2026-06-25T12:13:55Z","isPatch":true,"body":"strstr() is not enough to validate the format placeholders in\nremote-object-info causing two errors:\n\n- Atoms recognized by expand_atom() but the remote doesn't returns 1, but\n  data->type contains garbage causing segfault.\n- expand_atom() returns 0 for unknown atoms, calling\n  strbuf_expand_bad_format() which ends in die() blocking local queries\n  if the same format is shared.\n\nAdd an allow_list with the supported atoms at the top of expand_atom().\nIn remote mode, unsupported atoms return 1 leaving the sb empty,\nhonoring how for-each-ref handles known but inapplicable atoms.\n\nAs extra safety, initialize data->type to OBJ_BAD and add a NULL check\nfor type_name() so uninitialized data doesn't cause segfault.\n\nUpdate tests that expect previous die() behaviour to expect an empty\nstring and add an explicit test for empty string return on unknown\nplaceholder.\n\nUpdate caveat behaviour documentation.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  5 +++--\n builtin/cat-file.c                     | 41 +++++++++++++++++++++++++++-------\n t/t1017-cat-file-remote-object-info.sh | 27 ++++++++++++++++++----\n 3 files changed, 59 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex aba20eb770..3b7a85b383 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -451,8 +451,9 @@ CAVEATS\n -------\n \n Note that since %(objecttype), %(objectsize:disk) and %(deltabase) are\n-currently not supported by the `remote-object-info` command, we will raise\n-an error and exit when they appear in the format string.\n+currently not supported by the `remote-object-info` command, they will\n+return an empty string for remote queries, matching how `for-each-ref`\n+behaves for known but inapplicable placeholders.\n \n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 49bd62ba3f..ea3869f3ed 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -333,8 +333,18 @@ struct expand_data {\n \t * optimized out.\n \t */\n \tunsigned skip_object_info : 1;\n+\n+\t/*\n+\t * Flags about when an object info is being fetched from remote.\n+\t */\n+\tunsigned is_remote:1;\n+};\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n+\n+static const char *remote_object_info_atoms[] = {\n+\t\"objectname\",\n+\t\"objectsize\",\n };\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -345,14 +355,31 @@ static int is_atom(const char *atom, const char *s, int slen)\n static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n+\tif (data->is_remote) {\n+\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n+\t\tfor (i = 0; i < allowed_nr; i++)\n+\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * On remote, skip unsupported atoms returning an empty sb,\n+\t\t * honoring how for-each-ref handles known but inapplicable\n+\t\t * atoms (e.g. %(tagger)).\n+\t\t */\n+\t\tif (i == allowed_nr)\n+\t\t\treturn 1;\n+\t}\n+\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n \t\t\tstrbuf_add_oid_hex(sb, &data->oid);\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n-\t\tif (data->mark_query)\n+\t\tif (data->mark_query) {\n \t\t\tdata->info.typep = &data->type;\n-\t\telse\n-\t\t\tstrbuf_addstr(sb, type_name(data->type));\n+\t\t} else {\n+\t\t\tconst char *t = type_name(data->type);\n+\t\t\tstrbuf_addstr(sb, t ? t : \"\");\n+\t\t}\n \t} else if (is_atom(\"objectsize\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.sizep = &data->size;\n@@ -707,10 +734,6 @@ static int get_remote_info(struct batch_options *opt,\n \tgtransport->smart_options->object_info = 1;\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\t/* 'objectsize' is the only option currently supported */\n-\tif (!strstr(opt->format, \"%(objectsize)\"))\n-\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n-\n \tstring_list_append(&object_info_options, \"size\");\n \n \tif (object_info_options.nr > 0) {\n@@ -840,7 +863,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n \t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tdata->is_remote = 1;\n \t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t\tdata->is_remote = 0;\n \t\t} else {\n \t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n \t\t}\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex b744e81701..9d8f114b72 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -236,6 +236,21 @@ test_expect_success 'remote-object-info does not die on missing oid like info' '\n \t)\n '\n \n+# This tests depends on %(objecttype) not being supported yet, once supported\n+# it needs to be updated.\n+test_expect_success 'unsupported placeholder on remote returns empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"\" >expect &&\n+\t\tgit cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n # Test --batch-command remote-object-info with 'git://' and\n # transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n@@ -575,10 +590,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (obje\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\techo \"$hello_oid \" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize:disk)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n@@ -587,10 +604,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (delt\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(deltabase)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n\n-- \n2.54.0\n"},{"id":"546415","messageId":"20260625-ps-eric-work-rebase-v14-13-09f7ffe21a53@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v14 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-25T12:13:35Z","receivedAt":"2026-06-25T12:13:56Z","isPatch":true,"body":"The static allow-list in expand_atom() is hardcoded to only allow\n\"objectname\" and \"objectsize\" for remote queries. This works because\nup to this point all servers will either support object-info with name\nand size or they do not support them at all, but we cannot expect that\nin a future different servers with different git versions to have the\nsame object-info capabilities. Therefore, the allow_list needs to be\ndynamic depending on what the server advertises.\n\nThe client will now:\n\n1. Request the protocol option that the placeholder refers to (i.e.\n   \"size\" when \"%(objectsize)\").\n\n2. Filters the request in fetch_object_info() dropping any option that\n   the server does not advertise.\n\n3. After the fetching, the options that haven't been dropped are the ones\n   fetched and supported by the server, these supported options are\n   mapped and remote_allowed_atoms is populated with the placeholders.\n\n4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n   the static allow_list had.\n\nMove object_info_options out of get_remote_info so the caller which has\ndata can select what options will be requested instead of requesting\nalways size.\nMove batch_object_write() out so there will always be an output even if\nall the placeholders are not supported by the server (returns an empty\nline).\n\nInclude \"type\" in the object_info_options so once the server supports\nit, the clients know already how to request it.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c  | 97 +++++++++++++++++++++++++++++++++++------------------\n fetch-object-info.c | 20 +++++++++++\n 2 files changed, 84 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex ea3869f3ed..86e4f78eb6 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -338,13 +338,11 @@ struct expand_data {\n \t * Flags about when an object info is being fetched from remote.\n \t */\n \tunsigned is_remote:1;\n-};\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n \n-static const char *remote_object_info_atoms[] = {\n-\t\"objectname\",\n-\t\"objectsize\",\n+\tstruct string_list remote_allowed_atoms;\n };\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -356,17 +354,11 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n \tif (data->is_remote) {\n-\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n-\t\tfor (i = 0; i < allowed_nr; i++)\n-\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\tsize_t i;\n+\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n \t\t\t\tbreak;\n-\n-\t\t/*\n-\t\t * On remote, skip unsupported atoms returning an empty sb,\n-\t\t * honoring how for-each-ref handles known but inapplicable\n-\t\t * atoms (e.g. %(tagger)).\n-\t\t */\n-\t\tif (i == allowed_nr)\n+\t\tif (i == data->remote_allowed_atoms.nr)\n \t\t\treturn 1;\n \t}\n \n@@ -680,12 +672,12 @@ static int get_remote_info(struct batch_options *opt,\n \t\t\t   int argc,\n \t\t\t   const char **argv,\n \t\t\t   struct object_info **remote_object_info,\n-\t\t\t   struct oid_array *object_info_oids)\n+\t\t\t   struct oid_array *object_info_oids,\n+\t\t\t   struct string_list *object_info_options)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n \tstruct object_id oid;\n-\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tstruct transport *gtransport;\n \n \t/*\n@@ -734,15 +726,12 @@ static int get_remote_info(struct batch_options *opt,\n \tgtransport->smart_options->object_info = 1;\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\tstring_list_append(&object_info_options, \"size\");\n-\n-\tif (object_info_options.nr > 0) {\n-\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tif (object_info_options->nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = object_info_options;\n \t\tgtransport->smart_options->object_info_data = *remote_object_info;\n \t\tretval = transport_fetch_refs(gtransport, NULL);\n \t}\n cleanup:\n-\tstring_list_clear(&object_info_options, 0);\n \ttransport_disconnect(gtransport);\n \treturn retval;\n }\n@@ -828,6 +817,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+struct protocol_placeholder_entry {\n+\tconst char *option;\n+\tconst char *atom;\n+};\n+\n+static const struct protocol_placeholder_entry remote_atom_map[] = {\n+\t{\"size\", \"objectsize\"},\n+\t{\"type\", \"objecttype\"},\n+\t/*\n+\t * Add new protocol options here. Even if the server doesn't support\n+\t * them the allow_list will drop them if the server doesn't advertise\n+\t * them.\n+\t */\n+};\n+\n static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n@@ -837,6 +841,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct object_info *remote_object_info = NULL;\n \tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \n \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n \t\tdie(_(\"remote-object-info command too long\"));\n@@ -849,32 +854,57 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n+\tif (data->info.sizep)\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\tif (data->info.typep)\n+\t\tstring_list_append(&object_info_options, \"type\");\n+\n \tif (get_remote_info(opt, count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids))\n+\t\t\t    &object_info_oids, &object_info_options))\n \t\tgoto cleanup;\n \n+\tstring_list_clear(&data->remote_allowed_atoms, 0);\n+\tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n+\tfor (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n+\t\tif (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n+\t\t\tstring_list_append(&data->remote_allowed_atoms,\n+\t\t\t\t\t   remote_atom_map[i].atom);\n+\n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tint found = 0;\n \t\tdata->oid = object_info_oids.oid[i];\n+\t\t/*\n+\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t * information from server without downloading them.\n+\t\t */\n \t\tif (remote_object_info[i].sizep) {\n-\t\t\t/*\n-\t\t\t * When reaching here, it means remote-object-info can retrieve\n-\t\t\t * information from server without downloading them.\n-\t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n-\t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tdata->is_remote = 1;\n-\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n-\t\t\tdata->is_remote = 0;\n-\t\t} else {\n-\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t\tfound = 1;\n \t\t}\n+\n+\t\tif (remote_object_info[i].typep) {\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\t\t\tfound = 1;\n+\t\t}\n+\n+\t\tif (!found && object_info_options.nr > 0) {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid),\n+\t\t\t\t\t     &data->oid, \"missing\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tdata->is_remote = 1;\n+\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n \n cleanup:\n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n+\tstring_list_clear(&object_info_options, 0);\n \tfree(line_to_split);\n \tfree(argv);\n \tfree(remote_object_info);\n@@ -1190,6 +1220,7 @@ static int batch_objects(struct batch_options *opt)\n  cleanup:\n \tstrbuf_release(&input);\n \tstrbuf_release(&output);\n+\tstring_list_clear(&data.remote_allowed_atoms, 0);\n \tcfg->warn_on_object_refname_ambiguity = save_warning;\n \treturn retval;\n }\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 9c4ae9bd11..c6954bde5d 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -39,6 +39,26 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \tcase protocol_v2:\n \t\tif (!server_supports_v2(\"object-info\"))\n \t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\t/*\n+\t\t * When removing an element from the list it gets swapped by the\n+\t\t * last element, iterate backwards to prevent elements skipping\n+\t\t * evaluation.\n+\t\t *\n+\t\t * object_info_options->nr can be safely casted without overflow\n+\t\t * beacuse the number of options is a small known number (the\n+\t\t * supported placeholders which currently are size and type).\n+\t\t */\n+\t\tfor (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)\n+\t\t\tif (!server_supports_feature(\"object-info\",\n+\t\t\t\t\t\t     args->object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(args->object_info_options, i, 0);\n+\t\t/*\n+\t\t * If no options are left after the filtering, avoid unnecessary\n+\t\t * request to the server.\n+\t\t */\n+\t\tif (!args->object_info_options->nr)\n+\t\t\treturn 0;\n+\n \t\tsend_object_info_request(fd_out, args);\n \t\tbreak;\n \tcase protocol_v1:\n\n-- \n2.54.0\n"},{"id":"546435","messageId":"xmqqjyrme393.fsf@gitster.g","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-2-09f7ffe21a53@gmail.com","subject":"Re: [PATCH GSoC v14 02/13] git-compat-util: add strtoul_szt() with error handling","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-25T21:09:28Z","receivedAt":"2026-06-25T21:09:31Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> From: Eric Ju <eric.peijian@gmail.com>\n>\n> We already have strtoul_ui() and similar functions that provide proper\n> error handling using strtoul from the standard library. However,\n> there isn't currently a variant that returns an unsigned long.\n\nBut this one no longer returns an unsigned long anymore ;-)\n\n> This variant is needed in a subsequent commit to enable returning an\n> size_t with proper error handling.\n\nI think it would allow a lot of code paths that want to deal with\nsize_t not to worry about \"is ulong large enough?\" to have a\nfunction like this, but for that to happen, the implementation of\nthe function must carefully think through if these steps do sensible\nthings on platforms with too small ulong (which often is OK when we\nare coming from decimal string to ulong and then to size_t) and too\nlarge ulong (which is not OK, when coming from decimal string to\nulong which might be fine, but will bust the size of the final\ntype), etc.\n\nAlso, would it make sense to add yet another \"static inline\" like\nthis?  After the dust settles, we may want to rethink these strtoX\nwrappers we have, benchmark, and possibly make them into a proper\nlibrary function, not \"static inline\" that may bloat the runtime.\n\n> diff --git a/git-compat-util.h b/git-compat-util.h\n> index 8809776407..7f417f1acf 100644\n> --- a/git-compat-util.h\n> +++ b/git-compat-util.h\n> @@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n>  \treturn 0;\n>  }\n>  \n> +/*\n> + * Convert a string to a size_t using the standard library's strtoul, with\n> + * additional error handling to ensure robustness.\n> + */\n> +static inline int strtoul_szt(char const *s, int base, size_t *result)\n> +{\n> +\tunsigned long ul;\n> +\tchar *p;\n> +\n> +\terrno = 0;\n> +\t/* negative values would be accepted by strtoul */\n> +\tif (strchr(s, '-'))\n> +\t\treturn -1;\n> +\tul = strtoul(s, &p, base);\n> +\tif (errno || *p || p == s)\n> +\t\treturn -1;\n> +\t*result = ul;\n> +\treturn 0;\n> +}\n> +\n>  static inline int strtol_i(char const *s, int base, int *result)\n>  {\n>  \tlong ul;\n"},{"id":"546454","messageId":"CAN5EUNStfvgBCpwokfooa9MY3ZGSf9f=xok+aeKXKK9Zv8uSgw@mail.gmail.com","threadId":"61695","inReplyTo":"xmqqjyrme393.fsf@gitster.g","subject":"Re: [PATCH GSoC v14 02/13] git-compat-util: add strtoul_szt() with error handling","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-26T12:00:01Z","receivedAt":"2026-06-26T12:00:16Z","isPatch":true,"body":"El jue, 25 jun 2026 a las 23:09, Junio C Hamano (<gitster@pobox.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> > From: Eric Ju <eric.peijian@gmail.com>\n> >\n> > We already have strtoul_ui() and similar functions that provide proper\n> > error handling using strtoul from the standard library. However,\n> > there isn't currently a variant that returns an unsigned long.\n>\n> But this one no longer returns an unsigned long anymore ;-)\n\nTrue, I missed that.\n\n>\n> > This variant is needed in a subsequent commit to enable returning an\n> > size_t with proper error handling.\n>\n> I think it would allow a lot of code paths that want to deal with\n> size_t not to worry about \"is ulong large enough?\" to have a\n> function like this, but for that to happen, the implementation of\n> the function must carefully think through if these steps do sensible\n> things on platforms with too small ulong (which often is OK when we\n> are coming from decimal string to ulong and then to size_t) and too\n> large ulong (which is not OK, when coming from decimal string to\n> ulong which might be fine, but will bust the size of the final\n> type), etc.\n\nOk, so the strtoul_szt() is not a bad idea but it is not ok how I did\nit. What about using uintmax_t and strtoumax() and after that (before\ncasting) check if it fits into a size_t?\nSomething like:\n\nstatic inline int strtoumax_szt(char const *s, int base, size_t *result)\n{\n     uintmax_t val;\n     char *p;\n\n     errno = 0;\n     /* negative values would be accepted by strtoul */\n     if (strchr(s, '-'))\n             return -1;\n     val = strtoumax(s, &p, base);\n     if ((errno || *p || p == s) || val > SIZE_MAX)\n             return -1;\n     *result = val;\n     return 0;\n}\n\nAlternatively I could go back to the unsigned long version and make\nthe relevant checks on the caller which is only one place at\n`fetch_object_info()`\n\n>\n> Also, would it make sense to add yet another \"static inline\" like\n> this?  After the dust settles, we may want to rethink these strtoX\n> wrappers we have, benchmark, and possibly make them into a proper\n> library function, not \"static inline\" that may bloat the runtime.\n\nYeah, I thought the same, I kept it on the header also to avoid\nbloating this series too much because it's kinda big already. But a\nfollow-up after would be a good idea tho.\n\n>\n> > diff --git a/git-compat-util.h b/git-compat-util.h\n> > index 8809776407..7f417f1acf 100644\n> > --- a/git-compat-util.h\n> > +++ b/git-compat-util.h\n> > @@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n> >       return 0;\n> >  }\n> >\n> > +/*\n> > + * Convert a string to a size_t using the standard library's strtoul, with\n> > + * additional error handling to ensure robustness.\n> > + */\n> > +static inline int strtoul_szt(char const *s, int base, size_t *result)\n> > +{\n> > +     unsigned long ul;\n> > +     char *p;\n> > +\n> > +     errno = 0;\n> > +     /* negative values would be accepted by strtoul */\n> > +     if (strchr(s, '-'))\n> > +             return -1;\n> > +     ul = strtoul(s, &p, base);\n> > +     if (errno || *p || p == s)\n> > +             return -1;\n> > +     *result = ul;\n> > +     return 0;\n> > +}\n> > +\n> >  static inline int strtol_i(char const *s, int base, int *result)\n> >  {\n> >       long ul;\n\nThanks for the review,\nPablo.\n"},{"id":"546457","messageId":"CA+J6zkRm_F4MQ2K8Ayv8PGJOx+pNAg73+p-4VdOgkxeuKAkKew@mail.gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-6-09f7ffe21a53@gmail.com","subject":"Re: [PATCH GSoC v14 06/13] fetch-pack: move function to connect.c","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-26T12:14:07Z","receivedAt":"2026-06-26T12:14:36Z","isPatch":true,"body":"On Thu, 25 Jun 2026 at 17:43, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> write_fetch_command_and_capabilities will be refactored in a subsequent\n\nNit: The paragraph below and the preceding patches refer to this function\nas `write_fetch_command_and_capabilities()`. It will be nice to maintain\nconsistency throughout this series.\n\n> commit where it will become a more general-purpose function, making it\n> more accessible to additional commands in the future.\n>\n> Move `write_fetch_command_and_capabilities()` to `connect.c`, where\n> there are similar purpose functions.\n>\n> Because string_list is only used as a pointer, use a forward\n> declaration [1].\n>\n> [1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n[snip]\n"},{"id":"546458","messageId":"CA+J6zkQtrKqQwZpR9hmyn9-uBt8RoUbv_H7g-2LnzRUDkrHY6A@mail.gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-5-09f7ffe21a53@gmail.com","subject":"Re: [PATCH GSoC v14 05/13] fetch-pack: prepare function to be moved","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-06-26T12:14:49Z","receivedAt":"2026-06-26T12:15:16Z","isPatch":true,"body":"On Thu, 25 Jun 2026 at 17:43, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> `write_fetch_command_and_capabilities()` will be refactored and moved in\n> subsequent commits where it will become a more general-purpose function,\n> making it more accessible to additional commands in the future.\n>\n> To move `write_fetch_command_and_capabilities()` to `connect.c`, we\n> previously need to adjust how `advertise_sid` is managed. Currently in\n> `fetch_pack.c`, `advertise_sid` is a static variable, modified using\n> `repo_config_get_bool()`.\n>\n> Initialize `advertise_sid` at the begining by directly using\n> `repo_config_get_bool()`. This change is safe because:\n>\n> In the original `fetch-pack.c` code, there are only two places that write\n> `advertise_sid`:\n>\n> 1. In function `do_fetch_pack()`:\n>         if (!server_supports(\"session_id\"))\n>                advertise_sid = 0;\n> 2. In function `fetch_pack_config()`:\n>         repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n>\n> About 1, since `do_fetch_pack()` is only relevant for protocol v1, this\n> assignment can be ignored, as `write_fetch_command_and_capabilities()`\n> is only used in v2.\n>\n> About 2, `repo_config_get_bool()` is from `config.h` and it's an\n> out-of-box dependency of `connect.c`, so we can reuse it directly.\n\nNit: This only explains the `advertise_sid` change in this patch. We should\nalso add a few lines explaining the `hash_algo` change. Maybe something\nlike:\n\nWhile at it, change `hash_algo`'s type to `hash_algo_by_name()`'s actual\nreturn type (`unsigned int`) and make it `const`.\n\n\n\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  fetch-pack.c | 5 ++++-\n>  1 file changed, 4 insertions(+), 1 deletion(-)\n>\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index f13951d154..ad07603755 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1380,6 +1380,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>                                                  const struct string_list *server_options)\n>  {\n>         const char *hash_name;\n> +       int advertise_sid;\n> +\n> +       repo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>\n>         ensure_server_supports_v2(\"fetch\");\n>         packet_buf_write(req_buf, \"command=fetch\");\n> @@ -1395,7 +1398,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>         }\n>\n>         if (server_feature_v2(\"object-format\", &hash_name)) {\n> -               int hash_algo = hash_algo_by_name(hash_name);\n> +               const unsigned int hash_algo = hash_algo_by_name(hash_name);\n>                 if (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>                         die(_(\"mismatched algorithms: client %s; server %s\"),\n>                             the_hash_algo->name, hash_name);\n>\n> --\n> 2.54.0\n"},{"id":"546476","messageId":"CAOLa=ZRR8w2Uaxive8zYyb6fk46rnLuTb-91d2yf=5=Nijzc8g@mail.gmail.com","threadId":"61695","inReplyTo":"xmqqpl1fhesc.fsf@gitster.g","subject":"Re: [PATCH GSoC RFC v13 06/12] connect: refactor packet writing","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-26T14:39:01Z","receivedAt":"2026-06-26T14:39:02Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>>> +/*\n>>> + * Writes a command along with the requested server capabilities/features into a\n>>> + * request buffer.\n>>> + */\n>>>  struct string_list;\n>>\n>> The comment should be above the function and not the forward\n>> declaration.\n>>\n>> While we're here, why not `#include \"string-list.h\"` and remove the\n>> forward declaration, is there a circular dependency?\n>\n> Isn't it to avoid unnecessary include?  When the header itself only\n> needs to know about the presence of the type, and not the concrete\n> shape of the type (e.g., because it only uses a pointer to that\n> type), it may be overkill to include the entire header file.\n\nIndeed that seems to be it. Makes sense to me.\n"},{"id":"546489","messageId":"CAOLa=ZTzj4_awdE0P4eU_oBd6Laq4CO=dvLJZ2F42CSsGo3pPQ@mail.gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-2-09f7ffe21a53@gmail.com","subject":"Re: [PATCH GSoC v14 02/13] git-compat-util: add strtoul_szt() with error handling","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-26T16:43:48Z","receivedAt":"2026-06-26T16:43:50Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> From: Eric Ju <eric.peijian@gmail.com>\n>\n> We already have strtoul_ui() and similar functions that provide proper\n> error handling using strtoul from the standard library. However,\n> there isn't currently a variant that returns an unsigned long.\n>\n\nShouldn't this say returns a 'size_t'?\n\n> This variant is needed in a subsequent commit to enable returning an\n> size_t with proper error handling.\n>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  git-compat-util.h | 20 ++++++++++++++++++++\n>  1 file changed, 20 insertions(+)\n>\n> diff --git a/git-compat-util.h b/git-compat-util.h\n> index 8809776407..7f417f1acf 100644\n> --- a/git-compat-util.h\n> +++ b/git-compat-util.h\n> @@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n>  \treturn 0;\n>  }\n>\n> +/*\n> + * Convert a string to a size_t using the standard library's strtoul, with\n> + * additional error handling to ensure robustness.\n> + */\n> +static inline int strtoul_szt(char const *s, int base, size_t *result)\n> +{\n> +\tunsigned long ul;\n> +\tchar *p;\n> +\n> +\terrno = 0;\n> +\t/* negative values would be accepted by strtoul */\n> +\tif (strchr(s, '-'))\n> +\t\treturn -1;\n> +\tul = strtoul(s, &p, base);\n> +\tif (errno || *p || p == s)\n> +\t\treturn -1;\n> +\t*result = ul;\n\nThis converts unsigned long to size_t, but that is fine.\n\nLooks good.\n\n> +\treturn 0;\n> +}\n> +\n>  static inline int strtol_i(char const *s, int base, int *result)\n>  {\n>  \tlong ul;\n>\n> --\n> 2.54.0\n"},{"id":"546492","messageId":"CAOLa=ZScS3Gmm5BAgJF69phpaDXGnP_j9jx+bMhn_tfF65RXEg@mail.gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-5-09f7ffe21a53@gmail.com","subject":"Re: [PATCH GSoC v14 05/13] fetch-pack: prepare function to be moved","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-26T16:54:55Z","receivedAt":"2026-06-26T16:54:57Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\nThe subject doesn't really give much insight into what the patch does.\nPerhaps something like:\n\n    fetch-pack: use repo config in `write_fetch_command_and_capabilities()`\n    fetch-pack: drop static variable use in\n`write_fetch_command_and_capabilities()`\n\n> `write_fetch_command_and_capabilities()` will be refactored and moved in\n> subsequent commits where it will become a more general-purpose function,\n> making it more accessible to additional commands in the future.\n>\n> To move `write_fetch_command_and_capabilities()` to `connect.c`, we\n> previously need to adjust how `advertise_sid` is managed. Currently in\n\nI don't think 'previously' makes sense here.\n\n> `fetch_pack.c`, `advertise_sid` is a static variable, modified using\n> `repo_config_get_bool()`.\n>\n\nPerhaps:\n\n    To move `write_fetch_command_and_capabilities()` to `connect.c`,\n    drop the usage of file static variable `advertise_sid` within the\n    function. Currently, `advertise_sid` is modified...\n\n>\n> Initialize `advertise_sid` at the begining by directly using\n> `repo_config_get_bool()`. This change is safe because:\n>\n> In the original `fetch-pack.c` code, there are only two places that write\n> `advertise_sid`:\n>\n\nThis needs to be modified no? This is from the prev patch, where we\nmoved and refactored in the same patch, this no longer is the case.\n\n> 1. In function `do_fetch_pack()`:\n>         if (!server_supports(\"session_id\"))\n>                advertise_sid = 0;\n> 2. In function `fetch_pack_config()`:\n>         repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n>\n> About 1, since `do_fetch_pack()` is only relevant for protocol v1, this\n> assignment can be ignored, as `write_fetch_command_and_capabilities()`\n> is only used in v2.\n>\n> About 2, `repo_config_get_bool()` is from `config.h` and it's an\n> out-of-box dependency of `connect.c`, so we can reuse it directly.\n>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  fetch-pack.c | 5 ++++-\n>  1 file changed, 4 insertions(+), 1 deletion(-)\n>\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index f13951d154..ad07603755 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1380,6 +1380,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>  \t\t\t\t\t\t const struct string_list *server_options)\n>  {\n>  \tconst char *hash_name;\n> +\tint advertise_sid;\n> +\n> +\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>\n>  \tensure_server_supports_v2(\"fetch\");\n>  \tpacket_buf_write(req_buf, \"command=fetch\");\n> @@ -1395,7 +1398,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>  \t}\n>\n>  \tif (server_feature_v2(\"object-format\", &hash_name)) {\n> -\t\tint hash_algo = hash_algo_by_name(hash_name);\n> +\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n>\n\nAgreed with Chandra, this needs to be assessed.\n\n>  \t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>  \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n>  \t\t\t    the_hash_algo->name, hash_name);\n>\n> --\n> 2.54.0\n"},{"id":"546493","messageId":"CAOLa=ZRhCXBQN7CqwLyE2F9u+oqAsqvcFP+fuiyw4SVaSDfT6Q@mail.gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-6-09f7ffe21a53@gmail.com","subject":"Re: [PATCH GSoC v14 06/13] fetch-pack: move function to connect.c","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-26T16:57:22Z","receivedAt":"2026-06-26T16:57:25Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\nNit: could we mention the function in the subject?\n\n> write_fetch_command_and_capabilities will be refactored in a\n> subsequent\n\nSuper Nit: Some parts of your patches use backticks for quoting code or\nfilenames and others skip the convention. It would be nice to be\nconsistent.\n\n> commit where it will become a more general-purpose function, making it\n> more accessible to additional commands in the future.\n>\n> Move `write_fetch_command_and_capabilities()` to `connect.c`, where\n> there are similar purpose functions.\n>\n> Because string_list is only used as a pointer, use a forward\n> declaration [1].\n>\n> [1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  connect.c    | 34 ++++++++++++++++++++++++++++++++++\n>  connect.h    |  4 ++++\n>  fetch-pack.c | 34 ----------------------------------\n>  3 files changed, 38 insertions(+), 34 deletions(-)\n>\n> diff --git a/connect.c b/connect.c\n> index 47e39d2a73..1dced8e632 100644\n> --- a/connect.c\n> +++ b/connect.c\n> @@ -700,6 +700,40 @@ int server_supports(const char *feature)\n>  \treturn !!server_feature_value(feature, NULL);\n>  }\n>\n> +void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> +\t\t\t\t\t  const struct string_list *server_options)\n> +{\n> +\tconst char *hash_name;\n> +\tint advertise_sid;\n> +\n> +\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n> +\n> +\tensure_server_supports_v2(\"fetch\");\n> +\tpacket_buf_write(req_buf, \"command=fetch\");\n> +\tif (server_supports_v2(\"agent\"))\n> +\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n> +\tif (advertise_sid && server_supports_v2(\"session-id\"))\n> +\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n> +\tif (server_options && server_options->nr) {\n> +\t\tensure_server_supports_v2(\"server-option\");\n> +\t\tfor (size_t i = 0; i < server_options->nr; i++)\n> +\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n> +\t\t\t\t\t server_options->items[i].string);\n> +\t}\n> +\n> +\tif (server_feature_v2(\"object-format\", &hash_name)) {\n> +\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n> +\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n> +\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n> +\t\t\t    the_hash_algo->name, hash_name);\n> +\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n> +\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n> +\t\tdie(_(\"the server does not support algorithm '%s'\"),\n> +\t\t    the_hash_algo->name);\n> +\t}\n> +\tpacket_buf_delim(req_buf);\n> +}\n> +\n>  static const char *url_scheme_name(enum url_scheme scheme)\n>  {\n>  \tswitch (scheme) {\n> diff --git a/connect.h b/connect.h\n> index aa482a37fb..c4f6ea4b0a 100644\n> --- a/connect.h\n> +++ b/connect.h\n> @@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n>  \t\t\t       struct packet_reader *reader,\n>  \t\t\t       const char *error);\n>\n> +struct string_list;\n> +void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> +\t\t\t\t\t  const struct string_list *server_options);\n> +\n>  #endif\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index ad07603755..4a8a70b5f3 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1376,40 +1376,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n>  \treturn haves_added;\n>  }\n>\n> -static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> -\t\t\t\t\t\t const struct string_list *server_options)\n> -{\n> -\tconst char *hash_name;\n> -\tint advertise_sid;\n> -\n> -\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n> -\n> -\tensure_server_supports_v2(\"fetch\");\n> -\tpacket_buf_write(req_buf, \"command=fetch\");\n> -\tif (server_supports_v2(\"agent\"))\n> -\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n> -\tif (advertise_sid && server_supports_v2(\"session-id\"))\n> -\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n> -\tif (server_options && server_options->nr) {\n> -\t\tensure_server_supports_v2(\"server-option\");\n> -\t\tfor (size_t i = 0; i < server_options->nr; i++)\n> -\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n> -\t\t\t\t\t server_options->items[i].string);\n> -\t}\n> -\n> -\tif (server_feature_v2(\"object-format\", &hash_name)) {\n> -\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n> -\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n> -\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n> -\t\t\t    the_hash_algo->name, hash_name);\n> -\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n> -\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n> -\t\tdie(_(\"the server does not support algorithm '%s'\"),\n> -\t\t    the_hash_algo->name);\n> -\t}\n> -\tpacket_buf_delim(req_buf);\n> -}\n> -\n>  static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n>  \t\t\t      struct fetch_pack_args *args,\n>  \t\t\t      const struct ref *wants, struct oidset *common,\n>\n> --\n> 2.54.0\n"},{"id":"546494","messageId":"CAOLa=ZTdcg47nmZs2t1FvyOgG9S4Ap3RaK+C0Dhku6cG+wj_Kw@mail.gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-7-09f7ffe21a53@gmail.com","subject":"Re: [PATCH GSoC v14 07/13] connect: refactor packet writing","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-26T17:03:49Z","receivedAt":"2026-06-26T17:03:52Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\nThe subject is bit too generic no? Maybe we can talk about the function?\nPerhaps:\n\n    connect: make `write_fetch_command_and_capabilities()` more generic\n\n> Refactor `write_fetch_command_and_capabilities()`, enabling it to serve\n> both fetch and additional commands.\n>\n> In this context, \"command\" refers to the \"operations\" supported by\n> Git's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\n> subcommand (e.g., git-fetch(1)) or a server-side operation like\n> \"object-info\" as implemented in commit a2ba162\n> (object-info: support for retrieving object info, 2021-04-20).\n>\n> Refactor the function signature to accept a command instead of the\n> hardcoded \"fetch\".\n\n[snip]\n"},{"id":"546520","messageId":"CAOLa=ZShTcHOekT+NuxMwZz-7P5i9aSWXSL6MW9_9aKkNwqsig@mail.gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-9-09f7ffe21a53@gmail.com","subject":"Re: [PATCH GSoC v14 09/13] serve: advertise object-info feature","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-26T22:23:17Z","receivedAt":"2026-06-26T22:23:18Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> From: Calvin Wan <calvinwan@google.com>\n>\n> In order for a client to know what object-info components a server can\n> provide, advertise supported object-info features. This will allow a\n> client to decide whether to query the server for object-info or fetch\n> as a fallback.\n>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  serve.c | 5 ++++-\n>  1 file changed, 4 insertions(+), 1 deletion(-)\n>\n> diff --git a/serve.c b/serve.c\n> index 49a6e39b1d..2b07d922b3 100644\n> --- a/serve.c\n> +++ b/serve.c\n> @@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n>  \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n>  }\n>\n> -static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n> +static int object_info_advertise(struct repository *r, struct strbuf *value)\n>  {\n>  \tif (advertise_object_info == -1 &&\n>  \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n> @@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n>  \t\t/* disabled by default */\n>  \t\tadvertise_object_info = 0;\n>  \t}\n> +\t/* Currently only size is supported */\n> +\tif (value && advertise_object_info)\n> +\t\tstrbuf_addstr(value, \"size\");\n\nSo is the plan that further options will be added here to value? If so,\nwhats the format we will follow?\n\n>  \treturn advertise_object_info;\n>  }\n>\n>\n> --\n> 2.54.0\n"},{"id":"546523","messageId":"CAOLa=ZT8u32YUqhnq-pMCCK8Qzx+7k-3E-+yAoM_miJ7BQjxTA@mail.gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-10-09f7ffe21a53@gmail.com","subject":"Re: [PATCH GSoC v14 10/13] transport: add client support for object-info","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-27T00:22:45Z","receivedAt":"2026-06-27T00:22:47Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> From: Calvin Wan <calvinwan@google.com>\n>\n> Sometimes, it is beneficial to retrieve information about an object\n> without downloading it entirely. The server-side logic for this\n> functionality was implemented in commit \"a2ba162cda (object-info:\n> support for retrieving object info, 2021-04-20).\" And the wire\n> format is documented at\n> https://git-scm.com/docs/protocol-v2#_object_info.\n>\n> This commit introduces client functions to interact with the server.\n>\n> Currently, the client supports requesting a list of object IDs with\n> the 'size' feature from a v2 server. If the server does not advertise\n> this feature (i.e., transfer.advertiseobjectinfo is set to false),\n> the client will return an error and exit.\n>\n> Notice that the entire request is written into req_buf before being\n> sent to the remote. This approach follows the pattern used in the\n> `send_fetch_request()` logic within fetch-pack.c.\n> Streaming the request is not addressed in this patch.\n>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  Makefile            |  1 +\n>  fetch-object-info.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  fetch-object-info.h | 22 +++++++++++++\n>  fetch-pack.c        |  3 ++\n>  fetch-pack.h        |  2 ++\n>  meson.build         |  1 +\n>  transport-helper.c  | 11 +++++--\n>  transport.c         | 28 ++++++++++++++++-\n>  transport.h         | 11 +++++++\n>  9 files changed, 166 insertions(+), 3 deletions(-)\n>\n> diff --git a/Makefile b/Makefile\n> index 1cec251f43..ec4df39a6b 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -1159,6 +1159,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n>  LIB_OBJS += exec-cmd.o\n>  LIB_OBJS += fetch-negotiator.o\n>  LIB_OBJS += fetch-pack.o\n> +LIB_OBJS += fetch-object-info.o\n>  LIB_OBJS += fmt-merge-msg.o\n>  LIB_OBJS += fsck.o\n>  LIB_OBJS += fsmonitor.o\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> new file mode 100644\n> index 0000000000..9c4ae9bd11\n> --- /dev/null\n> +++ b/fetch-object-info.c\n> @@ -0,0 +1,90 @@\n> +#include \"git-compat-util.h\"\n> +#include \"gettext.h\"\n> +#include \"hex.h\"\n> +#include \"pkt-line.h\"\n> +#include \"connect.h\"\n> +#include \"oid-array.h\"\n> +#include \"odb.h\"\n> +#include \"fetch-object-info.h\"\n> +#include \"string-list.h\"\n> +\n> +/* Sends git-cat-file object-info command and its arguments into the request buffer. */\n\nThis file doesn't know about git-cat-file(1), nor should it care about\nit, right? Since git-cat-file(1) is simply a user of this function.\nWould it make more sense to structure the document around what this\nfunction is supposed to do?\n\nAlso the comment for `fetch_object_info` seems to be similar, perhaps\nworthwhile changing both without referring to git-cat-file(1).\nTheoretically there could be other users in the future too.\n\n> +static void send_object_info_request(const int fd_out, struct object_info_args *args)\n> +{\n> +\tstruct strbuf req_buf = STRBUF_INIT;\n> +\n> +\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n> +\n> +\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n> +\t\tpacket_buf_write(&req_buf, \"size\");\n> +\n\nOkay so if the user requests 'size', we forward that to the server. But\nwhat about the 'else' condition? Should we BUG() out?\n\n> +\tif (args->oids)\n> +\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n> +\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n> +\n> +\tpacket_buf_flush(&req_buf);\n> +\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n> +\t\tdie_errno(_(\"unable to write request to remote\"));\n> +\n\nWe write out all the oids, flush and write to the fd. Okay.\n\n> +\tstrbuf_release(&req_buf);\n> +}\n> +\n> +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n> +\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n> +\t\t      const int stateless_rpc, const int fd_out)\n> +{\n> +\tint size_index = -1;\n> +\n> +\tswitch (version) {\n> +\tcase protocol_v2:\n> +\t\tif (!server_supports_v2(\"object-info\"))\n> +\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n> +\t\tsend_object_info_request(fd_out, args);\n\nSo if the server does support 'object-info', we call\n`send_object_info_request()`. Makes sense.\n\n> +\t\tbreak;\n> +\tcase protocol_v1:\n> +\tcase protocol_v0:\n> +\t\tdie(_(\"unsupported protocol version. expected v2\"));\n> +\tcase protocol_unknown_version:\n> +\t\tBUG(\"unknown protocol version\");\n> +\t}\n> +\n\nNow that we've sent the request, we should start parsing the response.\n\n> +\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n> +\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n> +\t\t\tcheck_stateless_delimiter(stateless_rpc, reader,\n> +\t\t\t\t\t\t  \"stateless delimiter expected\");\n> +\t\t\treturn -1;\n> +\t\t}\n> +\n> +\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n> +\t\t\treturn -1;\n> +\n> +\t\tif (!strcmp(reader->line, \"size\")) {\n> +\t\t\tsize_index = i;\n> +\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n> +\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n> +\t\t}\n> +\t}\n> +\n\nSo this function seems to iterate over the list of options to only find\nand store the indexes. If the server does support size we also allocate\nthe pointers to store the size.\n\nShouldn't we similarly BUG() if there is anything apart from 'size' here?\n\n> +\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n> +\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n> +\n> +\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n> +\t\tif (0 <= size_index) {\n> +\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\")) {\n> +\t\t\t\tFREE_AND_NULL(object_info_data[i].sizep);\n> +\t\t\t\tstring_list_clear(&object_info_values, 0);\n> +\t\t\t\tcontinue;\n> +\t\t\t}\n> +\t\t\tif (strtoul_szt(object_info_values.items[1 + size_index].string,\n> +\t\t\t\t       10, object_info_data[i].sizep))\n\nThis is now no longer correctly aligned.\n\n> +\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n> +\t\t\t\t    object_info_values.items[0].string,\n> +\t\t\t\t    object_info_values.items[1 + size_index].string);\n> +\t\t}\n> +\n> +\t\tstring_list_clear(&object_info_values, 0);\n> +\t}\n> +\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n> +\n\nWe parse each line and obtain the size and parse it into\n`object_info_data[i].sizep`. If the value is missing, we simply continue\nthe iteration.\n\nI think we had this discussion off the list about how this means that\noids which do not have a size will not error out but rather display a\nmissing info value.\n\nThe argument for not error'ing out was better user experience where the\ncommand would complete without exiting. I still think we should error\nout, because:\n\n1. Without error'ing out, we'd have to display to the user a missing\nvalue token. There is contention around what this token should be,\nas such a token shouldn't be a valid value for the info type being\ndisplayed. Future options must be considered here.\n\n2. What will the error code of such a situation be? Do we consider it a\nsuccess or a failure? Is there a situation where an object can have a\nmissing size?\n\n> +\treturn 0;\n> +}\n> diff --git a/fetch-object-info.h b/fetch-object-info.h\n> new file mode 100644\n> index 0000000000..d35284bd6b\n> --- /dev/null\n> +++ b/fetch-object-info.h\n> @@ -0,0 +1,22 @@\n> +#ifndef FETCH_OBJECT_INFO_H\n> +#define FETCH_OBJECT_INFO_H\n> +\n> +#include \"pkt-line.h\"\n> +#include \"protocol.h\"\n> +#include \"odb.h\"\n> +\n> +struct object_info_args {\n> +\tstruct string_list *object_info_options;\n> +\tconst struct string_list *server_options;\n> +\tstruct oid_array *oids;\n> +};\n> +\n> +/*\n> + * Sends git-cat-file object-info command into the request buf and read the\n> + * results from packets.\n> + */\n> +int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n> +\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n> +\t\t      int stateless_rpc, int fd_out);\n> +\n> +#endif /* FETCH_OBJECT_INFO_H */\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index cdebd3476f..a86c93fc52 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1742,6 +1742,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n>  \tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n>  \t\targs->deepen = 1;\n>\n> +\tif (args->object_info)\n> +\t\tstate = FETCH_SEND_REQUEST;\n> +\n>  \twhile (state != FETCH_DONE) {\n>  \t\tswitch (state) {\n>  \t\tcase FETCH_CHECK_LOCAL:\n> diff --git a/fetch-pack.h b/fetch-pack.h\n> index 6d0dec7f41..5a428f11ed 100644\n> --- a/fetch-pack.h\n> +++ b/fetch-pack.h\n> @@ -16,6 +16,7 @@ struct fetch_pack_args {\n>  \tconst struct string_list *deepen_not;\n>  \tstruct list_objects_filter_options filter_options;\n>  \tconst struct string_list *server_options;\n> +\tstruct object_info *object_info_data;\n>\n>  \t/*\n>  \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n> @@ -43,6 +44,7 @@ struct fetch_pack_args {\n>  \tunsigned reject_shallow_remote:1;\n>  \tunsigned deepen:1;\n>  \tunsigned refetch:1;\n> +\tunsigned object_info:1;\n>\n>  \t/*\n>  \t * Indicate that the remote of this request is a promisor remote. The\n> diff --git a/meson.build b/meson.build\n> index 3247697f74..145c6882eb 100644\n> --- a/meson.build\n> +++ b/meson.build\n> @@ -347,6 +347,7 @@ libgit_sources = [\n>    'exec-cmd.c',\n>    'fetch-negotiator.c',\n>    'fetch-pack.c',\n> +  'fetch-object-info.c',\n>    'fmt-merge-msg.c',\n>    'fsck.c',\n>    'fsmonitor.c',\n> diff --git a/transport-helper.c b/transport-helper.c\n> index f195070788..c77599f6fb 100644\n> --- a/transport-helper.c\n> +++ b/transport-helper.c\n> @@ -727,8 +727,8 @@ static int fetch_refs(struct transport *transport,\n>\n>  \t/*\n>  \t * If we reach here, then the server, the client, and/or the transport\n> -\t * helper does not support protocol v2. --negotiate-only requires\n> -\t * protocol v2.\n> +\t * helper does not support protocol v2. --negotiate-only and cat-file\n> +\t * remote-object-info require protocol v2.\n\nThis is not really true as of this commit. This only comes into effect\nin the next commit. So shouldn't this be added there? That said, I would\nmodify this to only talk about object-info requiring v2 and drop the\nreference to cat-file.\n\n>  \t */\n>  \tif (data->transport_options.acked_commits) {\n>  \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n> @@ -744,6 +744,13 @@ static int fetch_refs(struct transport *transport,\n>  \t\tfree_refs(dummy);\n>  \t}\n>\n> +\t/* fail the command explicitly to avoid further commands input. */\n> +\tif (transport->smart_options->object_info)\n> +\t\tdie(_(\"remote-object-info requires protocol v2\"));\n> +\n> +\tif (!data->get_refs_list_called)\n> +\t\tget_refs_list_using_list(transport, 0);\n> +\n\nDon't we already do this right above? Why is this needed again?\n\n>  \tcount = 0;\n>  \tfor (i = 0; i < nr_heads; i++)\n>  \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\n> diff --git a/transport.c b/transport.c\n> index 0f5ec30247..7d3246e12b 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -9,6 +9,7 @@\n>  #include \"hook.h\"\n>  #include \"pkt-line.h\"\n>  #include \"fetch-pack.h\"\n> +#include \"fetch-object-info.h\"\n>  #include \"remote.h\"\n>  #include \"connect.h\"\n>  #include \"send-pack.h\"\n> @@ -467,8 +468,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \targs.negotiation_restrict_tips = data->options.negotiation_restrict_tips;\n>  \targs.negotiation_include_tips = data->options.negotiation_include_tips;\n>  \targs.reject_shallow_remote = transport->smart_options->reject_shallow;\n> +\targs.object_info = transport->smart_options->object_info;\n> +\n\nHmm, so we piggy-back on top of the `fetch_refs_via_pack()` function...\n\n> +\tif (transport->smart_options->object_info\n> +\t    && transport->smart_options->object_info_oids->nr > 0) {\n> +\t\tstruct packet_reader reader;\n> +\t\tstruct object_info_args obj_info_args = { 0 };\n> +\n> +\t\tobj_info_args.server_options = transport->server_options;\n> +\t\tobj_info_args.oids = transport->smart_options->object_info_oids;\n> +\t\tobj_info_args.object_info_options = transport->smart_options->object_info_options;\n> +\t\tstring_list_sort(obj_info_args.object_info_options);\n> +\n> +\t\tconnect_setup(transport, 0);\n> +\t\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n> +\t\t\t\tPACKET_READ_CHOMP_NEWLINE |\n> +\t\t\t\tPACKET_READ_GENTLE_ON_EOF |\n> +\t\t\t\tPACKET_READ_DIE_ON_ERR_PACKET);\n> +\n> +\t\tdata->version = discover_version(&reader);\n> +\t\ttransport->hash_algo = reader.hash_algo;\n> +\n> +\t\tret = fetch_object_info(data->version, &obj_info_args, &reader,\n> +\t\t\t\t\tdata->options.object_info_data, transport->stateless_rpc,\n> +\t\t\t\t\tdata->fd[1]);\n> +\t\tgoto cleanup;\n>\n\n... and we jump to exit when we only want object info information. This\nskips the call to fetch_pack(). I'm a bit uneasy with this. Ideally we\nshould be adding a new function to the vtable to only fetch object info.\nWhile this works, this doesn't fit the contract of what this function is\nsupposed to do. See the comment around `fetch_refs` in `struct\ntransport_vtable`. Shouldn't we update that documentation at the very\nleast?\n\n> -\tif (!data->finished_handshake) {\n> +\t} else if (!data->finished_handshake) {\n>  \t\tint i;\n>  \t\tint must_list_refs = 0;\n>  \t\tfor (i = 0; i < nr_heads; i++) {\n> diff --git a/transport.h b/transport.h\n> index 7e5867cffa..bd60b10af4 100644\n> --- a/transport.h\n> +++ b/transport.h\n> @@ -6,6 +6,7 @@\n>  #include \"list-objects-filter-options.h\"\n>  #include \"string-list.h\"\n>  #include \"connect.h\"\n> +#include \"odb.h\"\n>\n>  struct git_transport_options {\n>  \tunsigned thin : 1;\n> @@ -31,6 +32,12 @@ struct git_transport_options {\n>  \t */\n>  \tunsigned connectivity_checked:1;\n>\n> +\t/*\n> +\t * Transport will attempt to retrieve only object-info.\n> +\t * If object-info is not supported, the operation will error and exit.\n> +\t */\n> +\tunsigned object_info : 1;\n> +\n\nAccording to our style, this should be `unsigned object_info:1`.\n\n>  \tint depth;\n>  \tconst char *deepen_since;\n>  \tconst struct string_list *deepen_not;\n> @@ -55,6 +62,10 @@ struct git_transport_options {\n>  \t * common commits to this oidset instead of fetching any packfiles.\n>  \t */\n>  \tstruct oidset *acked_commits;\n> +\n> +\tstruct oid_array *object_info_oids;\n> +\tstruct object_info *object_info_data;\n> +\tstruct string_list *object_info_options;\n>  };\n>\n>  enum transport_family {\n>\n> --\n> 2.54.0\n\nNit: I do think the commit message doesn't sufficiently capture the\nentirety of this patch. We do not talk about:\n\n1. How we piggy-back on top of `fetch_refs_via_pack()` and don't\nintroduce our own pointer in the vtable.\n2. The changes made to 'transport-helper.c' and why that is required.\n3. How we don't error out when there is no size value provided for an\nOID and what the implication of this is.\n"},{"id":"546533","messageId":"CAOLa=ZSCKbwckV-j+DyUqOkDkfYcW5xSCPza562mq+OJtQc7DA@mail.gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-11-09f7ffe21a53@gmail.com","subject":"Re: [PATCH GSoC v14 11/13] cat-file: add remote-object-info to batch-command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-27T13:14:57Z","receivedAt":"2026-06-27T13:14:59Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n[snip]\n\n> diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\n> index 86b9181599..aba20eb770 100644\n> --- a/Documentation/git-cat-file.adoc\n> +++ b/Documentation/git-cat-file.adoc\n> @@ -169,6 +169,13 @@ info <object>::\n>  \tPrint object info for object reference `<object>`. This corresponds to the\n>  \toutput of `--batch-check`.\n>\n> +remote-object-info <remote> <object>...::\n> +\tPrint object info for object references `<object>` at specified\n> +\t`<remote>` without downloading objects from the remote.\n> +\tRaise an error when the `object-info` capability is not supported by the remote.\n> +\tRaise an error when no object references are provided.\n> +\tThis command may be combined with `--buffer`.\n> +\n>  flush::\n>  \tUsed with `--buffer` to execute all preceding commands that were issued\n>  \tsince the beginning or since the last flush was issued. When `--buffer`\n> @@ -312,7 +319,8 @@ newline. The available atoms are:\n>  \tThe full hex representation of the object name.\n>\n>  `objecttype`::\n> -\tThe type of the object (the same as `cat-file -t` reports).\n> +\tThe type of the object (the same as `cat-file -t` reports). See\n> +\t`CAVEATS` below. Not supported by `remote-object-info`.\n>\n\nDo we have to keep adding 'Not supported by `remote-object-info`' to\neach type? Can't we do the inverse and only add 'Supported by\n`remote-object-info`' to `objectsize`. This avoid having to add this\nline to every new type.\n\n>  If no format is specified, the default format is `%(objectname)\n> -%(objecttype) %(objectsize)`.\n> +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n\nNit: I would drop the 'for now' here, since we don't know when the changes\nfor 'objecttype' will land.\n\n[snip]\n\n>  enum batch_mode {\n>  \tBATCH_MODE_CONTENTS,\n> @@ -633,6 +649,81 @@ static void batch_one_object(const char *obj_name,\n>  \tobject_context_release(&ctx);\n>  }\n>\n> +static int get_remote_info(struct batch_options *opt,\n> +\t\t\t   int argc,\n> +\t\t\t   const char **argv,\n> +\t\t\t   struct object_info **remote_object_info,\n> +\t\t\t   struct oid_array *object_info_oids)\n> +{\n> +\tint retval = 0;\n> +\tstruct remote *remote = NULL;\n> +\tstruct object_id oid;\n> +\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> +\tstruct transport *gtransport;\n> +\n> +\t/*\n> +\t * Change the format to \"%(objectname) %(objectsize)\" when\n\nNit: perhaps prepend a \"TODO\"\n\n> +\t * remote-object-info command is used. Once we start supporting objecttype\n> +\t * the default format should change to DEFAULT_FORMAT.\n> +\t */\n> +\tif (!opt->format)\n> +\t\topt->format = \"%(objectname) %(objectsize)\";\n> +\n> +\tremote = remote_get(argv[0]);\n> +\tif (!remote)\n> +\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n> +\n> +\toid_array_clear(object_info_oids);\n> +\tfor (size_t i = 1; i < argc; i++) {\n> +\t\tif (get_oid_hex(argv[i], &oid)) {\n> +\t\t\tsize_t len = strlen(argv[i]);\n> +\n> +\t\t\tif (len < the_hash_algo->hexsz && len >= 4) {\n> +\t\t\t\tsize_t j;\n> +\t\t\t\tfor (j = 0; j < len; j++)\n> +\t\t\t\t\tif (!isxdigit(argv[i][j]))\n> +\t\t\t\t\t\tbreak;\n> +\t\t\t\tif (j == len)\n> +\t\t\t\t\tdie(_(\"remote-object-info does not support \"\n> +\t\t\t\t\t      \"short oids, %d characters required\"),\n> +\t\t\t\t\t    (int)the_hash_algo->hexsz);\n> +\t\t\t}\n> +\t\t\tdie(_(\"not a valid object name '%s'\"), argv[i]);\n> +\t\t}\n> +\t\toid_array_append(object_info_oids, &oid);\n> +\t}\n> +\n> +\tif (!object_info_oids->nr)\n> +\t\tdie(_(\"remote-object-info requires objects\"));\n> +\n> +\tgtransport = transport_get(remote, NULL);\n> +\n> +\tif (!gtransport->smart_options) {\n> +\t\tretval = -1;\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n> +\tgtransport->smart_options->object_info = 1;\n> +\tgtransport->smart_options->object_info_oids = object_info_oids;\n> +\n> +\t/* 'objectsize' is the only option currently supported */\n> +\tif (!strstr(opt->format, \"%(objectsize)\"))\n> +\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n> +\n\nAren't we setting the opt->format ourselves in this function? Why do we\nneed to check it?\n\n> +\tstring_list_append(&object_info_options, \"size\");\n> +\n> +\tif (object_info_options.nr > 0) {\n> +\t\tgtransport->smart_options->object_info_options = &object_info_options;\n> +\t\tgtransport->smart_options->object_info_data = *remote_object_info;\n> +\t\tretval = transport_fetch_refs(gtransport, NULL);\n> +\t}\n> +cleanup:\n> +\tstring_list_clear(&object_info_options, 0);\n> +\ttransport_disconnect(gtransport);\n> +\treturn retval;\n> +}\n> +\n>  struct object_cb_data {\n>  \tstruct batch_options *opt;\n>  \tstruct expand_data *expand;\n> @@ -714,6 +805,57 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n>  \t\tload_mailmap();\n>  }\n>\n> +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> +\t\t\t\t\t const char *line, struct strbuf *output,\n> +\t\t\t\t\t struct expand_data *data)\n> +{\n> +\tint count;\n> +\tconst char **argv;\n> +\tchar *line_to_split;\n> +\tstruct object_info *remote_object_info = NULL;\n> +\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n> +\n> +\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n> +\t\tdie(_(\"remote-object-info command too long\"));\n> +\n> +\tline_to_split = xstrdup(line);\n> +\tcount = split_cmdline(line_to_split, &argv);\n> +\tif (count < 0)\n> +\t\tdie(_(\"split remote-object-info command\"));\n\nWe should  be using `split_cmdline_strerror()` here\n\n> +\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n> +\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n> +\t\t    MAX_ALLOWED_OBJ_LIMIT);\n> +\n> +\tif (get_remote_info(opt, count, argv, &remote_object_info,\n> +\t\t\t    &object_info_oids))\n> +\t\tgoto cleanup;\n> +\n> +\tdata->skip_object_info = 1;\n> +\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n> +\t\tdata->oid = object_info_oids.oid[i];\n> +\t\tif (remote_object_info[i].sizep) {\n> +\t\t\t/*\n> +\t\t\t * When reaching here, it means remote-object-info can retrieve\n> +\t\t\t * information from server without downloading them.\n> +\t\t\t */\n> +\t\t\tdata->size = *remote_object_info[i].sizep;\n> +\t\t\topt->batch_mode = BATCH_MODE_INFO;\n> +\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n> +\t\t} else {\n> +\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n> +\t\t}\n> +\t}\n> +\tdata->skip_object_info = 0;\n> +\n> +cleanup:\n> +\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n> +\t\tfree_object_info_contents(&remote_object_info[i]);\n> +\tfree(line_to_split);\n> +\tfree(argv);\n> +\tfree(remote_object_info);\n> +\toid_array_clear(&object_info_oids);\n> +}\n> +\n\n[snip]\n\n> diff --git a/t/meson.build b/t/meson.build\n> index 3219264fe7..54d21111a3 100644\n> --- a/t/meson.build\n> +++ b/t/meson.build\n> @@ -170,6 +170,7 @@ integration_tests = [\n>    't1014-read-tree-confusing.sh',\n>    't1015-read-index-unmerged.sh',\n>    't1016-compatObjectFormat.sh',\n> +  't1017-cat-file-remote-object-info.sh',\n>    't1020-subdirectory.sh',\n>    't1022-read-tree-partial-clone.sh',\n>    't1050-large.sh',\n\n[snip]\n"},{"id":"546556","messageId":"CAN5EUNTrdNArd5SX9df6x9bOhRfzE4c7dLOuNu7ONUdn4TLsUA@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZShTcHOekT+NuxMwZz-7P5i9aSWXSL6MW9_9aKkNwqsig@mail.gmail.com","subject":"Re: [PATCH GSoC v14 09/13] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-27T17:52:59Z","receivedAt":"2026-06-27T17:53:11Z","isPatch":true,"body":"El sáb, 27 jun 2026 a las 0:23, Karthik Nayak\n(<karthik.188@gmail.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> > From: Calvin Wan <calvinwan@google.com>\n> >\n> > In order for a client to know what object-info components a server can\n> > provide, advertise supported object-info features. This will allow a\n> > client to decide whether to query the server for object-info or fetch\n> > as a fallback.\n> >\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> > ---\n> >  serve.c | 5 ++++-\n> >  1 file changed, 4 insertions(+), 1 deletion(-)\n> >\n> > diff --git a/serve.c b/serve.c\n> > index 49a6e39b1d..2b07d922b3 100644\n> > --- a/serve.c\n> > +++ b/serve.c\n> > @@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n> >       trace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n> >  }\n> >\n> > -static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n> > +static int object_info_advertise(struct repository *r, struct strbuf *value)\n> >  {\n> >       if (advertise_object_info == -1 &&\n> >           repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n> > @@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n> >               /* disabled by default */\n> >               advertise_object_info = 0;\n> >       }\n> > +     /* Currently only size is supported */\n> > +     if (value && advertise_object_info)\n> > +             strbuf_addstr(value, \"size\");\n>\n> So is the plan that further options will be added here to value? If so,\n> whats the format we will follow?\n\nHi!\nThe current documented format is at `gitprotocol-v2.adoc`, however I\nthink it could be improved. I have a more complete version in the\nnot-yet-sent %(objecttype) support series, but since the question\ncomes up here, I will update the format documentation in this series\nfor size only:\n\noid <oid>\n  Indicates to the server an object which the client wants to obtain\n- information for.\n+ information for. They must be full object IDs.\n\n- info = PKT-LINE(attrs) LF)\n+ info = PKT-LINE(attrs LF)\n       *PKT-LINE(obj-info LF)\n\n  attrs = attr | attrs SP attrs\n\n+ obj-size = 1*DIGIT\n+\n  attr = \"size\"\n\n- obj-info = obj-id SP obj-size\n+ obj-info = obj-id SP [obj-size]\n+\n+If the server does not recognize the object id, the response will be\n+`obj-id SP` regardless of the number of attributes requested.\n\nAbout the names `size` and future ones `type` they are arbitrarily\nchosen, so for example: `delta:base` could be `delta`. They are\nappended to the buffer so in case of adding `type`, it would look\nlike:\n\nstrbuf_addstr(value, \"size type\");\n\nWhat do you think?\n\nThanks for the review,\nPablo.\n\n>\n> >       return advertise_object_info;\n> >  }\n> >\n> >\n> > --\n> > 2.54.0\n"},{"id":"546562","messageId":"CAN5EUNQaEvJa_ro+wYwHjGYttCDB+kJKMoO6ypGAH7QjdSea-w@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZT8u32YUqhnq-pMCCK8Qzx+7k-3E-+yAoM_miJ7BQjxTA@mail.gmail.com","subject":"Re: [PATCH GSoC v14 10/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-27T19:44:42Z","receivedAt":"2026-06-27T19:44:54Z","isPatch":true,"body":"El sáb, 27 jun 2026 a las 2:22, Karthik Nayak\n(<karthik.188@gmail.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> > From: Calvin Wan <calvinwan@google.com>\n> >\n> > Sometimes, it is beneficial to retrieve information about an object\n> > without downloading it entirely. The server-side logic for this\n> > functionality was implemented in commit \"a2ba162cda (object-info:\n> > support for retrieving object info, 2021-04-20).\" And the wire\n> > format is documented at\n> > https://git-scm.com/docs/protocol-v2#_object_info.\n> >\n> > This commit introduces client functions to interact with the server.\n> >\n> > Currently, the client supports requesting a list of object IDs with\n> > the 'size' feature from a v2 server. If the server does not advertise\n> > this feature (i.e., transfer.advertiseobjectinfo is set to false),\n> > the client will return an error and exit.\n> >\n> > Notice that the entire request is written into req_buf before being\n> > sent to the remote. This approach follows the pattern used in the\n> > `send_fetch_request()` logic within fetch-pack.c.\n> > Streaming the request is not addressed in this patch.\n> >\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> > ---\n> >  Makefile            |  1 +\n> >  fetch-object-info.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n> >  fetch-object-info.h | 22 +++++++++++++\n> >  fetch-pack.c        |  3 ++\n> >  fetch-pack.h        |  2 ++\n> >  meson.build         |  1 +\n> >  transport-helper.c  | 11 +++++--\n> >  transport.c         | 28 ++++++++++++++++-\n> >  transport.h         | 11 +++++++\n> >  9 files changed, 166 insertions(+), 3 deletions(-)\n> >\n> > diff --git a/Makefile b/Makefile\n> > index 1cec251f43..ec4df39a6b 100644\n> > --- a/Makefile\n> > +++ b/Makefile\n> > @@ -1159,6 +1159,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n> >  LIB_OBJS += exec-cmd.o\n> >  LIB_OBJS += fetch-negotiator.o\n> >  LIB_OBJS += fetch-pack.o\n> > +LIB_OBJS += fetch-object-info.o\n> >  LIB_OBJS += fmt-merge-msg.o\n> >  LIB_OBJS += fsck.o\n> >  LIB_OBJS += fsmonitor.o\n> > diff --git a/fetch-object-info.c b/fetch-object-info.c\n> > new file mode 100644\n> > index 0000000000..9c4ae9bd11\n> > --- /dev/null\n> > +++ b/fetch-object-info.c\n> > @@ -0,0 +1,90 @@\n> > +#include \"git-compat-util.h\"\n> > +#include \"gettext.h\"\n> > +#include \"hex.h\"\n> > +#include \"pkt-line.h\"\n> > +#include \"connect.h\"\n> > +#include \"oid-array.h\"\n> > +#include \"odb.h\"\n> > +#include \"fetch-object-info.h\"\n> > +#include \"string-list.h\"\n> > +\n> > +/* Sends git-cat-file object-info command and its arguments into the request buffer. */\n>\n> This file doesn't know about git-cat-file(1), nor should it care about\n> it, right? Since git-cat-file(1) is simply a user of this function.\n> Would it make more sense to structure the document around what this\n> function is supposed to do?\n>\n> Also the comment for `fetch_object_info` seems to be similar, perhaps\n> worthwhile changing both without referring to git-cat-file(1).\n> Theoretically there could be other users in the future too.\n\n`git cat-file` is currently the only consumer of `object-info` but you\nare right, this file shouldn't know about it nor mention it in a\ncomment, I'll drop it.\n\n>\n> > +static void send_object_info_request(const int fd_out, struct object_info_args *args)\n> > +{\n> > +     struct strbuf req_buf = STRBUF_INIT;\n> > +\n> > +     write_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n> > +\n> > +     if (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n> > +             packet_buf_write(&req_buf, \"size\");\n> > +\n>\n> Okay so if the user requests 'size', we forward that to the server. But\n> what about the 'else' condition? Should we BUG() out?\n\nIn patch [13/13] the options are built dynamically  and filtered\nagainst what the server advertises, so by the time we reach here only\nvalid options remain. For this commit there is no client support yet\nso there is no way of this to fail and in the next commit [11/13]:\n\nget_remote_info()\n[snip]\n        string_list_append(&object_info_options, \"size\");\n\n        if (object_info_options.nr > 0) {\n                gtransport->smart_options->object_info_options =\n&object_info_options;\n                gtransport->smart_options->object_info_data =\n*remote_object_info;\n                retval = transport_fetch_refs(gtransport, NULL);\n        }\n[snip]\n\nget_remote_info() appends \"size\" always to object_info_options and\nthis is changed in commit [12/13] with the allow-list where only asked\nobject-info is appended. I could move that check to this commit as if\nsize always gets appended the `if` below will always be true.\n\nAlso because size is always appended there can't be an else condition\nto BUG() out.\n>\n> > +     if (args->oids)\n> > +             for (size_t i = 0; i < args->oids->nr; i++)\n> > +                     packet_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n> > +\n> > +     packet_buf_flush(&req_buf);\n> > +     if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n> > +             die_errno(_(\"unable to write request to remote\"));\n> > +\n>\n> We write out all the oids, flush and write to the fd. Okay.\n>\n> > +     strbuf_release(&req_buf);\n> > +}\n> > +\n> > +int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n> > +                   struct packet_reader *reader, struct object_info *object_info_data,\n> > +                   const int stateless_rpc, const int fd_out)\n> > +{\n> > +     int size_index = -1;\n> > +\n> > +     switch (version) {\n> > +     case protocol_v2:\n> > +             if (!server_supports_v2(\"object-info\"))\n> > +                     die(_(\"object-info capability is not enabled on the server\"));\n> > +             send_object_info_request(fd_out, args);\n>\n> So if the server does support 'object-info', we call\n> `send_object_info_request()`. Makes sense.\n>\n> > +             break;\n> > +     case protocol_v1:\n> > +     case protocol_v0:\n> > +             die(_(\"unsupported protocol version. expected v2\"));\n> > +     case protocol_unknown_version:\n> > +             BUG(\"unknown protocol version\");\n> > +     }\n> > +\n>\n> Now that we've sent the request, we should start parsing the response.\n>\n> > +     for (size_t i = 0; i < args->object_info_options->nr; i++) {\n> > +             if (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n> > +                     check_stateless_delimiter(stateless_rpc, reader,\n> > +                                               \"stateless delimiter expected\");\n> > +                     return -1;\n> > +             }\n> > +\n> > +             if (!string_list_has_string(args->object_info_options, reader->line))\n> > +                     return -1;\n> > +\n> > +             if (!strcmp(reader->line, \"size\")) {\n> > +                     size_index = i;\n> > +                     for (size_t j = 0; j < args->oids->nr; j++)\n> > +                             object_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n> > +             }\n> > +     }\n> > +\n>\n> So this function seems to iterate over the list of options to only find\n> and store the indexes. If the server does support size we also allocate\n> the pointers to store the size.\n>\n> Shouldn't we similarly BUG() if there is anything apart from 'size' here?\n\nBecause only size is appended and the server size support comes with\nthe series, there shouldn't be a scenario where anything else appears.\n\n1. Full response with size.\n\n2. OID unrecognized by the server.\n\nWe can quickly check this because the server response when the OID is\nunrecognized is `OID SP` if there's nothing after the SP the object is\nunrecognized.\n\n>\n> > +     for (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n> > +             struct string_list object_info_values = STRING_LIST_INIT_DUP;\n> > +\n> > +             string_list_split(&object_info_values, reader->line, \" \", -1);\n> > +             if (0 <= size_index) {\n> > +                     if (!strcmp(object_info_values.items[1 + size_index].string, \"\")) {\n> > +                             FREE_AND_NULL(object_info_data[i].sizep);\n> > +                             string_list_clear(&object_info_values, 0);\n> > +                             continue;\n> > +                     }\n> > +                     if (strtoul_szt(object_info_values.items[1 + size_index].string,\n> > +                                    10, object_info_data[i].sizep))\n>\n> This is now no longer correctly aligned.\n\nI will fix it.\n\n>\n> > +                             die(\"object-info: ref %s has invalid size %s\",\n> > +                                 object_info_values.items[0].string,\n> > +                                 object_info_values.items[1 + size_index].string);\n> > +             }\n> > +\n> > +             string_list_clear(&object_info_values, 0);\n> > +     }\n> > +     check_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n> > +\n>\n> We parse each line and obtain the size and parse it into\n> `object_info_data[i].sizep`. If the value is missing, we simply continue\n> the iteration.\n\nRelated to that above, if `size` is missing we FREE_AND_NULL so later\non `parse_cmd_remote_object_info()` can detect that and print\n\"missing\" for that oid. This \"missing\" behavior follows what the local\noption `info` does for unrecognized OIDs.\n\nparse_cmd_remote_object_info()\n[snip]\n        for (size_t i = 0; i < object_info_oids.nr; i++) {\n        data->oid = object_info_oids.oid[i];\n        if (remote_object_info[i].sizep) {\n                /*\n                * When reaching here, it means remote-object-info can retrieve\n                * information from server without downloading them.\n                */\n                data->size = *remote_object_info[i].sizep;\n                opt->batch_mode = BATCH_MODE_INFO;\n                batch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n                } else {\n                        report_object_status(opt,\noid_to_hex(&data->oid), &data->oid, \"missing\");\n                }\n        }\n[snip]\n\n\n>\n> I think we had this discussion off the list about how this means that\n> oids which do not have a size will not error out but rather display a\n> missing info value.\n\nThe following commit [11/13] that uses the functions introduced in\nthis commit, will print \"oid missing\" in case of the object being\nunrecognized by the server, the no error out (empty string) would be\nin the scenario where the object is recognized by the server but the\ncapability is not. Because this same series introduces the capability\nadvertisement at [9/13] commit we can be sure that size will always be\nsupported.\nSubsequent commits with the allow-list aim to tackle this with the\nempty-strings when a server doesn't support a capability.\n\n>\n> The argument for not error'ing out was better user experience where the\n> command would complete without exiting. I still think we should error\n> out, because:\n>\n> 1. Without error'ing out, we'd have to display to the user a missing\n> value token. There is contention around what this token should be,\n> as such a token shouldn't be a valid value for the info type being\n> displayed. Future options must be considered here.\n\nI believe that better user experience was my argument last time we\ndiscussed and I still believe that. But a stronger argument is that\nthe local option `info` doesn't die either but rather it prints \"OID\nmissing\" and the next commit where the cmd-related functions are\nimplemented tries to be consistent with it for unrecognized OIDs.\n\n>\n> 2. What will the error code of such a situation be? Do we consider it a\n> success or a failure? Is there a situation where an object can have a\n> missing size?\n\nThere are two different scenarios to consider here:\n\n1. OID unrecognized by the server: the server responds with `oid SP`\n(no value after the space).\n   In this case, the next commit [11/13] prints \"<OID> missing\" using\n`report_object_status()`, which is consistent with how the local\n`info` command handles missing objects in `--batch-command`.\n\n2. OID recognized but a capability is not supported by the server:\nsubsequent commits [12/13] and [13/13] introduce an allow-list that\nfilters options against what the server advertises.\n   Unsupported placeholders return an empty string, following how\n`for-each-ref`handles known but inapplicable atoms.\n\nFor the exit code, both cases return 0. In case 1, this matches the\nlocal `info` behavior. In case 2, we are not failing to fetch, we\nsimply see that the server does not support the requested capabilities\nand skip the request to the server. For the missing token (your point\n1), we reuse the existing \"missing\" token from\n`report_object_status()`, which is what the local code path already\nuses, so no new token is introduced.\n\n>\n> > +     return 0;\n> > +}\n> > diff --git a/fetch-object-info.h b/fetch-object-info.h\n> > new file mode 100644\n> > index 0000000000..d35284bd6b\n> > --- /dev/null\n> > +++ b/fetch-object-info.h\n> > @@ -0,0 +1,22 @@\n> > +#ifndef FETCH_OBJECT_INFO_H\n> > +#define FETCH_OBJECT_INFO_H\n> > +\n> > +#include \"pkt-line.h\"\n> > +#include \"protocol.h\"\n> > +#include \"odb.h\"\n> > +\n> > +struct object_info_args {\n> > +     struct string_list *object_info_options;\n> > +     const struct string_list *server_options;\n> > +     struct oid_array *oids;\n> > +};\n> > +\n> > +/*\n> > + * Sends git-cat-file object-info command into the request buf and read the\n> > + * results from packets.\n> > + */\n> > +int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n> > +                   struct packet_reader *reader, struct object_info *object_info_data,\n> > +                   int stateless_rpc, int fd_out);\n> > +\n> > +#endif /* FETCH_OBJECT_INFO_H */\n> > diff --git a/fetch-pack.c b/fetch-pack.c\n> > index cdebd3476f..a86c93fc52 100644\n> > --- a/fetch-pack.c\n> > +++ b/fetch-pack.c\n> > @@ -1742,6 +1742,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n> >       if (args->depth > 0 || args->deepen_since || args->deepen_not)\n> >               args->deepen = 1;\n> >\n> > +     if (args->object_info)\n> > +             state = FETCH_SEND_REQUEST;\n> > +\n> >       while (state != FETCH_DONE) {\n> >               switch (state) {\n> >               case FETCH_CHECK_LOCAL:\n> > diff --git a/fetch-pack.h b/fetch-pack.h\n> > index 6d0dec7f41..5a428f11ed 100644\n> > --- a/fetch-pack.h\n> > +++ b/fetch-pack.h\n> > @@ -16,6 +16,7 @@ struct fetch_pack_args {\n> >       const struct string_list *deepen_not;\n> >       struct list_objects_filter_options filter_options;\n> >       const struct string_list *server_options;\n> > +     struct object_info *object_info_data;\n> >\n> >       /*\n> >        * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\n> > @@ -43,6 +44,7 @@ struct fetch_pack_args {\n> >       unsigned reject_shallow_remote:1;\n> >       unsigned deepen:1;\n> >       unsigned refetch:1;\n> > +     unsigned object_info:1;\n> >\n> >       /*\n> >        * Indicate that the remote of this request is a promisor remote. The\n> > diff --git a/meson.build b/meson.build\n> > index 3247697f74..145c6882eb 100644\n> > --- a/meson.build\n> > +++ b/meson.build\n> > @@ -347,6 +347,7 @@ libgit_sources = [\n> >    'exec-cmd.c',\n> >    'fetch-negotiator.c',\n> >    'fetch-pack.c',\n> > +  'fetch-object-info.c',\n> >    'fmt-merge-msg.c',\n> >    'fsck.c',\n> >    'fsmonitor.c',\n> > diff --git a/transport-helper.c b/transport-helper.c\n> > index f195070788..c77599f6fb 100644\n> > --- a/transport-helper.c\n> > +++ b/transport-helper.c\n> > @@ -727,8 +727,8 @@ static int fetch_refs(struct transport *transport,\n> >\n> >       /*\n> >        * If we reach here, then the server, the client, and/or the transport\n> > -      * helper does not support protocol v2. --negotiate-only requires\n> > -      * protocol v2.\n> > +      * helper does not support protocol v2. --negotiate-only and cat-file\n> > +      * remote-object-info require protocol v2.\n>\n> This is not really true as of this commit. This only comes into effect\n> in the next commit. So shouldn't this be added there? That said, I would\n> modify this to only talk about object-info requiring v2 and drop the\n> reference to cat-file.\n\nAgreed, I will do that, thanks.\n\n>\n> >        */\n> >       if (data->transport_options.acked_commits) {\n> >               warning(_(\"--negotiate-only requires protocol v2\"));\n> > @@ -744,6 +744,13 @@ static int fetch_refs(struct transport *transport,\n> >               free_refs(dummy);\n> >       }\n> >\n> > +     /* fail the command explicitly to avoid further commands input. */\n> > +     if (transport->smart_options->object_info)\n> > +             die(_(\"remote-object-info requires protocol v2\"));\n> > +\n> > +     if (!data->get_refs_list_called)\n> > +             get_refs_list_using_list(transport, 0);\n> > +\n>\n> Don't we already do this right above? Why is this needed again?\n\nTrue, I'll drop the second `if`.\n\n>\n> >       count = 0;\n> >       for (i = 0; i < nr_heads; i++)\n> >               if (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\n> > diff --git a/transport.c b/transport.c\n> > index 0f5ec30247..7d3246e12b 100644\n> > --- a/transport.c\n> > +++ b/transport.c\n> > @@ -9,6 +9,7 @@\n> >  #include \"hook.h\"\n> >  #include \"pkt-line.h\"\n> >  #include \"fetch-pack.h\"\n> > +#include \"fetch-object-info.h\"\n> >  #include \"remote.h\"\n> >  #include \"connect.h\"\n> >  #include \"send-pack.h\"\n> > @@ -467,8 +468,33 @@ static int fetch_refs_via_pack(struct transport *transport,\n> >       args.negotiation_restrict_tips = data->options.negotiation_restrict_tips;\n> >       args.negotiation_include_tips = data->options.negotiation_include_tips;\n> >       args.reject_shallow_remote = transport->smart_options->reject_shallow;\n> > +     args.object_info = transport->smart_options->object_info;\n> > +\n>\n> Hmm, so we piggy-back on top of the `fetch_refs_via_pack()` function...\n>\n> > +     if (transport->smart_options->object_info\n> > +         && transport->smart_options->object_info_oids->nr > 0) {\n> > +             struct packet_reader reader;\n> > +             struct object_info_args obj_info_args = { 0 };\n> > +\n> > +             obj_info_args.server_options = transport->server_options;\n> > +             obj_info_args.oids = transport->smart_options->object_info_oids;\n> > +             obj_info_args.object_info_options = transport->smart_options->object_info_options;\n> > +             string_list_sort(obj_info_args.object_info_options);\n> > +\n> > +             connect_setup(transport, 0);\n> > +             packet_reader_init(&reader, data->fd[0], NULL, 0,\n> > +                             PACKET_READ_CHOMP_NEWLINE |\n> > +                             PACKET_READ_GENTLE_ON_EOF |\n> > +                             PACKET_READ_DIE_ON_ERR_PACKET);\n> > +\n> > +             data->version = discover_version(&reader);\n> > +             transport->hash_algo = reader.hash_algo;\n> > +\n> > +             ret = fetch_object_info(data->version, &obj_info_args, &reader,\n> > +                                     data->options.object_info_data, transport->stateless_rpc,\n> > +                                     data->fd[1]);\n> > +             goto cleanup;\n> >\n>\n> ... and we jump to exit when we only want object info information. This\n> skips the call to fetch_pack(). I'm a bit uneasy with this. Ideally we\n> should be adding a new function to the vtable to only fetch object info.\n> While this works, this doesn't fit the contract of what this function is\n> supposed to do. See the comment around `fetch_refs` in `struct\n> transport_vtable`. Shouldn't we update that documentation at the very\n> least?\n\nNow that you point that out, piggy-backing doesn't look very good,\nwhile it works, updating the documentation leaves this in a bad\nsituation, so I will move it to a new function and add it to the\nvtable for the next version. Thanks for noticing it.\n\n>\n> > -     if (!data->finished_handshake) {\n> > +     } else if (!data->finished_handshake) {\n> >               int i;\n> >               int must_list_refs = 0;\n> >               for (i = 0; i < nr_heads; i++) {\n> > diff --git a/transport.h b/transport.h\n> > index 7e5867cffa..bd60b10af4 100644\n> > --- a/transport.h\n> > +++ b/transport.h\n> > @@ -6,6 +6,7 @@\n> >  #include \"list-objects-filter-options.h\"\n> >  #include \"string-list.h\"\n> >  #include \"connect.h\"\n> > +#include \"odb.h\"\n> >\n> >  struct git_transport_options {\n> >       unsigned thin : 1;\n> > @@ -31,6 +32,12 @@ struct git_transport_options {\n> >        */\n> >       unsigned connectivity_checked:1;\n> >\n> > +     /*\n> > +      * Transport will attempt to retrieve only object-info.\n> > +      * If object-info is not supported, the operation will error and exit.\n> > +      */\n> > +     unsigned object_info : 1;\n> > +\n>\n> According to our style, this should be `unsigned object_info:1`.\n\nI'll fix it to properly follow the style, I kept it like this because\nother bit fields on these files seem to be outdated in that style.\n\n>\n> >       int depth;\n> >       const char *deepen_since;\n> >       const struct string_list *deepen_not;\n> > @@ -55,6 +62,10 @@ struct git_transport_options {\n> >        * common commits to this oidset instead of fetching any packfiles.\n> >        */\n> >       struct oidset *acked_commits;\n> > +\n> > +     struct oid_array *object_info_oids;\n> > +     struct object_info *object_info_data;\n> > +     struct string_list *object_info_options;\n> >  };\n> >\n> >  enum transport_family {\n> >\n> > --\n> > 2.54.0\n>\n> Nit: I do think the commit message doesn't sufficiently capture the\n> entirety of this patch. We do not talk about:\n>\n> 1. How we piggy-back on top of `fetch_refs_via_pack()` and don't\n> introduce our own pointer in the vtable.\n> 2. The changes made to 'transport-helper.c' and why that is required.\n> 3. How we don't error out when there is no size value provided for an\n> OID and what the implication of this is.\n\nI will extend the commit message to include all 3 points (I'll change\nthe piggy-backing to have its own function).\n\nThanks for the review,\nPablo.\n"},{"id":"546565","messageId":"CAN5EUNRCOHu1M1OujRzhjdt1Oc=nyNSh2t0HrzECV+MO2kbrDA@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZSCKbwckV-j+DyUqOkDkfYcW5xSCPza562mq+OJtQc7DA@mail.gmail.com","subject":"Re: [PATCH GSoC v14 11/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-27T20:51:51Z","receivedAt":"2026-06-27T20:52:05Z","isPatch":true,"body":"El sáb, 27 jun 2026 a las 15:14, Karthik Nayak\n(<karthik.188@gmail.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> [snip]\n>\n> > diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\n> > index 86b9181599..aba20eb770 100644\n> > --- a/Documentation/git-cat-file.adoc\n> > +++ b/Documentation/git-cat-file.adoc\n> > @@ -169,6 +169,13 @@ info <object>::\n> >       Print object info for object reference `<object>`. This corresponds to the\n> >       output of `--batch-check`.\n> >\n> > +remote-object-info <remote> <object>...::\n> > +     Print object info for object references `<object>` at specified\n> > +     `<remote>` without downloading objects from the remote.\n> > +     Raise an error when the `object-info` capability is not supported by the remote.\n> > +     Raise an error when no object references are provided.\n> > +     This command may be combined with `--buffer`.\n> > +\n> >  flush::\n> >       Used with `--buffer` to execute all preceding commands that were issued\n> >       since the beginning or since the last flush was issued. When `--buffer`\n> > @@ -312,7 +319,8 @@ newline. The available atoms are:\n> >       The full hex representation of the object name.\n> >\n> >  `objecttype`::\n> > -     The type of the object (the same as `cat-file -t` reports).\n> > +     The type of the object (the same as `cat-file -t` reports). See\n> > +     `CAVEATS` below. Not supported by `remote-object-info`.\n> >\n>\n> Do we have to keep adding 'Not supported by `remote-object-info`' to\n> each type? Can't we do the inverse and only add 'Supported by\n> `remote-object-info`' to `objectsize`. This avoid having to add this\n> line to every new type.\n\nYes, I will do that.\n\n>\n> >  If no format is specified, the default format is `%(objectname)\n> > -%(objecttype) %(objectsize)`.\n> > +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which use\n> > +`%(objectname) %(objectsize)` for now because \"%(objecttype)\" is not supported yet.\n>\n> Nit: I would drop the 'for now' here, since we don't know when the changes\n> for 'objecttype' will land.\n\nOkay, I'll drop it.\n\n>\n> [snip]\n>\n> >  enum batch_mode {\n> >       BATCH_MODE_CONTENTS,\n> > @@ -633,6 +649,81 @@ static void batch_one_object(const char *obj_name,\n> >       object_context_release(&ctx);\n> >  }\n> >\n> > +static int get_remote_info(struct batch_options *opt,\n> > +                        int argc,\n> > +                        const char **argv,\n> > +                        struct object_info **remote_object_info,\n> > +                        struct oid_array *object_info_oids)\n> > +{\n> > +     int retval = 0;\n> > +     struct remote *remote = NULL;\n> > +     struct object_id oid;\n> > +     struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> > +     struct transport *gtransport;\n> > +\n> > +     /*\n> > +      * Change the format to \"%(objectname) %(objectsize)\" when\n>\n> Nit: perhaps prepend a \"TODO\"\n\nI'll add it.\n\n>\n> > +      * remote-object-info command is used. Once we start supporting objecttype\n> > +      * the default format should change to DEFAULT_FORMAT.\n> > +      */\n> > +     if (!opt->format)\n> > +             opt->format = \"%(objectname) %(objectsize)\";\n> > +\n> > +     remote = remote_get(argv[0]);\n> > +     if (!remote)\n> > +             die(_(\"must supply valid remote when using remote-object-info\"));\n> > +\n> > +     oid_array_clear(object_info_oids);\n> > +     for (size_t i = 1; i < argc; i++) {\n> > +             if (get_oid_hex(argv[i], &oid)) {\n> > +                     size_t len = strlen(argv[i]);\n> > +\n> > +                     if (len < the_hash_algo->hexsz && len >= 4) {\n> > +                             size_t j;\n> > +                             for (j = 0; j < len; j++)\n> > +                                     if (!isxdigit(argv[i][j]))\n> > +                                             break;\n> > +                             if (j == len)\n> > +                                     die(_(\"remote-object-info does not support \"\n> > +                                           \"short oids, %d characters required\"),\n> > +                                         (int)the_hash_algo->hexsz);\n> > +                     }\n> > +                     die(_(\"not a valid object name '%s'\"), argv[i]);\n> > +             }\n> > +             oid_array_append(object_info_oids, &oid);\n> > +     }\n> > +\n> > +     if (!object_info_oids->nr)\n> > +             die(_(\"remote-object-info requires objects\"));\n> > +\n> > +     gtransport = transport_get(remote, NULL);\n> > +\n> > +     if (!gtransport->smart_options) {\n> > +             retval = -1;\n> > +             goto cleanup;\n> > +     }\n> > +\n> > +     CALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n> > +     gtransport->smart_options->object_info = 1;\n> > +     gtransport->smart_options->object_info_oids = object_info_oids;\n> > +\n> > +     /* 'objectsize' is the only option currently supported */\n> > +     if (!strstr(opt->format, \"%(objectsize)\"))\n> > +             die(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n> > +\n>\n> Aren't we setting the opt->format ourselves in this function? Why do we\n> need to check it?\n\nWe only set `opt->format` when the user does not provide a custom format.\n\nThe `strstr` check catches cases like `%(objecttype)` alone, but is\nnot sufficient for mixed formats like `%(objecttype) %(objectsize)`.\nThis is fixed in [12/13] with the allow-list.\n>\n> > +     string_list_append(&object_info_options, \"size\");\n> > +\n> > +     if (object_info_options.nr > 0) {\n> > +             gtransport->smart_options->object_info_options = &object_info_options;\n> > +             gtransport->smart_options->object_info_data = *remote_object_info;\n> > +             retval = transport_fetch_refs(gtransport, NULL);\n> > +     }\n> > +cleanup:\n> > +     string_list_clear(&object_info_options, 0);\n> > +     transport_disconnect(gtransport);\n> > +     return retval;\n> > +}\n> > +\n> >  struct object_cb_data {\n> >       struct batch_options *opt;\n> >       struct expand_data *expand;\n> > @@ -714,6 +805,57 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n> >               load_mailmap();\n> >  }\n> >\n> > +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> > +                                      const char *line, struct strbuf *output,\n> > +                                      struct expand_data *data)\n> > +{\n> > +     int count;\n> > +     const char **argv;\n> > +     char *line_to_split;\n> > +     struct object_info *remote_object_info = NULL;\n> > +     struct oid_array object_info_oids = OID_ARRAY_INIT;\n> > +\n> > +     if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n> > +             die(_(\"remote-object-info command too long\"));\n> > +\n> > +     line_to_split = xstrdup(line);\n> > +     count = split_cmdline(line_to_split, &argv);\n> > +     if (count < 0)\n> > +             die(_(\"split remote-object-info command\"));\n>\n> We should  be using `split_cmdline_strerror()` here\n\nOk, I'll use it.\n\n>\n> > +     if (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n> > +             die(_(\"remote-object-info supports at most %d objects\"),\n> > +                 MAX_ALLOWED_OBJ_LIMIT);\n> > +\n> > +     if (get_remote_info(opt, count, argv, &remote_object_info,\n> > +                         &object_info_oids))\n> > +             goto cleanup;\n> > +\n> > +     data->skip_object_info = 1;\n> > +     for (size_t i = 0; i < object_info_oids.nr; i++) {\n> > +             data->oid = object_info_oids.oid[i];\n> > +             if (remote_object_info[i].sizep) {\n> > +                     /*\n> > +                      * When reaching here, it means remote-object-info can retrieve\n> > +                      * information from server without downloading them.\n> > +                      */\n> > +                     data->size = *remote_object_info[i].sizep;\n> > +                     opt->batch_mode = BATCH_MODE_INFO;\n> > +                     batch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n> > +             } else {\n> > +                     report_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n> > +             }\n> > +     }\n> > +     data->skip_object_info = 0;\n> > +\n> > +cleanup:\n> > +     for (size_t i = 0; i < object_info_oids.nr; i++)\n> > +             free_object_info_contents(&remote_object_info[i]);\n> > +     free(line_to_split);\n> > +     free(argv);\n> > +     free(remote_object_info);\n> > +     oid_array_clear(&object_info_oids);\n> > +}\n> > +\n>\n> [snip]\n>\n> > diff --git a/t/meson.build b/t/meson.build\n> > index 3219264fe7..54d21111a3 100644\n> > --- a/t/meson.build\n> > +++ b/t/meson.build\n> > @@ -170,6 +170,7 @@ integration_tests = [\n> >    't1014-read-tree-confusing.sh',\n> >    't1015-read-index-unmerged.sh',\n> >    't1016-compatObjectFormat.sh',\n> > +  't1017-cat-file-remote-object-info.sh',\n> >    't1020-subdirectory.sh',\n> >    't1022-read-tree-partial-clone.sh',\n> >    't1050-large.sh',\n>\n> [snip]\n\nThanks for the feedback,\nPablo.\n"},{"id":"546566","messageId":"CAN5EUNTZ+m1v90K2bivD1ZTNPc0jFPt4LseN-B-mGbifYHj5RQ@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZTdcg47nmZs2t1FvyOgG9S4Ap3RaK+C0Dhku6cG+wj_Kw@mail.gmail.com","subject":"Re: [PATCH GSoC v14 07/13] connect: refactor packet writing","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-27T20:58:30Z","receivedAt":"2026-06-27T20:58:41Z","isPatch":true,"body":"El vie, 26 jun 2026 a las 19:03, Karthik Nayak\n(<karthik.188@gmail.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> The subject is bit too generic no? Maybe we can talk about the function?\n> Perhaps:\n>\n>     connect: make `write_fetch_command_and_capabilities()` more generic\n\nOkay, I'll use that as the subject.\n\n>\n> > Refactor `write_fetch_command_and_capabilities()`, enabling it to serve\n> > both fetch and additional commands.\n> >\n> > In this context, \"command\" refers to the \"operations\" supported by\n> > Git's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\n> > subcommand (e.g., git-fetch(1)) or a server-side operation like\n> > \"object-info\" as implemented in commit a2ba162\n> > (object-info: support for retrieving object info, 2021-04-20).\n> >\n> > Refactor the function signature to accept a command instead of the\n> > hardcoded \"fetch\".\n>\n> [snip]\n\nThanks for the feedback,\nPablo.\n"},{"id":"546567","messageId":"CAN5EUNRP7ZBxP-Q_eWrjdh5hY3+WC92nWPhyjYScTdzZWOkZcg@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZRhCXBQN7CqwLyE2F9u+oqAsqvcFP+fuiyw4SVaSDfT6Q@mail.gmail.com","subject":"Re: [PATCH GSoC v14 06/13] fetch-pack: move function to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-27T21:03:09Z","receivedAt":"2026-06-27T21:03:21Z","isPatch":true,"body":"El vie, 26 jun 2026 a las 18:57, Karthik Nayak\n(<karthik.188@gmail.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> Nit: could we mention the function in the subject?\n\nYes, I'll mention it.\n\n>\n> > write_fetch_command_and_capabilities will be refactored in a\n> > subsequent\n>\n> Super Nit: Some parts of your patches use backticks for quoting code or\n> filenames and others skip the convention. It would be nice to be\n> consistent.\n\nSorry about that, I'll stick to backticks always.\n\n>\n> > commit where it will become a more general-purpose function, making it\n> > more accessible to additional commands in the future.\n> >\n> > Move `write_fetch_command_and_capabilities()` to `connect.c`, where\n> > there are similar purpose functions.\n> >\n> > Because string_list is only used as a pointer, use a forward\n> > declaration [1].\n> >\n> > [1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n> >\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> > ---\n> >  connect.c    | 34 ++++++++++++++++++++++++++++++++++\n> >  connect.h    |  4 ++++\n> >  fetch-pack.c | 34 ----------------------------------\n> >  3 files changed, 38 insertions(+), 34 deletions(-)\n> >\n[snip]\n\nThanks for the feedback,\nPablo.\n"},{"id":"546568","messageId":"CAN5EUNQzwoZ+v1cCZSWkdJUqqgQBVzLNUmyDsz9jhuKK42Q0Yg@mail.gmail.com","threadId":"61695","inReplyTo":"CA+J6zkRm_F4MQ2K8Ayv8PGJOx+pNAg73+p-4VdOgkxeuKAkKew@mail.gmail.com","subject":"Re: [PATCH GSoC v14 06/13] fetch-pack: move function to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-27T21:04:40Z","receivedAt":"2026-06-27T21:04:51Z","isPatch":true,"body":"El vie, 26 jun 2026 a las 14:14, Chandra Pratap\n(<chandrapratap3519@gmail.com>) escribió:\n>\n> On Thu, 25 Jun 2026 at 17:43, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n> >\n> > write_fetch_command_and_capabilities will be refactored in a subsequent\n>\n> Nit: The paragraph below and the preceding patches refer to this function\n> as `write_fetch_command_and_capabilities()`. It will be nice to maintain\n> consistency throughout this series.\n\nI'll do that.\n\n>\n> > commit where it will become a more general-purpose function, making it\n> > more accessible to additional commands in the future.\n> >\n> > Move `write_fetch_command_and_capabilities()` to `connect.c`, where\n> > there are similar purpose functions.\n> >\n> > Because string_list is only used as a pointer, use a forward\n> > declaration [1].\n> >\n> > [1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n> >\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> > ---\n> [snip]\n\nThanks for the feedback,\nPablo.\n"},{"id":"546569","messageId":"CAN5EUNR2Sv_JtSwQ66m1CwYxgDs4Zb_RxpkpnDTWevPVAOm4xA@mail.gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZScS3Gmm5BAgJF69phpaDXGnP_j9jx+bMhn_tfF65RXEg@mail.gmail.com","subject":"Re: [PATCH GSoC v14 05/13] fetch-pack: prepare function to be moved","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-06-27T21:06:58Z","receivedAt":"2026-06-27T21:07:10Z","isPatch":true,"body":"El vie, 26 jun 2026 a las 18:54, Karthik Nayak\n(<karthik.188@gmail.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> The subject doesn't really give much insight into what the patch does.\n> Perhaps something like:\n>\n>     fetch-pack: use repo config in `write_fetch_command_and_capabilities()`\n>     fetch-pack: drop static variable use in\n> `write_fetch_command_and_capabilities()`\n>\n> > `write_fetch_command_and_capabilities()` will be refactored and moved in\n> > subsequent commits where it will become a more general-purpose function,\n> > making it more accessible to additional commands in the future.\n> >\n> > To move `write_fetch_command_and_capabilities()` to `connect.c`, we\n> > previously need to adjust how `advertise_sid` is managed. Currently in\n>\n> I don't think 'previously' makes sense here.\n>\n> > `fetch_pack.c`, `advertise_sid` is a static variable, modified using\n> > `repo_config_get_bool()`.\n> >\n>\n> Perhaps:\n>\n>     To move `write_fetch_command_and_capabilities()` to `connect.c`,\n>     drop the usage of file static variable `advertise_sid` within the\n>     function. Currently, `advertise_sid` is modified...\n>\n> >\n> > Initialize `advertise_sid` at the begining by directly using\n> > `repo_config_get_bool()`. This change is safe because:\n> >\n> > In the original `fetch-pack.c` code, there are only two places that write\n> > `advertise_sid`:\n> >\n>\n> This needs to be modified no? This is from the prev patch, where we\n> moved and refactored in the same patch, this no longer is the case.\n>\n> > 1. In function `do_fetch_pack()`:\n> >         if (!server_supports(\"session_id\"))\n> >                advertise_sid = 0;\n> > 2. In function `fetch_pack_config()`:\n> >         repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n> >\n> > About 1, since `do_fetch_pack()` is only relevant for protocol v1, this\n> > assignment can be ignored, as `write_fetch_command_and_capabilities()`\n> > is only used in v2.\n> >\n> > About 2, `repo_config_get_bool()` is from `config.h` and it's an\n> > out-of-box dependency of `connect.c`, so we can reuse it directly.\n> >\n> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> > Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> > ---\n> >  fetch-pack.c | 5 ++++-\n> >  1 file changed, 4 insertions(+), 1 deletion(-)\n> >\n> > diff --git a/fetch-pack.c b/fetch-pack.c\n> > index f13951d154..ad07603755 100644\n> > --- a/fetch-pack.c\n> > +++ b/fetch-pack.c\n> > @@ -1380,6 +1380,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> >                                                const struct string_list *server_options)\n> >  {\n> >       const char *hash_name;\n> > +     int advertise_sid;\n> > +\n> > +     repo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n> >\n> >       ensure_server_supports_v2(\"fetch\");\n> >       packet_buf_write(req_buf, \"command=fetch\");\n> > @@ -1395,7 +1398,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> >       }\n> >\n> >       if (server_feature_v2(\"object-format\", &hash_name)) {\n> > -             int hash_algo = hash_algo_by_name(hash_name);\n> > +             const unsigned int hash_algo = hash_algo_by_name(hash_name);\n> >\n>\n> Agreed with Chandra, this needs to be assessed.\n>\n> >               if (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n> >                       die(_(\"mismatched algorithms: client %s; server %s\"),\n> >                           the_hash_algo->name, hash_name);\n> >\n> > --\n> > 2.54.0\n\nI'll reword the commit message with all you and Chandra reviewed.\n\nThanks for the feedback,\nPablo.\n"},{"id":"546616","messageId":"CAOLa=ZR0B-wEOHFNeu7HjypiTqr45mfGd1NkN5cPH85hxYqe4Q@mail.gmail.com","threadId":"61695","inReplyTo":"CAN5EUNTrdNArd5SX9df6x9bOhRfzE4c7dLOuNu7ONUdn4TLsUA@mail.gmail.com","subject":"Re: [PATCH GSoC v14 09/13] serve: advertise object-info feature","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-28T22:55:22Z","receivedAt":"2026-06-28T22:55:25Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> El sáb, 27 jun 2026 a las 0:23, Karthik Nayak\n> (<karthik.188@gmail.com>) escribió:\n>>\n>> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>>\n>> > From: Calvin Wan <calvinwan@google.com>\n>> >\n>> > In order for a client to know what object-info components a server can\n>> > provide, advertise supported object-info features. This will allow a\n>> > client to decide whether to query the server for object-info or fetch\n>> > as a fallback.\n>> >\n>> > Helped-by: Jonathan Tan <jonathantanmy@google.com>\n>> > Helped-by: Christian Couder <chriscool@tuxfamily.org>\n>> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n>> > Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n>> > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n>> > ---\n>> >  serve.c | 5 ++++-\n>> >  1 file changed, 4 insertions(+), 1 deletion(-)\n>> >\n>> > diff --git a/serve.c b/serve.c\n>> > index 49a6e39b1d..2b07d922b3 100644\n>> > --- a/serve.c\n>> > +++ b/serve.c\n>> > @@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n>> >       trace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n>> >  }\n>> >\n>> > -static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n>> > +static int object_info_advertise(struct repository *r, struct strbuf *value)\n>> >  {\n>> >       if (advertise_object_info == -1 &&\n>> >           repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n>> > @@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n>> >               /* disabled by default */\n>> >               advertise_object_info = 0;\n>> >       }\n>> > +     /* Currently only size is supported */\n>> > +     if (value && advertise_object_info)\n>> > +             strbuf_addstr(value, \"size\");\n>>\n>> So is the plan that further options will be added here to value? If so,\n>> whats the format we will follow?\n>\n> Hi!\n> The current documented format is at `gitprotocol-v2.adoc`, however I\n> think it could be improved. I have a more complete version in the\n> not-yet-sent %(objecttype) support series, but since the question\n> comes up here, I will update the format documentation in this series\n> for size only:\n>\n\nAh nice, I didn't see that.\n\n> oid <oid>\n>   Indicates to the server an object which the client wants to obtain\n> - information for.\n> + information for. They must be full object IDs.\n>\n> - info = PKT-LINE(attrs) LF)\n> + info = PKT-LINE(attrs LF)\n>        *PKT-LINE(obj-info LF)\n>\n>   attrs = attr | attrs SP attrs\n>\n> + obj-size = 1*DIGIT\n> +\n>   attr = \"size\"\n>\n> - obj-info = obj-id SP obj-size\n> + obj-info = obj-id SP [obj-size]\n> +\n> +If the server does not recognize the object id, the response will be\n> +`obj-id SP` regardless of the number of attributes requested.\n>\n> About the names `size` and future ones `type` they are arbitrarily\n> chosen, so for example: `delta:base` could be `delta`. They are\n> appended to the buffer so in case of adding `type`, it would look\n> like:\n>\n> strbuf_addstr(value, \"size type\");\n>\n> What do you think?\n>\n\nI didn't know this part was already documented, so its all good :)\n\n> Thanks for the review,\n> Pablo.\n>\n>>\n>> >       return advertise_object_info;\n>> >  }\n>> >\n>> >\n>> > --\n>> > 2.54.0\n"},{"id":"546882","messageId":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260625-ps-eric-work-rebase-v14-0-09f7ffe21a53@gmail.com","subject":"[PATCH GSoC v15 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:34Z","receivedAt":"2026-07-01T12:18:51Z","isPatch":true,"body":"This patch series is a continuation of Eric Ju's (eric.peijian@gmail.com) and\nCalvin Wan's (calvinwan@google.com) patch series [1] and [2] respectively.\n\nSometimes it is beneficial to retrieve information about an object without\nhaving to download it completely. The server logic for retrieving size has\nalready been implemented and merged in \"a2ba162cda (object-info: support for\nretrieving object info, 2021-04-20)\"[3]. This patch series implement the client\noption for it.\n\nEric's series adds the `remote-object-info` command to\n`cat-file --batch-command`. This command allows the client to make an\nobject-info command request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info capability,\n`cat-file --batch-command` will die.\n\nIf a user attempts to use `remote-object-info` with protocol v1,\n`cat-file --batch-command` will die.\n\nCurrently, only the size (%(objectsize)) is supported end to end in this\nimplementation. The type (%(objecttype)) is known by the client's allow-list\nand request path but is not supported on the server side nor the response\nparsing. A follow up series will add full end-to-end support for %(objecttype).\n\nThe default format for remote-object-info is set to %(objectname) %(objectsize).\nOnce %(objecttype) is supported, the default format will be unified accordingly.\n\nIf the batch command format includes unsupported fields such as %(objecttype),\n%(objectsize:disk), or %(deltabase), the command will return empty strings for\neach unsupported field.\n\nThis series completes Eric's work mainly with the refactor of the validation\nof the placeholder with an allow-list that filters what the client asks with\nwhat the server is capable of provide following Jeff King's idea [4].\n\nGitHub CI: https://github.com/pabloosabaterr/git/actions/runs/28435046129\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n[4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n\nChanges since v14:\n- Changed strtou_szt to be strtoumax_szt so there are no problems\n  between `size_t` and `unsigned long`.\n- Reworded commits to be more consistent and clear.\n- gitprotocol-v2.adoc documentation is now being modified on commit\n  [9/13].\n- Added BUG() calls on [10/13].\n- style and nits fixes.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nCalvin Wan (3):\n      fetch-pack: move fetch initialization\n      serve: advertise object-info feature\n      transport: add client support for object-info\n\nEric Ju (4):\n      git-compat-util: add `strtoumax_szt()` with error handling\n      cat-file: declare loop counter inside for()\n      t1006: split test utility functions into new 'lib-cat-file.sh'\n      cat-file: add remote-object-info to batch-command\n\nPablo Sabater (6):\n      transport-helper: fix memory leak of helper on disconnect\n      fetch-pack: drop static `advertise_sid` variable\n      fetch-pack: move `write_fetch_command_and_capabilities()` to connect.c\n      connect: make `write_fetch_command_and_capabilities()` more generic\n      cat-file: validate remote atoms with an allow-list\n      cat-file: make remote-object-info allow-list dynamic\n\n Documentation/git-cat-file.adoc        |  29 +-\n Documentation/gitprotocol-v2.adoc      |  11 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 221 ++++++++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    | 115 ++++++\n fetch-object-info.h                    |  22 ++\n fetch-pack.c                           |  48 +--\n fetch-pack.h                           |   1 +\n git-compat-util.h                      |  20 +\n meson.build                            |   1 +\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n serve.c                                |   5 +-\n t/lib-cat-file.sh                      |  16 +\n t/meson.build                          |   1 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 699 +++++++++++++++++++++++++++++++++\n transport-helper.c                     |  15 +-\n transport-internal.h                   |   8 +\n transport.c                            |  46 +++\n transport.h                            |  10 +\n 23 files changed, 1257 insertions(+), 80 deletions(-)\n---\nbase-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc\nchange-id: 20260608-ps-eric-work-rebase-b73ae84ba671\n\nBest regards,\n--  \nPablo Sabater <pabloosabaterr@gmail.com>\n"},{"id":"546883","messageId":"20260701-ps-eric-work-rebase-v15-1-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 01/13] transport-helper: fix memory leak of helper on disconnect","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:35Z","receivedAt":"2026-07-01T12:18:52Z","isPatch":true,"body":"`disconnect_helper()` only frees data inside of the `if(data->helper)`\nblock [1]. When the transport is disconnected without the helper\nbeing fully started, `data->name` allocated in `transport_helper_init()`\nis never freed.\n\nMove `FREE_AND_NULL(data->name)` outside the conditional block so it's\nalways freed on disconnect.\n\n[1]: https://lore.kernel.org/git/05fbadbae2184479c87c37675dde7bd79b3e32ab.1716465556.git.ps@pks.im/\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n transport-helper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 80f90eb7ba..f195070788 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tres = finish_command(data->helper);\n-\t\tFREE_AND_NULL(data->name);\n \t\tFREE_AND_NULL(data->helper);\n \t}\n+\tFREE_AND_NULL(data->name);\n \treturn res;\n }\n \n\n-- \n2.54.0\n"},{"id":"546884","messageId":"20260701-ps-eric-work-rebase-v15-2-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 02/13] git-compat-util: add `strtoumax_szt()` with error handling","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:36Z","receivedAt":"2026-07-01T12:18:53Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nWe already have `strtoul_ui()` and similar functions that provide proper\nerror handling using `strtoul` from the standard library. However,\nthere isn't currently a variant that returns a `size_t`.\n\nUsing `strtoul` is unreliable because `size_`t is platform-dependent,\n`unsigned long` could be too big to fit into a `size_t` or too small to\nhold a `size_t`.\n\nUse `strtoumax` which returns a `uintmax_t` guaranteed to be at least as\nlarge as `size_t`, add a range check against `SIZE_MAX` to prevent\n`size_t` overflow.\n\nThis variant is needed in a subsequent commit to enable returning a\n`size_t` with proper error handling.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n git-compat-util.h | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 8809776407..5ecce5bbd2 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n \treturn 0;\n }\n \n+/*\n+ * Convert a string to a size_t using the standard library's strtoumax, with\n+ * additional error handling to ensure robustness.\n+ */\n+static inline int strtoumax_szt(char const *s, int base, size_t *result)\n+{\n+\tuintmax_t uim;\n+\tchar *p;\n+\n+\terrno = 0;\n+\t/* negative values would be accepted by strtoul */\n+\tif (strchr(s, '-'))\n+\t\treturn -1;\n+\tuim = strtoumax(s, &p, base);\n+\tif ((errno || *p || p == s) || uim > SIZE_MAX)\n+\t\treturn -1;\n+\t*result = uim;\n+\treturn 0;\n+}\n+\n static inline int strtol_i(char const *s, int base, int *result)\n {\n \tlong ul;\n\n-- \n2.54.0\n"},{"id":"546885","messageId":"20260701-ps-eric-work-rebase-v15-3-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 03/13] cat-file: declare loop counter inside for()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:37Z","receivedAt":"2026-07-01T12:18:54Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSome code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\nWhile at it, we also change its type from `int` to `size_t` where the\nlatter makes more sense.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c | 13 ++++---------\n fetch-pack.c       |  3 +--\n 2 files changed, 5 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex d6ef8414ee..1e5473ab70 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -718,14 +718,12 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n \t\tstruct queued_cmd *cmd,\n-\t\tint nr)\n+\t\tsize_t nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -733,9 +731,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -762,7 +758,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -772,7 +767,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 120e01f3cf..f13951d154 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n\n-- \n2.54.0\n"},{"id":"546886","messageId":"20260701-ps-eric-work-rebase-v15-4-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 04/13] t1006: split test utility functions into new 'lib-cat-file.sh'","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:38Z","receivedAt":"2026-07-01T12:18:55Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nThis refactor extracts utility functions from the cat-file's test\nscript 't1006-cat-file.sh' into a new 'lib-cat-file.sh' dedicated\nlibrary file.\n\nA subsequent commit will need this functions, the goal is to improve\ncode reuse and readability,enabling future tests to leverage these\nutilities without duplicating code.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 13 +------------\n 2 files changed, 17 insertions(+), 12 deletions(-)\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..44af232d74\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 8e2c52652c..8360f3bbd9 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-loose.sh\"\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     object_name=\"$2\"\n\n-- \n2.54.0\n"},{"id":"546887","messageId":"20260701-ps-eric-work-rebase-v15-5-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 05/13] fetch-pack: drop static `advertise_sid` variable","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:39Z","receivedAt":"2026-07-01T12:18:56Z","isPatch":true,"body":"`write_fetch_command_and_capabilities()` is moved to `connect.c` in a\nsubsequent commit. To prepare for that, drop the static variable usage\nof `advertise_sid`. Currently `advertise_sid` is used in two places:\n\n1. In function `do_fetch_pack()`:\n        if (!server_supports(\"session_id\"))\n               advertise_sid = 0;\n2. In function `fetch_pack_config()`:\n        repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nSince `do_fetch_pack()` is only relevant for protocol v1, it can be\nignored because `write_fetch_command_and_capabilities()` is only used in\nprotocol v2.\n\nAbout 2, call `repo_config_get_bool()` directly inside of the function.\n\nWhile at it, change `hash_algo`'s type to match `hash_algo_by_name()`'s\nactual return type (`unsigned int`) and make it `const`.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f13951d154..ad07603755 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1380,6 +1380,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t\t\t\t\t\t const struct string_list *server_options)\n {\n \tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tensure_server_supports_v2(\"fetch\");\n \tpacket_buf_write(req_buf, \"command=fetch\");\n@@ -1395,7 +1398,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t}\n \n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n \t\t\t    the_hash_algo->name, hash_name);\n\n-- \n2.54.0\n"},{"id":"546888","messageId":"20260701-ps-eric-work-rebase-v15-6-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 06/13] fetch-pack: move `write_fetch_command_and_capabilities()` to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:40Z","receivedAt":"2026-07-01T12:18:58Z","isPatch":true,"body":"`write_fetch_command_and_capabilities()` is refactored in a subsequent\ncommit where it becomes a more general-purpose function, making it\nmore accessible to additional commands in the future.\n\nMove `write_fetch_command_and_capabilities()` to `connect.c`, where\nthere are similar purpose functions.\n\nBecause `string_list` is only used as a pointer, use a forward\ndeclaration [1].\n\n[1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 34 ----------------------------------\n 3 files changed, 38 insertions(+), 34 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 47e39d2a73..1dced8e632 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,6 +700,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(\"fetch\");\n+\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t    the_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t    the_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n static const char *url_scheme_name(enum url_scheme scheme)\n {\n \tswitch (scheme) {\ndiff --git a/connect.h b/connect.h\nindex aa482a37fb..c4f6ea4b0a 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+struct string_list;\n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex ad07603755..4a8a70b5f3 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1376,40 +1376,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\tint advertise_sid;\n-\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n\n-- \n2.54.0\n"},{"id":"546889","messageId":"20260701-ps-eric-work-rebase-v15-7-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 07/13] connect: make `write_fetch_command_and_capabilities()` more generic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:41Z","receivedAt":"2026-07-01T12:18:59Z","isPatch":true,"body":"Refactor `write_fetch_command_and_capabilities()`, enabling it to serve\nboth fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162\n(object-info: support for retrieving object info, 2021-04-20).\n\nRefactor the function signature to accept a command instead of the\nhardcoded \"fetch\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 8 ++++----\n connect.h    | 8 ++++++--\n fetch-pack.c | 4 ++--\n 3 files changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 1dced8e632..7b472f8e5f 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,16 +700,16 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options)\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \tint advertise_sid;\n \n \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\ndiff --git a/connect.h b/connect.h\nindex c4f6ea4b0a..c2bf492ed9 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -35,7 +35,11 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       const char *error);\n \n struct string_list;\n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options);\n+/*\n+ * Writes a command along with the requested server capabilities/features into a\n+ * request buffer.\n+ */\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 4a8a70b5f3..3d32114907 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1387,7 +1387,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2255,7 +2255,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n\n-- \n2.54.0\n"},{"id":"546890","messageId":"20260701-ps-eric-work-rebase-v15-8-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 08/13] fetch-pack: move fetch initialization","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:42Z","receivedAt":"2026-07-01T12:19:00Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\n`do_fetch_pack_v2()` state machine. Currently, they are initialized\nin `FETCH_CHECK_LOCAL`, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3d32114907..cdebd3476f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1736,18 +1736,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n\n-- \n2.54.0\n"},{"id":"546891","messageId":"20260701-ps-eric-work-rebase-v15-9-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 09/13] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:43Z","receivedAt":"2026-07-01T12:19:01Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what `object-info` components a server can\nprovide, advertise supported `object-info` features. This allows a\nclient to decide whether to query the server for object-info or fetch\nas a fallback.\n\nWhile at it, update the `object-info` section in `gitprotocol-v2.adoc`:\n- Require full `obj-oid` explicitly.\n- Fix parentheses.\n- Define `obj-size` explicitly.\n- Make `obj-size` optional in `obj-info` and document the behavior\n  for unrecognized object IDs.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/gitprotocol-v2.adoc | 11 ++++++++---\n serve.c                           |  5 ++++-\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex befa697d21..f21a6cbcaa 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -568,21 +568,26 @@ An `object-info` request takes the following arguments:\n \n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n-\tinformation for.\n+\tinformation for. They must be full object IDs.\n \n The response of `object-info` is a list of the requested object ids\n and associated requested information, each separated by a single space.\n \n \toutput = info flush-pkt\n \n-\tinfo = PKT-LINE(attrs) LF)\n+\tinfo = PKT-LINE(attrs LF)\n \t\t*PKT-LINE(obj-info LF)\n \n \tattrs = attr | attrs SP attrs\n \n+\tobj-size = 1*DIGIT\n+\n \tattr = \"size\"\n \n-\tobj-info = obj-id SP obj-size\n+\tobj-info = obj-id SP [obj-size]\n+\n+\tIf the server does not recognize the object id, the response will be\n+\t`obj-id SP` regardless of the number of attributes requested.\n \n bundle-uri\n ~~~~~~~~~~\ndiff --git a/serve.c b/serve.c\nindex 49a6e39b1d..2b07d922b3 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\t/* Currently only size is supported */\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n\n-- \n2.54.0\n"},{"id":"546892","messageId":"20260701-ps-eric-work-rebase-v15-10-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 10/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:44Z","receivedAt":"2026-07-01T12:19:03Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nIntroduce client-side support for the object-info capability.\n\nAdd its own function for object-info separate from existing fetch\ninfrastructure.\n\nCurrently, the client supports requesting a list of object IDs with\nthe `size` feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client returns an error and exit.\n\nNote that:\n\n1. the entire request is written into `req_buf` before being sent to the\n   remote. This approach follows the pattern used in the\n   `send_fetch_request()` logic within 'fetch-pack.c'. Streaming the\n   request is not addressed in this patch.\n\n2. When the server does not recognize an OID, following the v2 protocol,\n   the server returns \"<OID> SP\", when this happens,\n   `fetch_object_info()` sets the corresponding size pointer to NULL so\n   that callers can detect and handle it.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Makefile             |  1 +\n fetch-object-info.c  | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h  | 22 ++++++++++++\n fetch-pack.h         |  1 +\n meson.build          |  1 +\n transport-helper.c   | 13 +++++--\n transport-internal.h |  8 +++++\n transport.c          | 46 +++++++++++++++++++++++++\n transport.h          | 10 ++++++\n 9 files changed, 195 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 1cec251f43..ec4df39a6b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1159,6 +1159,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..03cfb70338\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,95 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"odb.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\telse\n+\t\tBUG(\"only size should be in object_info_options\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"unsupported protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader,\n+\t\t\t\t\t\t  \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\tsize_index = i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t} else {\n+\t\t\tBUG(\"only size is supported\");\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n+\t\tif (size_index >= 0) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\")) {\n+\t\t\t\tFREE_AND_NULL(object_info_data[i].sizep);\n+\t\t\t\tstring_list_clear(&object_info_values, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (strtoumax_szt(object_info_values.items[1 + size_index].string,\n+\t\t\t\t\t  10, object_info_data[i].sizep))\n+\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t\t    object_info_values.items[0].string,\n+\t\t\t\t    object_info_values.items[1 + size_index].string);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..d35284bd6b\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"odb.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 6d0dec7f41..0fba340a84 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\ndiff --git a/meson.build b/meson.build\nindex 3247697f74..145c6882eb 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -347,6 +347,7 @@ libgit_sources = [\n   'exec-cmd.c',\n   'fetch-negotiator.c',\n   'fetch-pack.c',\n+  'fetch-object-info.c',\n   'fmt-merge-msg.c',\n   'fsck.c',\n   'fsmonitor.c',\ndiff --git a/transport-helper.c b/transport-helper.c\nindex f195070788..f97e6d7b29 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -727,8 +727,7 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -784,6 +783,15 @@ static int fetch_refs(struct transport *transport,\n \treturn -1;\n }\n \n+static int fetch_object_info_helper(struct transport *transport)\n+{\n+\tget_helper(transport);\n+\tif (process_connect(transport, 0))\n+\t\treturn transport->vtable->fetch_object_info(transport);\n+\n+\tdie(_(\"object-info requires protocol v2\"));\n+}\n+\n struct push_update_ref_state {\n \tstruct ref *hint;\n \tstruct ref_push_report *report;\n@@ -1330,6 +1338,7 @@ static struct transport_vtable vtable = {\n \t.get_refs_list\t= get_refs_list,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs,\n+\t.fetch_object_info = fetch_object_info_helper,\n \t.push_refs\t= push_refs,\n \t.connect\t= connect_helper,\n \t.disconnect\t= release_helper\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 051f3ab0dc..60db0bedcd 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -45,6 +45,14 @@ struct transport_vtable {\n \t **/\n \tint (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);\n \n+\t/*\n+\t * Fetch object info (only size currently) from remote without\n+\t * downloading the objects.\n+\t *\n+\t * Uses object-info capability of v2 protocol.\n+\t */\n+\tint (*fetch_object_info)(struct transport *transport);\n+\n \t/**\n \t * Push the objects and refs. Send the necessary objects, and\n \t * then, for any refs where peer_ref is set and\ndiff --git a/transport.c b/transport.c\nindex 0f5ec30247..602b1c5512 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1,3 +1,4 @@\n+#include \"compat/posix.h\"\n #define USE_THE_REPOSITORY_VARIABLE\n \n #include \"git-compat-util.h\"\n@@ -9,6 +10,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -432,6 +434,48 @@ static int get_bundle_uri(struct transport *transport)\n \t\t\t\t     transport->bundles, stateless_rpc);\n }\n \n+static int fetch_object_info_via_pack(struct transport *transport)\n+{\n+\tint ret = 0;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct packet_reader reader;\n+\tstruct object_info_args args = { 0 };\n+\n+\targs.server_options = transport->server_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\tstring_list_sort(args.object_info_options);\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tdata->version = discover_version(&reader);\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tret = fetch_object_info(data->version, &args, &reader,\n+\t\t\t\tdata->options.object_info_data,\n+\t\t\t\ttransport->stateless_rpc, data->fd[1]);\n+\n+\tclose(data->fd[0]);\n+\tif (data->fd[1] >= 0)\n+\t\tclose(data->fd[1]);\n+\tif (finish_connect(data->conn))\n+\t\tret = -1;\n+\tdata->conn = NULL;\n+\n+\treturn ret;\n+}\n+\n+int transport_fetch_object_info(struct transport *transport)\n+{\n+\tif (!transport->vtable->fetch_object_info)\n+\t\tdie(_(\"remote does not support object-info\"));\n+\treturn transport->vtable->fetch_object_info(transport);\n+}\n+\n static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t       int nr_heads, struct ref **to_fetch)\n {\n@@ -1004,6 +1048,7 @@ static struct transport_vtable taken_over_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.disconnect\t= disconnect_git\n };\n@@ -1169,6 +1214,7 @@ static struct transport_vtable builtin_smart_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.connect\t= connect_git,\n \t.disconnect\t= disconnect_git\ndiff --git a/transport.h b/transport.h\nindex 7e5867cffa..9e85a4cd35 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -6,6 +6,7 @@\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n #include \"connect.h\"\n+#include \"odb.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -55,6 +56,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n@@ -309,6 +314,11 @@ int transport_get_remote_bundle_uri(struct transport *transport);\n const struct git_hash_algo *transport_get_hash_algo(struct transport *transport);\n int transport_fetch_refs(struct transport *transport, struct ref *refs);\n \n+/*\n+ * Fetch the object info from remote\n+ */\n+int transport_fetch_object_info(struct transport *transport);\n+\n /*\n  * If this flag is set, unlocking will avoid to call non-async-signal-safe\n  * functions. This will necessarily leave behind some data structures which\n\n-- \n2.54.0\n"},{"id":"546894","messageId":"20260701-ps-eric-work-rebase-v15-11-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 11/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:45Z","receivedAt":"2026-07-01T12:19:05Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSince the `info` command in `cat-file --batch-command` prints object\ninfo for a given object, it is natural to add another command in\n`cat-file --batch-command` to print object info for a given object\nfrom a remote.\n\nAdd `remote-object-info` to `cat-file --batch-command`.\n\nWhile `info` takes object ids one at a time, this creates\noverhead when making requests to a server. So `remote-object-info`\ninstead can take multiple object ids at once.\n\nThe `cat-file --batch-command` command is generally implemented in\nthe following manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, `remote-object-info` is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is `remote-object-info`:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode `remote-object-info`:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is `remote-object-info`:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, `remote-object-info` gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for `remote-object-info` to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  29 +-\n builtin/cat-file.c                     | 144 ++++++-\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n t/meson.build                          |   1 +\n t/t1017-cat-file-remote-object-info.sh | 680 +++++++++++++++++++++++++++++++++\n 6 files changed, 859 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex 86b9181599..a7fa6674c3 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -169,6 +169,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -301,7 +308,8 @@ one per line, and print information based on the command given. With\n `--batch-command`, the `info` command followed by an object will print\n information about the object the same way `--batch-check` would, and the\n `contents` command followed by an object prints contents in the same way\n-`--batch` would.\n+`--batch` would. The `remote-object-info` command followed by a remote and\n+objects IDs prints object info from the remote without downloading the objects.\n \n You can specify the information shown for each object by using a custom\n `<format>`. The `<format>` is copied literally to stdout for each\n@@ -324,15 +332,12 @@ newline. The available atoms are:\n \treports).\n \n `objectsize:disk`::\n-\tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tThe size, in bytes, that the object takes up on disk.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n-\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n-\n+\tOtherwise, expands to the null OID (all zeroes).\n `rest`::\n \tIf this atom is used in the output string, input lines are split\n \tat the first whitespace boundary. All characters before that\n@@ -340,8 +345,14 @@ newline. The available atoms are:\n \tafter that first run of whitespace (i.e., the \"rest\" of the\n \tline) are output in place of the `%(rest)` atom.\n \n+The command `remote-object-info` only supports the `%(objectname)` and\n+`%(objectsize)` placeholders. See `CAVEATS` below for more information.\n+\n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which\n+use `%(objectname) %(objectsize)` because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified,\n+so DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -438,6 +449,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since only `%(objectname)` and `%(objectsize)` are currently\n+supported by the `remote-object-info` command. Using any other placeholder in\n+the format string will raise an error.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 1e5473ab70..eb133113c0 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -29,6 +29,22 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/*\n+ * Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8 * 1024)\n+\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE \\\n+\t(MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -633,6 +649,80 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt,\n+\t\t\t   int argc,\n+\t\t\t   const char **argv,\n+\t\t\t   struct object_info **remote_object_info,\n+\t\t\t   struct oid_array *object_info_oids)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstruct transport *gtransport;\n+\n+\t/*\n+\t * TODO: Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT.\n+\t */\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid)) {\n+\t\t\tsize_t len = strlen(argv[i]);\n+\n+\t\t\tif (len < the_hash_algo->hexsz && len >= 4) {\n+\t\t\t\tsize_t j;\n+\t\t\t\tfor (j = 0; j < len; j++)\n+\t\t\t\t\tif (!isxdigit(argv[i][j]))\n+\t\t\t\t\t\tbreak;\n+\t\t\t\tif (j == len)\n+\t\t\t\t\tdie(_(\"remote-object-info does not support \"\n+\t\t\t\t\t      \"short oids, %d characters required\"),\n+\t\t\t\t\t    (int)the_hash_algo->hexsz);\n+\t\t\t}\n+\t\t\tdie(_(\"not a valid object name '%s'\"), argv[i]);\n+\t\t}\n+\t\toid_array_append(object_info_oids, &oid);\n+\t}\n+\n+\tif (!object_info_oids->nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\n+\tif (!gtransport->smart_options) {\n+\t\tretval = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n+\tgtransport->smart_options->object_info_oids = object_info_oids;\n+\n+\t/* 'objectsize' is the only option currently supported */\n+\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\tstring_list_append(&object_info_options, \"size\");\n+\n+\tif (object_info_options.nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\t\tretval = transport_fetch_object_info(gtransport);\n+\t}\n+cleanup:\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -714,6 +804,57 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\tstruct object_info *remote_object_info = NULL;\n+\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command too long\"));\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n+\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n+\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n+\t\t    MAX_ALLOWED_OBJ_LIMIT);\n+\n+\tif (get_remote_info(opt, count, argv, &remote_object_info,\n+\t\t\t    &object_info_oids))\n+\t\tgoto cleanup;\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t} else {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+\toid_array_clear(&object_info_oids);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -745,8 +886,9 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1 },\n \t{ \"info\", parse_cmd_info, 1 },\n-\t{ \"flush\", NULL, 0 },\n \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n+\t{ \"flush\", NULL, 0 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex e3d92bbda2..9928e82e0b 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1694,3 +1694,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)\n \n \treturn &transaction->base;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+}\ndiff --git a/odb.h b/odb.h\nindex 3834a0dcbf..42e3934035 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -573,4 +573,7 @@ void parse_alternates(const char *string,\n \t\t      const char *relative_base,\n \t\t      struct strvec *out);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* ODB_H */\ndiff --git a/t/meson.build b/t/meson.build\nindex 3219264fe7..54d21111a3 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -170,6 +170,7 @@ integration_tests = [\n   't1014-read-tree-confusing.sh',\n   't1015-read-index-unmerged.sh',\n   't1016-compatObjectFormat.sh',\n+  't1017-cat-file-remote-object-info.sh',\n   't1020-subdirectory.sh',\n   't1022-read-tree-partial-clone.sh',\n   't1050-large.sh',\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..49b6660934\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,680 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n+\n+unstored_content=\"Hello Git\"\n+unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not support short oids' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_short_oid\n+\t\tEOF\n+\t\ttest_grep \"does not support short oids\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not die on missing oid like info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tgit cat-file --batch-command >local <<-EOF &&\n+\t\tinfo $unstored_oid\n+\t\tEOF\n+\t\tgit cat-file --batch-command >remote <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $unstored_oid\n+\t\tEOF\n+\t\ttest_cmp local remote\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n\n-- \n2.54.0\n"},{"id":"546893","messageId":"20260701-ps-eric-work-rebase-v15-12-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 12/13] cat-file: validate remote atoms with an allow-list","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:46Z","receivedAt":"2026-07-01T12:19:06Z","isPatch":true,"body":"`strstr()` is not enough to validate the format placeholders in\n`remote-object-info` causing two errors:\n\n1. Atoms recognized by `expand_atom()` but the remote doesn't returns 1,\n   but `data->type` contains garbage causing segfault.\n\n2. `expand_atom()` returns 0 for unknown atoms, calling\n   `strbuf_expand_bad_format()` which ends up dying, blocking local\n   queries if the same format is shared.\n\nAdd an allow-list with the supported atoms at the top of `expand_atom()`.\nIn remote mode, unsupported atoms return 1 leaving the buffer empty,\nhonoring how `for-each-ref` handles known but inapplicable atoms.\n\nAs extra safety, initialize `data->type` to `OBJ_BAD` and add a `NULL`\ncheck for `type_name()` so uninitialized data doesn't cause segfault.\n\nUpdate tests that expect previous `die()` behavior to expect an empty\nstring and add an explicit test for empty string return on unknown\nplaceholder.\n\nUpdate cat-file command documentation regarding `remote-object-info`.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  2 +-\n builtin/cat-file.c                     | 41 +++++++++++++++++++++++++++-------\n t/t1017-cat-file-remote-object-info.sh | 27 ++++++++++++++++++----\n 3 files changed, 57 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex a7fa6674c3..643eac9245 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -451,7 +451,7 @@ CAVEATS\n \n Note that since only `%(objectname)` and `%(objectsize)` are currently\n supported by the `remote-object-info` command. Using any other placeholder in\n-the format string will raise an error.\n+the format string will return an empty string in its position.\n \n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex eb133113c0..eb7c5ba489 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -333,8 +333,18 @@ struct expand_data {\n \t * optimized out.\n \t */\n \tunsigned skip_object_info : 1;\n+\n+\t/*\n+\t * Flags about when an object info is being fetched from remote.\n+\t */\n+\tunsigned is_remote:1;\n+};\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n+\n+static const char *remote_object_info_atoms[] = {\n+\t\"objectname\",\n+\t\"objectsize\",\n };\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -345,14 +355,31 @@ static int is_atom(const char *atom, const char *s, int slen)\n static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n+\tif (data->is_remote) {\n+\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n+\t\tfor (i = 0; i < allowed_nr; i++)\n+\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * On remote, skip unsupported atoms returning an empty sb,\n+\t\t * honoring how for-each-ref handles known but inapplicable\n+\t\t * atoms (e.g. %(tagger)).\n+\t\t */\n+\t\tif (i == allowed_nr)\n+\t\t\treturn 1;\n+\t}\n+\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n \t\t\tstrbuf_add_oid_hex(sb, &data->oid);\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n-\t\tif (data->mark_query)\n+\t\tif (data->mark_query) {\n \t\t\tdata->info.typep = &data->type;\n-\t\telse\n-\t\t\tstrbuf_addstr(sb, type_name(data->type));\n+\t\t} else {\n+\t\t\tconst char *t = type_name(data->type);\n+\t\t\tstrbuf_addstr(sb, t ? t : \"\");\n+\t\t}\n \t} else if (is_atom(\"objectsize\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.sizep = &data->size;\n@@ -706,10 +733,6 @@ static int get_remote_info(struct batch_options *opt,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\t/* 'objectsize' is the only option currently supported */\n-\tif (!strstr(opt->format, \"%(objectsize)\"))\n-\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n-\n \tstring_list_append(&object_info_options, \"size\");\n \n \tif (object_info_options.nr > 0) {\n@@ -839,7 +862,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n \t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tdata->is_remote = 1;\n \t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t\tdata->is_remote = 0;\n \t\t} else {\n \t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n \t\t}\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 49b6660934..6bc863c391 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -236,6 +236,21 @@ test_expect_success 'remote-object-info does not die on missing oid like info' '\n \t)\n '\n \n+# This tests depends on %(objecttype) not being supported yet, once supported\n+# it needs to be updated.\n+test_expect_success 'unsupported placeholder on remote returns empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"\" >expect &&\n+\t\tgit cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n # Test --batch-command remote-object-info with 'git://' and\n # transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n@@ -575,10 +590,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (obje\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\techo \"$hello_oid \" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize:disk)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n@@ -587,10 +604,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (delt\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(deltabase)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n\n-- \n2.54.0\n"},{"id":"546895","messageId":"20260701-ps-eric-work-rebase-v15-13-c88a43b63917@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v15 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-01T12:18:47Z","receivedAt":"2026-07-01T12:19:07Z","isPatch":true,"body":"The static allow-list in expand_atom() is hardcoded to only allow\n\"objectname\" and \"objectsize\" for remote queries. This works because\nup to this point all servers will either support object-info with name\nand size or they do not support them at all, but we cannot expect that\nin a future different servers with different git versions to have the\nsame object-info capabilities. Therefore, the allow_list needs to be\ndynamic depending on what the server advertises.\n\nThe client will now:\n\n1. Request the protocol option that the placeholder refers to (i.e.\n   \"size\" when \"%(objectsize)\").\n\n2. Filters the request in fetch_object_info() dropping any option that\n   the server does not advertise.\n\n3. After the fetching, the options that haven't been dropped are the ones\n   fetched and supported by the server, these supported options are\n   mapped and remote_allowed_atoms is populated with the placeholders.\n\n4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n   the static allow_list had.\n\nMove object_info_options out of get_remote_info so the caller which has\ndata can select what options will be requested instead of requesting\nalways size.\nMove batch_object_write() out so there will always be an output even if\nall the placeholders are not supported by the server (returns an empty\nline).\n\nInclude \"type\" in the object_info_options so once the server supports\nit, the clients know already how to request it.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c  | 97 +++++++++++++++++++++++++++++++++++------------------\n fetch-object-info.c | 20 +++++++++++\n 2 files changed, 84 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex eb7c5ba489..eee8cbb7c9 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -338,13 +338,11 @@ struct expand_data {\n \t * Flags about when an object info is being fetched from remote.\n \t */\n \tunsigned is_remote:1;\n-};\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n \n-static const char *remote_object_info_atoms[] = {\n-\t\"objectname\",\n-\t\"objectsize\",\n+\tstruct string_list remote_allowed_atoms;\n };\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -356,17 +354,11 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n \tif (data->is_remote) {\n-\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n-\t\tfor (i = 0; i < allowed_nr; i++)\n-\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\tsize_t i;\n+\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n \t\t\t\tbreak;\n-\n-\t\t/*\n-\t\t * On remote, skip unsupported atoms returning an empty sb,\n-\t\t * honoring how for-each-ref handles known but inapplicable\n-\t\t * atoms (e.g. %(tagger)).\n-\t\t */\n-\t\tif (i == allowed_nr)\n+\t\tif (i == data->remote_allowed_atoms.nr)\n \t\t\treturn 1;\n \t}\n \n@@ -680,12 +672,12 @@ static int get_remote_info(struct batch_options *opt,\n \t\t\t   int argc,\n \t\t\t   const char **argv,\n \t\t\t   struct object_info **remote_object_info,\n-\t\t\t   struct oid_array *object_info_oids)\n+\t\t\t   struct oid_array *object_info_oids,\n+\t\t\t   struct string_list *object_info_options)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n \tstruct object_id oid;\n-\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tstruct transport *gtransport;\n \n \t/*\n@@ -733,15 +725,12 @@ static int get_remote_info(struct batch_options *opt,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\tstring_list_append(&object_info_options, \"size\");\n-\n-\tif (object_info_options.nr > 0) {\n-\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tif (object_info_options->nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = object_info_options;\n \t\tgtransport->smart_options->object_info_data = *remote_object_info;\n \t\tretval = transport_fetch_object_info(gtransport);\n \t}\n cleanup:\n-\tstring_list_clear(&object_info_options, 0);\n \ttransport_disconnect(gtransport);\n \treturn retval;\n }\n@@ -827,6 +816,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+struct protocol_placeholder_entry {\n+\tconst char *option;\n+\tconst char *atom;\n+};\n+\n+static const struct protocol_placeholder_entry remote_atom_map[] = {\n+\t{\"size\", \"objectsize\"},\n+\t{\"type\", \"objecttype\"},\n+\t/*\n+\t * Add new protocol options here. Even if the server doesn't support\n+\t * them the allow_list will drop them if the server doesn't advertise\n+\t * them.\n+\t */\n+};\n+\n static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n@@ -836,6 +840,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct object_info *remote_object_info = NULL;\n \tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \n \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n \t\tdie(_(\"remote-object-info command too long\"));\n@@ -848,32 +853,57 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n+\tif (data->info.sizep)\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\tif (data->info.typep)\n+\t\tstring_list_append(&object_info_options, \"type\");\n+\n \tif (get_remote_info(opt, count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids))\n+\t\t\t    &object_info_oids, &object_info_options))\n \t\tgoto cleanup;\n \n+\tstring_list_clear(&data->remote_allowed_atoms, 0);\n+\tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n+\tfor (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n+\t\tif (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n+\t\t\tstring_list_append(&data->remote_allowed_atoms,\n+\t\t\t\t\t   remote_atom_map[i].atom);\n+\n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tint found = 0;\n \t\tdata->oid = object_info_oids.oid[i];\n+\t\t/*\n+\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t * information from server without downloading them.\n+\t\t */\n \t\tif (remote_object_info[i].sizep) {\n-\t\t\t/*\n-\t\t\t * When reaching here, it means remote-object-info can retrieve\n-\t\t\t * information from server without downloading them.\n-\t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n-\t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tdata->is_remote = 1;\n-\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n-\t\t\tdata->is_remote = 0;\n-\t\t} else {\n-\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t\tfound = 1;\n \t\t}\n+\n+\t\tif (remote_object_info[i].typep) {\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\t\t\tfound = 1;\n+\t\t}\n+\n+\t\tif (!found && object_info_options.nr > 0) {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid),\n+\t\t\t\t\t     &data->oid, \"missing\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tdata->is_remote = 1;\n+\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n \n cleanup:\n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n+\tstring_list_clear(&object_info_options, 0);\n \tfree(line_to_split);\n \tfree(argv);\n \tfree(remote_object_info);\n@@ -1189,6 +1219,7 @@ static int batch_objects(struct batch_options *opt)\n  cleanup:\n \tstrbuf_release(&input);\n \tstrbuf_release(&output);\n+\tstring_list_clear(&data.remote_allowed_atoms, 0);\n \tcfg->warn_on_object_refname_ambiguity = save_warning;\n \treturn retval;\n }\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 03cfb70338..e968341676 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -41,6 +41,26 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \tcase protocol_v2:\n \t\tif (!server_supports_v2(\"object-info\"))\n \t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\t/*\n+\t\t * When removing an element from the list it gets swapped by the\n+\t\t * last element, iterate backwards to prevent elements skipping\n+\t\t * evaluation.\n+\t\t *\n+\t\t * object_info_options->nr can be safely casted without overflow\n+\t\t * beacuse the number of options is a small known number (the\n+\t\t * supported placeholders which currently are size and type).\n+\t\t */\n+\t\tfor (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)\n+\t\t\tif (!server_supports_feature(\"object-info\",\n+\t\t\t\t\t\t     args->object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(args->object_info_options, i, 0);\n+\t\t/*\n+\t\t * If no options are left after the filtering, avoid unnecessary\n+\t\t * request to the server.\n+\t\t */\n+\t\tif (!args->object_info_options->nr)\n+\t\t\treturn 0;\n+\n \t\tsend_object_info_request(fd_out, args);\n \t\tbreak;\n \tcase protocol_v1:\n\n-- \n2.54.0\n"},{"id":"546926","messageId":"xmqqse62obwh.fsf@gitster.g","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-2-c88a43b63917@gmail.com","subject":"Re: [PATCH GSoC v15 02/13] git-compat-util: add `strtoumax_szt()` with error handling","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-01T17:30:38Z","receivedAt":"2026-07-01T17:30:42Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> From: Eric Ju <eric.peijian@gmail.com>\n>\n> We already have `strtoul_ui()` and similar functions that provide proper\n> error handling using `strtoul` from the standard library. However,\n> there isn't currently a variant that returns a `size_t`.\n>\n> Using `strtoul` is unreliable because `size_`t is platform-dependent,\n> `unsigned long` could be too big to fit into a `size_t` or too small to\n> hold a `size_t`.\n\nIt is somehow annoying to see that the commit log desciption, which\nis *clearly* meant to be plaintext, is so heavily riddled with\nbackquoted references to code/program symbols.  Yes, `literal` is a\ncorrect way to format them in both AsciiDoc and Markdown, so we very\nmuch welcome them in our documentation, but not in proposed log\nmessages.\n\nIn any case, you dropped 't' in 'size_t' outside the pair of\nbackquotes.\n\n> Use `strtoumax` which returns a `uintmax_t` guaranteed to be at least as\n\n`strtoumax()`, as the convention you used above for strtoul_ui() is\nto suffix function names with ().\n\n> large as `size_t`, add a range check against `SIZE_MAX` to prevent\n> `size_t` overflow.\n\nOK.\n\n> This variant is needed in a subsequent commit to enable returning a\n> `size_t` with proper error handling.\n>\n> Mentored-by: Karthik Nayak <karthik.188@gmail.com>\n> Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  git-compat-util.h | 20 ++++++++++++++++++++\n>  1 file changed, 20 insertions(+)\n>\n> diff --git a/git-compat-util.h b/git-compat-util.h\n> index 8809776407..5ecce5bbd2 100644\n> --- a/git-compat-util.h\n> +++ b/git-compat-util.h\n> @@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n>  \treturn 0;\n>  }\n>  \n> +/*\n> + * Convert a string to a size_t using the standard library's strtoumax, with\n> + * additional error handling to ensure robustness.\n> + */\n> +static inline int strtoumax_szt(char const *s, int base, size_t *result)\n> +{\n> +\tuintmax_t uim;\n> +\tchar *p;\n> +\n> +\terrno = 0;\n> +\t/* negative values would be accepted by strtoul */\n> +\tif (strchr(s, '-'))\n> +\t\treturn -1;\n\nHmph, wouldn't \n\n\tif (*s == '-' || !*s)\n\t\treturn -1\n\ncut it?  Since your call to strtoumax() checks that the string was\nparsed to the end by insisting *p is NUL?\n\nIf you are trying to more explicitly insist that s[] has only\ndigits, which may not be a bad idea, as that is what we generally\nexpect, then\n\n\tif (!s[0] || s[strspn(s, \"0123456789\")])\n\t\treturn -1;\n\nperhaps.\n\n> +\tuim = strtoumax(s, &p, base);\n> +\tif ((errno || *p || p == s) || uim > SIZE_MAX)\n> +\t\treturn -1;\n\nAnd with !s[0] upfront, we can discard (p==s) case from here.  Other\nstrto*() wrappers we have may need the \"cannot be empty\" check,\nbecause they do not need any upfront validation of s[] like we do\nhere (we do so to reject negative numbers), but since we do need to\ncheck s[] before calling the system strto*() function anyway, it is\nOK to be different here from the others.\n\nIf uintmax_t and size_t are of the same width, then (SIZE_MAX < uim)\nbecomes mathmatically impossible, but hopefully no compiler or\nstatic checker is stupid enough to warn against it.\n\n> +\t*result = uim;\n> +\treturn 0;\n> +}\n> +\n>  static inline int strtol_i(char const *s, int base, int *result)\n>  {\n>  \tlong ul;\n"},{"id":"546934","messageId":"xmqq7bneo9mh.fsf@gitster.g","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-5-c88a43b63917@gmail.com","subject":"Re: [PATCH GSoC v15 05/13] fetch-pack: drop static `advertise_sid` variable","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-01T18:19:50Z","receivedAt":"2026-07-01T18:19:54Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> `write_fetch_command_and_capabilities()` is moved to `connect.c` in a\n> subsequent commit. To prepare for that, drop the static variable usage\n> of `advertise_sid`. Currently `advertise_sid` is used in two places:\n>\n> 1. In function `do_fetch_pack()`:\n>         if (!server_supports(\"session_id\"))\n>                advertise_sid = 0;\n> 2. In function `fetch_pack_config()`:\n>         repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n>\n> Since `do_fetch_pack()` is only relevant for protocol v1, it can be\n> ignored because `write_fetch_command_and_capabilities()` is only used in\n> protocol v2.\n>\n> About 2, call `repo_config_get_bool()` directly inside of the function.\n\nPuzzled.  The patch does introduce a local on-stack variable with\nthe same name, but does not remove the file-scope global one.\n\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index f13951d154..ad07603755 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1380,6 +1380,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>  \t\t\t\t\t\t const struct string_list *server_options)\n>  {\n>  \tconst char *hash_name;\n> +\tint advertise_sid;\n> +\n> +\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n\nIf there is no such configuration variable defined anywhere in the\nsystem, advertise_sid will be left uninitialied.  Wouldn't it cause\nproblems later.\n\nInitialize it to 0 (if the default is not to advertise, which I\nthink is the case but please double check), and you would be OK,\nprobably?\n\n>  \tensure_server_supports_v2(\"fetch\");\n>  \tpacket_buf_write(req_buf, \"command=fetch\");\n> @@ -1395,7 +1398,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>  \t}\n>  \n>  \tif (server_feature_v2(\"object-format\", &hash_name)) {\n> -\t\tint hash_algo = hash_algo_by_name(hash_name);\n> +\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n>  \t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>  \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n>  \t\t\t    the_hash_algo->name, hash_name);\n\n> While at it, change `hash_algo`'s type to match `hash_algo_by_name()`'s\n> actual return type (`unsigned int`) and make it `const`.\n\nThis one makes sense, but is better left out to a separate step.\nEspecially when the primary focus of the change is iffy (see above).\n"},{"id":"547314","messageId":"CAN5EUNTYeDrQMor29eYMhJD0jcdRQq36ZA6BgupV8gG9xs9rFQ@mail.gmail.com","threadId":"61695","inReplyTo":"xmqqse62obwh.fsf@gitster.g","subject":"Re: [PATCH GSoC v15 02/13] git-compat-util: add `strtoumax_szt()` with error handling","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-07T08:50:41Z","receivedAt":"2026-07-07T08:50:53Z","isPatch":true,"body":"El mié, 1 jul 2026 a las 19:30, Junio C Hamano (<gitster@pobox.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> > From: Eric Ju <eric.peijian@gmail.com>\n> >\n> > We already have `strtoul_ui()` and similar functions that provide proper\n> > error handling using `strtoul` from the standard library. However,\n> > there isn't currently a variant that returns a `size_t`.\n> >\n> > Using `strtoul` is unreliable because `size_`t is platform-dependent,\n> > `unsigned long` could be too big to fit into a `size_t` or too small to\n> > hold a `size_t`.\n>\n> It is somehow annoying to see that the commit log desciption, which\n> is *clearly* meant to be plaintext, is so heavily riddled with\n> backquoted references to code/program symbols.  Yes, `literal` is a\n> correct way to format them in both AsciiDoc and Markdown, so we very\n> much welcome them in our documentation, but not in proposed log\n> messages.\n>\n> In any case, you dropped 't' in 'size_t' outside the pair of\n> backquotes.\n\nOk, I'll drop the backquotes for the commit messages of this series.\n\n>\n> > Use `strtoumax` which returns a `uintmax_t` guaranteed to be at least as\n>\n> `strtoumax()`, as the convention you used above for strtoul_ui() is\n> to suffix function names with ().\n\nACK.\n\n>\n> > large as `size_t`, add a range check against `SIZE_MAX` to prevent\n> > `size_t` overflow.\n>\n> OK.\n>\n> > This variant is needed in a subsequent commit to enable returning a\n> > `size_t` with proper error handling.\n> >\n> > Mentored-by: Karthik Nayak <karthik.188@gmail.com>\n> > Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\n> > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> > ---\n> >  git-compat-util.h | 20 ++++++++++++++++++++\n> >  1 file changed, 20 insertions(+)\n> >\n> > diff --git a/git-compat-util.h b/git-compat-util.h\n> > index 8809776407..5ecce5bbd2 100644\n> > --- a/git-compat-util.h\n> > +++ b/git-compat-util.h\n> > @@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n> >       return 0;\n> >  }\n> >\n> > +/*\n> > + * Convert a string to a size_t using the standard library's strtoumax, with\n> > + * additional error handling to ensure robustness.\n> > + */\n> > +static inline int strtoumax_szt(char const *s, int base, size_t *result)\n> > +{\n> > +     uintmax_t uim;\n> > +     char *p;\n> > +\n> > +     errno = 0;\n> > +     /* negative values would be accepted by strtoul */\n> > +     if (strchr(s, '-'))\n> > +             return -1;\n>\n> Hmph, wouldn't\n>\n>         if (*s == '-' || !*s)\n>                 return -1\n>\n> cut it?  Since your call to strtoumax() checks that the string was\n> parsed to the end by insisting *p is NUL?\n>\n> If you are trying to more explicitly insist that s[] has only\n> digits, which may not be a bad idea, as that is what we generally\n> expect, then\n>\n>         if (!s[0] || s[strspn(s, \"0123456789\")])\n>                 return -1;\n>\n> perhaps.\n\nI like the idea of only digits but, even though in this series I only\nuse this function in base 10, I want the function to work in other\nbases, that's why I left the base in the function signature instead of\nhardcoding it. strspn(s, \"0123456789\") rejects bases >10  (\"ff\" for\nbase 16) while strtoumax does support higher ones.\nI think that it would be better to explicitly reject what we don't\nwant similarly to \"-\":\n\nif (!*s || isspace((unsigned char)*s) || *s == '-' || *s == '+')\n        return -1;\n\nAbout that, strtoumax works fine with \"+\" and ignores starting\nwhitespaces, but for consistency (we reject \"-\" and whitespaces\nbetween or at the end) rejecting whitespaces and +/- will be better\nand make the caller format it correctly.\n\nI'll do that for the next version.\n\n>\n> > +     uim = strtoumax(s, &p, base);\n> > +     if ((errno || *p || p == s) || uim > SIZE_MAX)\n> > +             return -1;\n>\n> And with !s[0] upfront, we can discard (p==s) case from here.  Other\n> strto*() wrappers we have may need the \"cannot be empty\" check,\n> because they do not need any upfront validation of s[] like we do\n> here (we do so to reject negative numbers), but since we do need to\n> check s[] before calling the system strto*() function anyway, it is\n> OK to be different here from the others.\n\nAgreed\n\n>\n> If uintmax_t and size_t are of the same width, then (SIZE_MAX < uim)\n> becomes mathmatically impossible, but hopefully no compiler or\n> static checker is stupid enough to warn against it.\n>\n> > +     *result = uim;\n> > +     return 0;\n> > +}\n> > +\n> >  static inline int strtol_i(char const *s, int base, int *result)\n> >  {\n> >       long ul;\n\nThanks for the feedback,\nPablo\n"},{"id":"547377","messageId":"xmqqcxwy4qp5.fsf@gitster.g","threadId":"61695","inReplyTo":"CAN5EUNTYeDrQMor29eYMhJD0jcdRQq36ZA6BgupV8gG9xs9rFQ@mail.gmail.com","subject":"Re: [PATCH GSoC v15 02/13] git-compat-util: add `strtoumax_szt()` with error handling","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-07T18:09:26Z","receivedAt":"2026-07-07T18:09:29Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n>> If you are trying to more explicitly insist that s[] has only\n>> digits, which may not be a bad idea, as that is what we generally\n>> expect, then\n>>\n>>         if (!s[0] || s[strspn(s, \"0123456789\")])\n>>                 return -1;\n>>\n>> perhaps.\n>\n> I like the idea of only digits but, even though in this series I only\n> use this function in base 10, I want the function to work in other\n> bases, that's why I left the base in the function signature instead of\n> hardcoding it. strspn(s, \"0123456789\") rejects bases >10  (\"ff\" for\n> base 16) while strtoumax does support higher ones.\n> I think that it would be better to explicitly reject what we don't\n> want similarly to \"-\":\n\nLet's step back a bit and think.\n\nWhere do we plan to use this function?  Remember that being a\nsuperset is not always necessarily good for a helper function that\nserves as a format checker.\n\nIn the output of \"git diff master...ps/cat-file-remote-object-info\",\nthere is only one caller, which is fetch_object_info().  It reads\ninto object_info_data[].sizep.  Do we expect to express the object\nsize in anything but an unsigned decimal integer?  Remember that it\nis better to be unambiguous when designing a protocol.  We do not\nwant a third-party reimplementation of whatever is talking to\nfetch_object_info() to send object size in hex ;-).\n\nIt may also be usable to parse the size of the object payload in\nobject-file.c::parse_loose_header() but notice that it is already\neven stricter not to use strto<anything> system function and instead\nhandcrafts the trivial number parsing.  This would avoid system\ndependent funnyness, which is a good thing.\n\n> if (!*s || isspace((unsigned char)*s) || *s == '-' || *s == '+')\n>         return -1;\n>\n> About that, strtoumax works fine with \"+\" and ignores starting\n> whitespaces, but for consistency (we reject \"-\" and whitespaces\n> between or at the end) rejecting whitespaces and +/- will be better\n> and make the caller format it correctly.\n>\n> I'll do that for the next version.\n"},{"id":"547392","messageId":"CAN5EUNQ=2qtKXSJvxQiNLYqx0N0m6sfyBGLLXm4FB1kwtOsdbQ@mail.gmail.com","threadId":"61695","inReplyTo":"xmqqcxwy4qp5.fsf@gitster.g","subject":"Re: [PATCH GSoC v15 02/13] git-compat-util: add `strtoumax_szt()` with error handling","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-07T19:53:04Z","receivedAt":"2026-07-07T19:53:16Z","isPatch":true,"body":"El mar, 7 jul 2026 a las 20:09, Junio C Hamano (<gitster@pobox.com>) escribió:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> >> If you are trying to more explicitly insist that s[] has only\n> >> digits, which may not be a bad idea, as that is what we generally\n> >> expect, then\n> >>\n> >>         if (!s[0] || s[strspn(s, \"0123456789\")])\n> >>                 return -1;\n> >>\n> >> perhaps.\n> >\n> > I like the idea of only digits but, even though in this series I only\n> > use this function in base 10, I want the function to work in other\n> > bases, that's why I left the base in the function signature instead of\n> > hardcoding it. strspn(s, \"0123456789\") rejects bases >10  (\"ff\" for\n> > base 16) while strtoumax does support higher ones.\n> > I think that it would be better to explicitly reject what we don't\n> > want similarly to \"-\":\n>\n> Let's step back a bit and think.\n>\n> Where do we plan to use this function?  Remember that being a\n> superset is not always necessarily good for a helper function that\n> serves as a format checker.\n>\n> In the output of \"git diff master...ps/cat-file-remote-object-info\",\n> there is only one caller, which is fetch_object_info().  It reads\n> into object_info_data[].sizep.  Do we expect to express the object\n> size in anything but an unsigned decimal integer?  Remember that it\n> is better to be unambiguous when designing a protocol.  We do not\n> want a third-party reimplementation of whatever is talking to\n> fetch_object_info() to send object size in hex ;-).\n\nNo haha, we don't want size being sent in hex :), I agree that it is\nbetter to be unambiguous. We could hardcode the base 10 but I feel\nthat calling the function strtoumax_szt() when it does not support >10\nbase (or I hardcode the base to be 10) lies to a future developer that\ntries to use this function thinking that it behaves as strtoumax_*().\n\nMaybe because it is called only once in this series it is better to\nhave a static function close to its caller that explicitly does what\nwe want and it is unambiguous.\n\nIf that sounds reasonable I'll move this function to the commit where\nit's called, call the function parse_object_size() and keep the strict\ndigits only with strspn proposed.\n\n>\n> It may also be usable to parse the size of the object payload in\n> object-file.c::parse_loose_header() but notice that it is already\n> even stricter not to use strto<anything> system function and instead\n> handcrafts the trivial number parsing.  This would avoid system\n> dependent funnyness, which is a good thing.\n>\n> > if (!*s || isspace((unsigned char)*s) || *s == '-' || *s == '+')\n> >         return -1;\n> >\n> > About that, strtoumax works fine with \"+\" and ignores starting\n> > whitespaces, but for consistency (we reject \"-\" and whitespaces\n> > between or at the end) rejecting whitespaces and +/- will be better\n> > and make the caller format it correctly.\n> >\n> > I'll do that for the next version.\n\nThanks,\nPablo\n"},{"id":"547762","messageId":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260701-ps-eric-work-rebase-v15-0-c88a43b63917@gmail.com","subject":"[PATCH GSoC v16 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:06Z","receivedAt":"2026-07-10T16:41:23Z","isPatch":true,"body":"This patch series is a continuation of Eric Ju's\n(eric.peijian@gmail.com) and Calvin Wan's (calvinwan@google.com) patch\nseries [1] and [2] respectively.\n\nSometimes it is beneficial to retrieve information about an object\nwithout having to download it completely. The server logic for\nretrieving size has already been implemented and merged in a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20) [3].\nThis patch series implement the client option for it.\n\nEric's series adds the remote-object-info command to cat-file\n--batch-command. This command allows the client to make an object-info\ncommand request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info\ncapability, cat-file --batch-command will die.\n\nIf a user attempts to use remote-object-info with protocol v1, cat-file\n--batch-command will die.\n\nCurrently, only the size (%(objectsize)) is supported end to end in this\nimplementation. The type (%(objecttype)) is known by the client's\nallow-list and request path but is not supported on the server side\nnor the response parsing. A follow up series will add full end-to-end\nsupport for %(objecttype).\n\nThe default format for remote-object-info is set to \"%(objectname)\n%(objectsize)\". Once %(objecttype) is supported, the default format will\nbe unified accordingly.\n\nIf the batch command format includes unsupported fields such as\n%(objecttype), %(objectsize:disk), or %(deltabase), the command will\nreturn empty strings for each unsupported field.\n\nThis series completes Eric's work mainly with the refactor of the\nvalidation of the placeholder with an allow-list that filters what the\nclient asks with what the server is capable of provide following Jeff\nKing's idea [4].\n\nGitHub CI: https://github.com/pabloosabaterr/git/actions/runs/29091116939\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n[4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n\nChanges since v15:\n- Completely dropped the static advertise_sid variable at fetch-pack.c\n- Split the hash_algo type change into its own commit.\n- Removed strtoumax_szt() from git-compat-util.h (and its commit) into a\n  static parse_object_size() helper.\n- Removed backquotes from commit message bodies and fixed typos.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nCalvin Wan (3):\n      fetch-pack: move fetch initialization\n      serve: advertise object-info feature\n      transport: add client support for object-info\n\nEric Ju (3):\n      cat-file: declare loop counter inside for()\n      t1006: split test utility functions into new 'lib-cat-file.sh'\n      cat-file: add remote-object-info to batch-command\n\nPablo Sabater (7):\n      transport-helper: fix memory leak of helper on disconnect\n      fetch-pack: fix hash_algo variable type\n      fetch-pack: drop static advertise_sid variable\n      fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n      connect: make write_fetch_command_and_capabilities() more generic\n      cat-file: validate remote atoms with an allow-list\n      cat-file: make remote-object-info allow-list dynamic\n\n Documentation/git-cat-file.adoc        |  29 +-\n Documentation/gitprotocol-v2.adoc      |  11 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 221 ++++++++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    | 129 ++++++\n fetch-object-info.h                    |  22 ++\n fetch-pack.c                           |  58 +--\n fetch-pack.h                           |   1 +\n meson.build                            |   1 +\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n serve.c                                |   5 +-\n t/lib-cat-file.sh                      |  16 +\n t/meson.build                          |   1 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 699 +++++++++++++++++++++++++++++++++\n transport-helper.c                     |  15 +-\n transport-internal.h                   |   8 +\n transport.c                            |  46 +++\n transport.h                            |  10 +\n 22 files changed, 1255 insertions(+), 86 deletions(-)\n\nbase-commit: f60db8d575adb79761d363e026fb49bddf330c73\n"},{"id":"547763","messageId":"20260710-ps-eric-work-rebase-v16-1-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 01/13] transport-helper: fix memory leak of helper on disconnect","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:07Z","receivedAt":"2026-07-10T16:41:24Z","isPatch":true,"body":"disconnect_helper() only frees data inside of the if(data->helper) block\n[1]. When the transport is disconnected without the helper being fully\nstarted, data->name allocated in transport_helper_init()\nis never freed.\n\nMove FREE_AND_NULL(data->name) outside the conditional block so it's\nalways freed on disconnect.\n\n[1]: https://lore.kernel.org/git/05fbadbae2184479c87c37675dde7bd79b3e32ab.1716465556.git.ps@pks.im/\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n transport-helper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 80f90eb7ba..f195070788 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tres = finish_command(data->helper);\n-\t\tFREE_AND_NULL(data->name);\n \t\tFREE_AND_NULL(data->helper);\n \t}\n+\tFREE_AND_NULL(data->name);\n \treturn res;\n }\n \n\n-- \n2.54.0\n"},{"id":"547764","messageId":"20260710-ps-eric-work-rebase-v16-2-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 02/13] cat-file: declare loop counter inside for()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:08Z","receivedAt":"2026-07-10T16:41:26Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSome code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\nWhile at it, we also change its type from int to size_t where the\nlatter makes more sense.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c | 13 ++++---------\n fetch-pack.c       |  3 +--\n 2 files changed, 5 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 60869b8b37..26ad07b62c 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -721,14 +721,12 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n \t\tstruct queued_cmd *cmd,\n-\t\tint nr)\n+\t\tsize_t nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -736,9 +734,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -765,7 +761,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -775,7 +770,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 120e01f3cf..f13951d154 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n\n-- \n2.54.0\n"},{"id":"547765","messageId":"20260710-ps-eric-work-rebase-v16-3-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 03/13] t1006: split test utility functions into new 'lib-cat-file.sh'","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:09Z","receivedAt":"2026-07-10T16:41:27Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nThis refactor extracts utility functions from the cat-file's test\nscript 't1006-cat-file.sh' into a new 'lib-cat-file.sh' dedicated\nlibrary file.\n\nA subsequent commit will need this functions, the goal is to improve\ncode reuse and readability,enabling future tests to leverage these\nutilities without duplicating code.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 13 +------------\n 2 files changed, 17 insertions(+), 12 deletions(-)\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..44af232d74\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 8e2c52652c..8360f3bbd9 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-loose.sh\"\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     object_name=\"$2\"\n\n-- \n2.54.0\n"},{"id":"547766","messageId":"20260710-ps-eric-work-rebase-v16-4-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 04/13] fetch-pack: fix hash_algo variable type","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:10Z","receivedAt":"2026-07-10T16:41:28Z","isPatch":true,"body":"hash_algo_by_name() returns \"unsigned int\", but the variable that it is\nassigned to is \"int\".\n\nChange hash_algo variable type to match hash_algo_by_name() type, also\nmake it const because it is never modified.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f13951d154..eea72b2500 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1395,7 +1395,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t}\n \n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n \t\t\t    the_hash_algo->name, hash_name);\n\n-- \n2.54.0\n"},{"id":"547769","messageId":"20260710-ps-eric-work-rebase-v16-5-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 05/13] fetch-pack: drop static advertise_sid variable","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:11Z","receivedAt":"2026-07-10T16:41:29Z","isPatch":true,"body":"write_fetch_command_and_capabilities() is moved to 'connect.c' in a\nsubsequent commit. To prepare for that, drop the static variable usage\nof advertise_sid. Currently advertise_sid is used in two places:\n\n1. In function do_fetch_pack():\n        if (!server_supports(\"session-id\"))\n               advertise_sid = 0;\n\n2. In function fetch_pack_config():\n        repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, it is only relevant for v0/v1 protocol, move it into\nfind_common().\n\nAbout 2, call repo_config_get_bool() inside of\nwrite_fetch_command_and_capabilities() and find_common() replacing the\nstatic variable.\n\nBecause repo_config_get_bool() leaves advertise_sid as is if it is not\nset, initialize it to 0 matching its default.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 13 +++++++------\n 1 file changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex eea72b2500..8e04db8640 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -49,7 +49,6 @@ static int fetch_fsck_objects = -1;\n static int transfer_fsck_objects = -1;\n static int agent_supported;\n static int server_supports_filtering;\n-static int advertise_sid;\n static struct shallow_lock shallow_lock;\n static const char *alternate_shallow_file;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n@@ -363,6 +362,9 @@ static int find_common(struct fetch_negotiator *negotiator,\n \tsize_t state_len = 0;\n \tstruct packet_reader reader;\n \tstruct oidset negotiation_include_oids = OIDSET_INIT;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tif (args->stateless_rpc && multi_ack == 1)\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--stateless-rpc\", \"multi_ack_detailed\");\n@@ -414,7 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator,\n \t\t\tif (deepen_not_ok)      strbuf_addstr(&c, \" deepen-not\");\n \t\t\tif (agent_supported)    strbuf_addf(&c, \" agent=%s\",\n \t\t\t\t\t\t\t    git_user_agent_sanitized());\n-\t\t\tif (advertise_sid)\n+\t\t\tif (advertise_sid && server_supports(\"session-id\"))\n \t\t\t\tstrbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n \t\t\tif (args->filter_options.choice)\n \t\t\t\tstrbuf_addstr(&c, \" filter\");\n@@ -1160,9 +1162,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\t\t\t      (int)agent_len, agent_feature);\n \t}\n \n-\tif (!server_supports(\"session-id\"))\n-\t\tadvertise_sid = 0;\n-\n \tif (server_supports(\"shallow\"))\n \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n \telse if (args->depth > 0 || is_repository_shallow(r))\n@@ -1380,6 +1379,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t\t\t\t\t\t const struct string_list *server_options)\n {\n \tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tensure_server_supports_v2(\"fetch\");\n \tpacket_buf_write(req_buf, \"command=fetch\");\n@@ -1998,7 +2000,6 @@ static void fetch_pack_config(void)\n \trepo_config_get_bool(the_repository, \"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n \trepo_config_get_bool(the_repository, \"fetch.fsckobjects\", &fetch_fsck_objects);\n \trepo_config_get_bool(the_repository, \"transfer.fsckobjects\", &transfer_fsck_objects);\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \tif (!uri_protocols.nr) {\n \t\tchar *str;\n \n\n-- \n2.54.0\n"},{"id":"547767","messageId":"20260710-ps-eric-work-rebase-v16-6-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 06/13] fetch-pack: move write_fetch_command_and_capabilities() to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:12Z","receivedAt":"2026-07-10T16:41:31Z","isPatch":true,"body":"write_fetch_command_and_capabilities() is refactored in a subsequent\ncommit where it becomes a more general-purpose function, making it\nmore accessible to additional commands in the future.\n\nMove write_fetch_command_and_capabilities() to 'connect.c', where\nthere are similar purpose functions.\n\nBecause string_list is only used as a pointer, use a forward\ndeclaration [1].\n\n[1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 34 ----------------------------------\n 3 files changed, 38 insertions(+), 34 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 47e39d2a73..c09947cc56 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,6 +700,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(\"fetch\");\n+\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t    the_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t    the_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n static const char *url_scheme_name(enum url_scheme scheme)\n {\n \tswitch (scheme) {\ndiff --git a/connect.h b/connect.h\nindex aa482a37fb..c4f6ea4b0a 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+struct string_list;\n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 8e04db8640..5e7c4f1d46 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1375,40 +1375,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\tint advertise_sid = 0;\n-\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n\n-- \n2.54.0\n"},{"id":"547768","messageId":"20260710-ps-eric-work-rebase-v16-7-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 07/13] connect: make write_fetch_command_and_capabilities() more generic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:13Z","receivedAt":"2026-07-10T16:41:32Z","isPatch":true,"body":"Refactor write_fetch_command_and_capabilities(), enabling it to serve\nboth fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162\n(object-info: support for retrieving object info, 2021-04-20).\n\nRefactor the function signature to accept a command instead of the\nhardcoded \"fetch\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 8 ++++----\n connect.h    | 8 ++++++--\n fetch-pack.c | 4 ++--\n 3 files changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex c09947cc56..127ed4a2e6 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,16 +700,16 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options)\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \tint advertise_sid = 0;\n \n \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\ndiff --git a/connect.h b/connect.h\nindex c4f6ea4b0a..c2bf492ed9 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -35,7 +35,11 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       const char *error);\n \n struct string_list;\n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options);\n+/*\n+ * Writes a command along with the requested server capabilities/features into a\n+ * request buffer.\n+ */\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 5e7c4f1d46..783e3474a6 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1386,7 +1386,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2253,7 +2253,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n\n-- \n2.54.0\n"},{"id":"547770","messageId":"20260710-ps-eric-work-rebase-v16-8-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 08/13] fetch-pack: move fetch initialization","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:14Z","receivedAt":"2026-07-10T16:41:34Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized in\nFETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 783e3474a6..2ab5ba108b 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1735,18 +1735,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n\n-- \n2.54.0\n"},{"id":"547771","messageId":"20260710-ps-eric-work-rebase-v16-9-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 09/13] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:15Z","receivedAt":"2026-07-10T16:41:35Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This allows a client\nto decide whether to query the server for object-info or fetch as a\nfallback.\n\nWhile at it, update the object-info section in 'gitprotocol-v2.adoc':\n- Require full obj-oid explicitly.\n- Fix parentheses.\n- Define obj-size explicitly.\n- Make obj-size optional in obj-info and document the behavior\n  for unrecognized object IDs.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/gitprotocol-v2.adoc | 11 ++++++++---\n serve.c                           |  5 ++++-\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 2beb70595f..d3530c52ea 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -568,21 +568,26 @@ An `object-info` request takes the following arguments:\n \n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n-\tinformation for.\n+\tinformation for. They must be full object IDs.\n \n The response of `object-info` is a list of the requested object ids\n and associated requested information, each separated by a single space.\n \n \toutput = info flush-pkt\n \n-\tinfo = PKT-LINE(attrs) LF)\n+\tinfo = PKT-LINE(attrs LF)\n \t\t*PKT-LINE(obj-info LF)\n \n \tattrs = attr | attrs SP attrs\n \n+\tobj-size = 1*DIGIT\n+\n \tattr = \"size\"\n \n-\tobj-info = obj-id SP obj-size\n+\tobj-info = obj-id SP [obj-size]\n+\n+\tIf the server does not recognize the object id, the response will be\n+\t`obj-id SP` regardless of the number of attributes requested.\n \n bundle-uri\n ~~~~~~~~~~\ndiff --git a/serve.c b/serve.c\nindex 49a6e39b1d..2b07d922b3 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\t/* Currently only size is supported */\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n\n-- \n2.54.0\n"},{"id":"547772","messageId":"20260710-ps-eric-work-rebase-v16-10-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 10/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:16Z","receivedAt":"2026-07-10T16:41:37Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nIntroduce client-side support for the object-info capability.\n\nAdd its own function for object-info separate from existing fetch\ninfrastructure.\n\nCurrently, the client supports requesting a list of object IDs with\nthe size feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client returns an error and exit.\n\nNote that:\n\n1. the entire request is written into req_buf before being sent to the\n   remote. This approach follows the pattern used in the\n   send_fetch_request() logic within 'fetch-pack.c'. Streaming the\n   request is not addressed in this patch.\n\n2. When the server does not recognize an OID, following the v2 protocol,\n   the server returns \"<OID> SP\", when this happens,\n   fetch_object_info() sets the corresponding size pointer to NULL so\n   that callers can detect and handle it.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Makefile             |   1 +\n fetch-object-info.c  | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h  |  22 +++++++++++\n fetch-pack.h         |   1 +\n meson.build          |   1 +\n transport-helper.c   |  13 +++++-\n transport-internal.h |   8 ++++\n transport.c          |  46 ++++++++++++++++++++++\n transport.h          |  10 +++++\n 9 files changed, 209 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 1f3f099f5c..cdabdb3771 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1159,6 +1159,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..91685cb355\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,109 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"odb.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\telse\n+\t\tBUG(\"only size should be in object_info_options\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+static size_t parse_object_size(const char *s, size_t *res)\n+{\n+\tuintmax_t uim;\n+\n+\tif (!s[0] || s[strspn(s, \"0123456789\")])\n+\t\treturn -1;\n+\terrno = 0;\n+\tuim = strtoumax(s, NULL, 10);\n+\tif (errno || uim > SIZE_MAX)\n+\t\treturn -1;\n+\t*res = uim;\n+\treturn 0;\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"unsupported protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader,\n+\t\t\t\t\t\t  \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\tsize_index = i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t} else {\n+\t\t\tBUG(\"only size is supported\");\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n+\t\tif (size_index >= 0) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\")) {\n+\t\t\t\tFREE_AND_NULL(object_info_data[i].sizep);\n+\t\t\t\tstring_list_clear(&object_info_values, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (parse_object_size(object_info_values.items[1 + size_index].string,\n+\t\t\t\t\t      object_info_data[i].sizep))\n+\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t\t    object_info_values.items[0].string,\n+\t\t\t\t    object_info_values.items[1 + size_index].string);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..d35284bd6b\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"odb.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 6d0dec7f41..0fba340a84 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\ndiff --git a/meson.build b/meson.build\nindex ca235801cf..19fad57da9 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -347,6 +347,7 @@ libgit_sources = [\n   'exec-cmd.c',\n   'fetch-negotiator.c',\n   'fetch-pack.c',\n+  'fetch-object-info.c',\n   'fmt-merge-msg.c',\n   'fsck.c',\n   'fsmonitor.c',\ndiff --git a/transport-helper.c b/transport-helper.c\nindex f195070788..f97e6d7b29 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -727,8 +727,7 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -784,6 +783,15 @@ static int fetch_refs(struct transport *transport,\n \treturn -1;\n }\n \n+static int fetch_object_info_helper(struct transport *transport)\n+{\n+\tget_helper(transport);\n+\tif (process_connect(transport, 0))\n+\t\treturn transport->vtable->fetch_object_info(transport);\n+\n+\tdie(_(\"object-info requires protocol v2\"));\n+}\n+\n struct push_update_ref_state {\n \tstruct ref *hint;\n \tstruct ref_push_report *report;\n@@ -1330,6 +1338,7 @@ static struct transport_vtable vtable = {\n \t.get_refs_list\t= get_refs_list,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs,\n+\t.fetch_object_info = fetch_object_info_helper,\n \t.push_refs\t= push_refs,\n \t.connect\t= connect_helper,\n \t.disconnect\t= release_helper\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 051f3ab0dc..60db0bedcd 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -45,6 +45,14 @@ struct transport_vtable {\n \t **/\n \tint (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);\n \n+\t/*\n+\t * Fetch object info (only size currently) from remote without\n+\t * downloading the objects.\n+\t *\n+\t * Uses object-info capability of v2 protocol.\n+\t */\n+\tint (*fetch_object_info)(struct transport *transport);\n+\n \t/**\n \t * Push the objects and refs. Send the necessary objects, and\n \t * then, for any refs where peer_ref is set and\ndiff --git a/transport.c b/transport.c\nindex fc144f0aed..3e0a6558b7 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1,3 +1,4 @@\n+#include \"compat/posix.h\"\n #define USE_THE_REPOSITORY_VARIABLE\n \n #include \"git-compat-util.h\"\n@@ -9,6 +10,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -432,6 +434,48 @@ static int get_bundle_uri(struct transport *transport)\n \t\t\t\t     transport->bundles, stateless_rpc);\n }\n \n+static int fetch_object_info_via_pack(struct transport *transport)\n+{\n+\tint ret = 0;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct packet_reader reader;\n+\tstruct object_info_args args = { 0 };\n+\n+\targs.server_options = transport->server_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\tstring_list_sort(args.object_info_options);\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tdata->version = discover_version(&reader);\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tret = fetch_object_info(data->version, &args, &reader,\n+\t\t\t\tdata->options.object_info_data,\n+\t\t\t\ttransport->stateless_rpc, data->fd[1]);\n+\n+\tclose(data->fd[0]);\n+\tif (data->fd[1] >= 0)\n+\t\tclose(data->fd[1]);\n+\tif (finish_connect(data->conn))\n+\t\tret = -1;\n+\tdata->conn = NULL;\n+\n+\treturn ret;\n+}\n+\n+int transport_fetch_object_info(struct transport *transport)\n+{\n+\tif (!transport->vtable->fetch_object_info)\n+\t\tdie(_(\"remote does not support object-info\"));\n+\treturn transport->vtable->fetch_object_info(transport);\n+}\n+\n static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t       int nr_heads, struct ref **to_fetch)\n {\n@@ -1004,6 +1048,7 @@ static struct transport_vtable taken_over_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.disconnect\t= disconnect_git\n };\n@@ -1169,6 +1214,7 @@ static struct transport_vtable builtin_smart_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.connect\t= connect_git,\n \t.disconnect\t= disconnect_git\ndiff --git a/transport.h b/transport.h\nindex 7e5867cffa..9e85a4cd35 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -6,6 +6,7 @@\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n #include \"connect.h\"\n+#include \"odb.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -55,6 +56,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n@@ -309,6 +314,11 @@ int transport_get_remote_bundle_uri(struct transport *transport);\n const struct git_hash_algo *transport_get_hash_algo(struct transport *transport);\n int transport_fetch_refs(struct transport *transport, struct ref *refs);\n \n+/*\n+ * Fetch the object info from remote\n+ */\n+int transport_fetch_object_info(struct transport *transport);\n+\n /*\n  * If this flag is set, unlocking will avoid to call non-async-signal-safe\n  * functions. This will necessarily leave behind some data structures which\n\n-- \n2.54.0\n"},{"id":"547774","messageId":"20260710-ps-eric-work-rebase-v16-11-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 11/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:17Z","receivedAt":"2026-07-10T16:41:37Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSince the info command in cat-file --batch-command prints object\ninfo for a given object, it is natural to add another command in\ncat-file --batch-command to print object info for a given object\nfrom a remote.\n\nAdd remote-object-info command to cat-file --batch-command.\n\nWhile info takes object ids one at a time, this creates overhead when\nmaking requests to a server. So remote-object-info instead can take\nmultiple object ids at once.\n\nThe cat-file --batch-command command is generally implemented in the\nfollowing manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, remote-object-info is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is remote-object-info:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode remote-object-info:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is remote-object-info:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, remote-object-info gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  29 +-\n builtin/cat-file.c                     | 144 ++++++-\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n t/meson.build                          |   1 +\n t/t1017-cat-file-remote-object-info.sh | 680 +++++++++++++++++++++++++++++++++\n 6 files changed, 859 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex 86b9181599..a7fa6674c3 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -169,6 +169,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -301,7 +308,8 @@ one per line, and print information based on the command given. With\n `--batch-command`, the `info` command followed by an object will print\n information about the object the same way `--batch-check` would, and the\n `contents` command followed by an object prints contents in the same way\n-`--batch` would.\n+`--batch` would. The `remote-object-info` command followed by a remote and\n+objects IDs prints object info from the remote without downloading the objects.\n \n You can specify the information shown for each object by using a custom\n `<format>`. The `<format>` is copied literally to stdout for each\n@@ -324,15 +332,12 @@ newline. The available atoms are:\n \treports).\n \n `objectsize:disk`::\n-\tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tThe size, in bytes, that the object takes up on disk.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n-\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n-\n+\tOtherwise, expands to the null OID (all zeroes).\n `rest`::\n \tIf this atom is used in the output string, input lines are split\n \tat the first whitespace boundary. All characters before that\n@@ -340,8 +345,14 @@ newline. The available atoms are:\n \tafter that first run of whitespace (i.e., the \"rest\" of the\n \tline) are output in place of the `%(rest)` atom.\n \n+The command `remote-object-info` only supports the `%(objectname)` and\n+`%(objectsize)` placeholders. See `CAVEATS` below for more information.\n+\n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which\n+use `%(objectname) %(objectsize)` because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified,\n+so DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -438,6 +449,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since only `%(objectname)` and `%(objectsize)` are currently\n+supported by the `remote-object-info` command. Using any other placeholder in\n+the format string will raise an error.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 26ad07b62c..77ecccdda3 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -29,6 +29,22 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/*\n+ * Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8 * 1024)\n+\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE \\\n+\t(MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -636,6 +652,80 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt,\n+\t\t\t   int argc,\n+\t\t\t   const char **argv,\n+\t\t\t   struct object_info **remote_object_info,\n+\t\t\t   struct oid_array *object_info_oids)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstruct transport *gtransport;\n+\n+\t/*\n+\t * TODO: Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT.\n+\t */\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid)) {\n+\t\t\tsize_t len = strlen(argv[i]);\n+\n+\t\t\tif (len < the_hash_algo->hexsz && len >= 4) {\n+\t\t\t\tsize_t j;\n+\t\t\t\tfor (j = 0; j < len; j++)\n+\t\t\t\t\tif (!isxdigit(argv[i][j]))\n+\t\t\t\t\t\tbreak;\n+\t\t\t\tif (j == len)\n+\t\t\t\t\tdie(_(\"remote-object-info does not support \"\n+\t\t\t\t\t      \"short oids, %d characters required\"),\n+\t\t\t\t\t    (int)the_hash_algo->hexsz);\n+\t\t\t}\n+\t\t\tdie(_(\"not a valid object name '%s'\"), argv[i]);\n+\t\t}\n+\t\toid_array_append(object_info_oids, &oid);\n+\t}\n+\n+\tif (!object_info_oids->nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\n+\tif (!gtransport->smart_options) {\n+\t\tretval = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n+\tgtransport->smart_options->object_info_oids = object_info_oids;\n+\n+\t/* 'objectsize' is the only option currently supported */\n+\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\tstring_list_append(&object_info_options, \"size\");\n+\n+\tif (object_info_options.nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\t\tretval = transport_fetch_object_info(gtransport);\n+\t}\n+cleanup:\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -717,6 +807,57 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\tstruct object_info *remote_object_info = NULL;\n+\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command too long\"));\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n+\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n+\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n+\t\t    MAX_ALLOWED_OBJ_LIMIT);\n+\n+\tif (get_remote_info(opt, count, argv, &remote_object_info,\n+\t\t\t    &object_info_oids))\n+\t\tgoto cleanup;\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t} else {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+\toid_array_clear(&object_info_oids);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -748,8 +889,9 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1 },\n \t{ \"info\", parse_cmd_info, 1 },\n-\t{ \"flush\", NULL, 0 },\n \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n+\t{ \"flush\", NULL, 0 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex 6453b1d6fa..07f019a0f6 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1694,3 +1694,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)\n \n \treturn &transaction->base;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+}\ndiff --git a/odb.h b/odb.h\nindex 3834a0dcbf..42e3934035 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -573,4 +573,7 @@ void parse_alternates(const char *string,\n \t\t      const char *relative_base,\n \t\t      struct strvec *out);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* ODB_H */\ndiff --git a/t/meson.build b/t/meson.build\nindex 7c3c070426..562923c257 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -171,6 +171,7 @@ integration_tests = [\n   't1014-read-tree-confusing.sh',\n   't1015-read-index-unmerged.sh',\n   't1016-compatObjectFormat.sh',\n+  't1017-cat-file-remote-object-info.sh',\n   't1020-subdirectory.sh',\n   't1022-read-tree-partial-clone.sh',\n   't1050-large.sh',\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..49b6660934\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,680 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n+\n+unstored_content=\"Hello Git\"\n+unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not support short oids' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_short_oid\n+\t\tEOF\n+\t\ttest_grep \"does not support short oids\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not die on missing oid like info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tgit cat-file --batch-command >local <<-EOF &&\n+\t\tinfo $unstored_oid\n+\t\tEOF\n+\t\tgit cat-file --batch-command >remote <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $unstored_oid\n+\t\tEOF\n+\t\ttest_cmp local remote\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n\n-- \n2.54.0\n"},{"id":"547773","messageId":"20260710-ps-eric-work-rebase-v16-12-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 12/13] cat-file: validate remote atoms with an allow-list","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:18Z","receivedAt":"2026-07-10T16:41:39Z","isPatch":true,"body":"strstr() is not enough to validate the format placeholders in\nremote-object-info causing two errors:\n\n1. Atoms recognized by expand_atom() but the remote doesn't returns 1,\n   but data->type contains garbage causing segfault.\n\n2. expand_atom() returns 0 for unknown atoms, calling\n   strbuf_expand_bad_format() which ends up dying, blocking local\n   queries if the same format is shared.\n\nAdd an allow-list with the supported atoms at the top of expand_atom().\nIn remote mode, unsupported atoms return 1 leaving the buffer empty,\nhonoring how for-each-ref handles known but inapplicable atoms.\n\nAs extra safety, initialize data->type to OBJ_BAD and add a NULL check\nfor type_name() so uninitialized data doesn't cause segfault.\n\nUpdate tests that expect previous die() behavior to expect an empty\nstring and add an explicit test for empty string return on unknown\nplaceholder.\n\nUpdate cat-file command documentation regarding remote-object-info.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  2 +-\n builtin/cat-file.c                     | 41 +++++++++++++++++++++++++++-------\n t/t1017-cat-file-remote-object-info.sh | 27 ++++++++++++++++++----\n 3 files changed, 57 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex a7fa6674c3..643eac9245 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -451,7 +451,7 @@ CAVEATS\n \n Note that since only `%(objectname)` and `%(objectsize)` are currently\n supported by the `remote-object-info` command. Using any other placeholder in\n-the format string will raise an error.\n+the format string will return an empty string in its position.\n \n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 77ecccdda3..af388b6238 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -333,8 +333,18 @@ struct expand_data {\n \t * optimized out.\n \t */\n \tunsigned skip_object_info : 1;\n+\n+\t/*\n+\t * Flags about when an object info is being fetched from remote.\n+\t */\n+\tunsigned is_remote:1;\n+};\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n+\n+static const char *remote_object_info_atoms[] = {\n+\t\"objectname\",\n+\t\"objectsize\",\n };\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -345,14 +355,31 @@ static int is_atom(const char *atom, const char *s, int slen)\n static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n+\tif (data->is_remote) {\n+\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n+\t\tfor (i = 0; i < allowed_nr; i++)\n+\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * On remote, skip unsupported atoms returning an empty sb,\n+\t\t * honoring how for-each-ref handles known but inapplicable\n+\t\t * atoms (e.g. %(tagger)).\n+\t\t */\n+\t\tif (i == allowed_nr)\n+\t\t\treturn 1;\n+\t}\n+\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n \t\t\tstrbuf_add_oid_hex(sb, &data->oid);\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n-\t\tif (data->mark_query)\n+\t\tif (data->mark_query) {\n \t\t\tdata->info.typep = &data->type;\n-\t\telse\n-\t\t\tstrbuf_addstr(sb, type_name(data->type));\n+\t\t} else {\n+\t\t\tconst char *t = type_name(data->type);\n+\t\t\tstrbuf_addstr(sb, t ? t : \"\");\n+\t\t}\n \t} else if (is_atom(\"objectsize\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.sizep = &data->size;\n@@ -709,10 +736,6 @@ static int get_remote_info(struct batch_options *opt,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\t/* 'objectsize' is the only option currently supported */\n-\tif (!strstr(opt->format, \"%(objectsize)\"))\n-\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n-\n \tstring_list_append(&object_info_options, \"size\");\n \n \tif (object_info_options.nr > 0) {\n@@ -842,7 +865,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n \t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tdata->is_remote = 1;\n \t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t\tdata->is_remote = 0;\n \t\t} else {\n \t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n \t\t}\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 49b6660934..6bc863c391 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -236,6 +236,21 @@ test_expect_success 'remote-object-info does not die on missing oid like info' '\n \t)\n '\n \n+# This tests depends on %(objecttype) not being supported yet, once supported\n+# it needs to be updated.\n+test_expect_success 'unsupported placeholder on remote returns empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"\" >expect &&\n+\t\tgit cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n # Test --batch-command remote-object-info with 'git://' and\n # transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n@@ -575,10 +590,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (obje\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\techo \"$hello_oid \" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize:disk)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n@@ -587,10 +604,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (delt\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(deltabase)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n\n-- \n2.54.0\n"},{"id":"547775","messageId":"20260710-ps-eric-work-rebase-v16-13-66e07b58a8fe@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v16 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-10T16:41:19Z","receivedAt":"2026-07-10T16:41:40Z","isPatch":true,"body":"The static allow-list in expand_atom() is hardcoded to only allow\n\"objectname\" and \"objectsize\" for remote queries. This works because\nup to this point all servers will either support object-info with name\nand size or they do not support them at all, but we cannot expect that\nin a future different servers with different git versions to have the\nsame object-info capabilities. Therefore, the allow_list needs to be\ndynamic depending on what the server advertises.\n\nThe client will now:\n\n1. Request the protocol option that the placeholder refers to (i.e.\n   \"size\" when \"%(objectsize)\").\n\n2. Filters the request in fetch_object_info() dropping any option that\n   the server does not advertise.\n\n3. After the fetching, the options that haven't been dropped are the ones\n   fetched and supported by the server, these supported options are\n   mapped and remote_allowed_atoms is populated with the placeholders.\n\n4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n   the static allow_list had.\n\nMove object_info_options out of get_remote_info so the caller which has\ndata can select what options will be requested instead of requesting\nalways size.\nMove batch_object_write() out so there will always be an output even if\nall the placeholders are not supported by the server (returns an empty\nline).\n\nInclude \"type\" in the object_info_options so once the server supports\nit, the clients know already how to request it.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c  | 97 +++++++++++++++++++++++++++++++++++------------------\n fetch-object-info.c | 20 +++++++++++\n 2 files changed, 84 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex af388b6238..20d9705a7f 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -338,13 +338,11 @@ struct expand_data {\n \t * Flags about when an object info is being fetched from remote.\n \t */\n \tunsigned is_remote:1;\n-};\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n \n-static const char *remote_object_info_atoms[] = {\n-\t\"objectname\",\n-\t\"objectsize\",\n+\tstruct string_list remote_allowed_atoms;\n };\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -356,17 +354,11 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n \tif (data->is_remote) {\n-\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n-\t\tfor (i = 0; i < allowed_nr; i++)\n-\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\tsize_t i;\n+\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n \t\t\t\tbreak;\n-\n-\t\t/*\n-\t\t * On remote, skip unsupported atoms returning an empty sb,\n-\t\t * honoring how for-each-ref handles known but inapplicable\n-\t\t * atoms (e.g. %(tagger)).\n-\t\t */\n-\t\tif (i == allowed_nr)\n+\t\tif (i == data->remote_allowed_atoms.nr)\n \t\t\treturn 1;\n \t}\n \n@@ -683,12 +675,12 @@ static int get_remote_info(struct batch_options *opt,\n \t\t\t   int argc,\n \t\t\t   const char **argv,\n \t\t\t   struct object_info **remote_object_info,\n-\t\t\t   struct oid_array *object_info_oids)\n+\t\t\t   struct oid_array *object_info_oids,\n+\t\t\t   struct string_list *object_info_options)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n \tstruct object_id oid;\n-\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tstruct transport *gtransport;\n \n \t/*\n@@ -736,15 +728,12 @@ static int get_remote_info(struct batch_options *opt,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\tstring_list_append(&object_info_options, \"size\");\n-\n-\tif (object_info_options.nr > 0) {\n-\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tif (object_info_options->nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = object_info_options;\n \t\tgtransport->smart_options->object_info_data = *remote_object_info;\n \t\tretval = transport_fetch_object_info(gtransport);\n \t}\n cleanup:\n-\tstring_list_clear(&object_info_options, 0);\n \ttransport_disconnect(gtransport);\n \treturn retval;\n }\n@@ -830,6 +819,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+struct protocol_placeholder_entry {\n+\tconst char *option;\n+\tconst char *atom;\n+};\n+\n+static const struct protocol_placeholder_entry remote_atom_map[] = {\n+\t{\"size\", \"objectsize\"},\n+\t{\"type\", \"objecttype\"},\n+\t/*\n+\t * Add new protocol options here. Even if the server doesn't support\n+\t * them the allow_list will drop them if the server doesn't advertise\n+\t * them.\n+\t */\n+};\n+\n static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n@@ -839,6 +843,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct object_info *remote_object_info = NULL;\n \tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \n \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n \t\tdie(_(\"remote-object-info command too long\"));\n@@ -851,32 +856,57 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n+\tif (data->info.sizep)\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\tif (data->info.typep)\n+\t\tstring_list_append(&object_info_options, \"type\");\n+\n \tif (get_remote_info(opt, count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids))\n+\t\t\t    &object_info_oids, &object_info_options))\n \t\tgoto cleanup;\n \n+\tstring_list_clear(&data->remote_allowed_atoms, 0);\n+\tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n+\tfor (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n+\t\tif (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n+\t\t\tstring_list_append(&data->remote_allowed_atoms,\n+\t\t\t\t\t   remote_atom_map[i].atom);\n+\n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tint found = 0;\n \t\tdata->oid = object_info_oids.oid[i];\n+\t\t/*\n+\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t * information from server without downloading them.\n+\t\t */\n \t\tif (remote_object_info[i].sizep) {\n-\t\t\t/*\n-\t\t\t * When reaching here, it means remote-object-info can retrieve\n-\t\t\t * information from server without downloading them.\n-\t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n-\t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tdata->is_remote = 1;\n-\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n-\t\t\tdata->is_remote = 0;\n-\t\t} else {\n-\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t\tfound = 1;\n \t\t}\n+\n+\t\tif (remote_object_info[i].typep) {\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\t\t\tfound = 1;\n+\t\t}\n+\n+\t\tif (!found && object_info_options.nr > 0) {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid),\n+\t\t\t\t\t     &data->oid, \"missing\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tdata->is_remote = 1;\n+\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n \n cleanup:\n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n+\tstring_list_clear(&object_info_options, 0);\n \tfree(line_to_split);\n \tfree(argv);\n \tfree(remote_object_info);\n@@ -1192,6 +1222,7 @@ static int batch_objects(struct batch_options *opt)\n  cleanup:\n \tstrbuf_release(&input);\n \tstrbuf_release(&output);\n+\tstring_list_clear(&data.remote_allowed_atoms, 0);\n \tcfg->warn_on_object_refname_ambiguity = save_warning;\n \treturn retval;\n }\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 91685cb355..e9b1011d07 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -55,6 +55,26 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \tcase protocol_v2:\n \t\tif (!server_supports_v2(\"object-info\"))\n \t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\t/*\n+\t\t * When removing an element from the list it gets swapped by the\n+\t\t * last element, iterate backwards to prevent elements skipping\n+\t\t * evaluation.\n+\t\t *\n+\t\t * object_info_options->nr can be safely casted without overflow\n+\t\t * beacuse the number of options is a small known number (the\n+\t\t * supported placeholders which currently are size and type).\n+\t\t */\n+\t\tfor (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)\n+\t\t\tif (!server_supports_feature(\"object-info\",\n+\t\t\t\t\t\t     args->object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(args->object_info_options, i, 0);\n+\t\t/*\n+\t\t * If no options are left after the filtering, avoid unnecessary\n+\t\t * request to the server.\n+\t\t */\n+\t\tif (!args->object_info_options->nr)\n+\t\t\treturn 0;\n+\n \t\tsend_object_info_request(fd_out, args);\n \t\tbreak;\n \tcase protocol_v1:\n\n-- \n2.54.0\n"},{"id":"547875","messageId":"CA+J6zkSo3ZqLe7HLEXRAs+hOq2FuOVokMQWbABcW95wihNtCgA@mail.gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"Re: [PATCH GSoC v16 00/13] cat-file: add remote-object-info to batch-command","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-07-12T05:26:59Z","receivedAt":"2026-07-12T05:27:28Z","isPatch":true,"body":"On Fri, 10 Jul 2026 at 22:11, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> This patch series is a continuation of Eric Ju's\n> (eric.peijian@gmail.com) and Calvin Wan's (calvinwan@google.com) patch\n> series [1] and [2] respectively.\n>\n> Sometimes it is beneficial to retrieve information about an object\n> without having to download it completely. The server logic for\n> retrieving size has already been implemented and merged in a2ba162cda\n> (object-info: support for retrieving object info, 2021-04-20) [3].\n> This patch series implement the client option for it.\n>\n> Eric's series adds the remote-object-info command to cat-file\n> --batch-command. This command allows the client to make an object-info\n> command request to a server that supports protocol v2.\n>\n> If the server uses protocol v2 but does not support the object-info\n> capability, cat-file --batch-command will die.\n>\n> If a user attempts to use remote-object-info with protocol v1, cat-file\n> --batch-command will die.\n>\n> Currently, only the size (%(objectsize)) is supported end to end in this\n> implementation. The type (%(objecttype)) is known by the client's\n> allow-list and request path but is not supported on the server side\n> nor the response parsing. A follow up series will add full end-to-end\n> support for %(objecttype).\n>\n> The default format for remote-object-info is set to \"%(objectname)\n> %(objectsize)\". Once %(objecttype) is supported, the default format will\n> be unified accordingly.\n>\n> If the batch command format includes unsupported fields such as\n> %(objecttype), %(objectsize:disk), or %(deltabase), the command will\n> return empty strings for each unsupported field.\n>\n> This series completes Eric's work mainly with the refactor of the\n> validation of the placeholder with an allow-list that filters what the\n> client asks with what the server is capable of provide following Jeff\n> King's idea [4].\n>\n> GitHub CI: https://github.com/pabloosabaterr/git/actions/runs/29091116939\n>\n> [1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n> [2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n> [3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n> [4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n>\n> Changes since v15:\n> - Completely dropped the static advertise_sid variable at fetch-pack.c\n> - Split the hash_algo type change into its own commit.\n> - Removed strtoumax_szt() from git-compat-util.h (and its commit) into a\n>   static parse_object_size() helper.\n> - Removed backquotes from commit message bodies and fixed typos.\n>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n> Calvin Wan (3):\n>       fetch-pack: move fetch initialization\n>       serve: advertise object-info feature\n>       transport: add client support for object-info\n>\n> Eric Ju (3):\n>       cat-file: declare loop counter inside for()\n>       t1006: split test utility functions into new 'lib-cat-file.sh'\n>       cat-file: add remote-object-info to batch-command\n>\n> Pablo Sabater (7):\n>       transport-helper: fix memory leak of helper on disconnect\n>       fetch-pack: fix hash_algo variable type\n>       fetch-pack: drop static advertise_sid variable\n>       fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n>       connect: make write_fetch_command_and_capabilities() more generic\n>       cat-file: validate remote atoms with an allow-list\n>       cat-file: make remote-object-info allow-list dynamic\n>\n>  Documentation/git-cat-file.adoc        |  29 +-\n>  Documentation/gitprotocol-v2.adoc      |  11 +-\n>  Makefile                               |   1 +\n>  builtin/cat-file.c                     | 221 ++++++++++-\n>  connect.c                              |  34 ++\n>  connect.h                              |   8 +\n>  fetch-object-info.c                    | 129 ++++++\n>  fetch-object-info.h                    |  22 ++\n>  fetch-pack.c                           |  58 +--\n>  fetch-pack.h                           |   1 +\n>  meson.build                            |   1 +\n>  object-file.c                          |  10 +\n>  odb.h                                  |   3 +\n>  serve.c                                |   5 +-\n>  t/lib-cat-file.sh                      |  16 +\n>  t/meson.build                          |   1 +\n>  t/t1006-cat-file.sh                    |  13 +-\n>  t/t1017-cat-file-remote-object-info.sh | 699 +++++++++++++++++++++++++++++++++\n>  transport-helper.c                     |  15 +-\n>  transport-internal.h                   |   8 +\n>  transport.c                            |  46 +++\n>  transport.h                            |  10 +\n>  22 files changed, 1255 insertions(+), 86 deletions(-)\n>\n> base-commit: f60db8d575adb79761d363e026fb49bddf330c73\n\nThis version looks fine to me.\n\nThanks,\nChandra.\n"},{"id":"548097","messageId":"DJY9BJ5QMT2G.2WA49SUCRRB3Z@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-10-66e07b58a8fe@gmail.com","subject":"Re: [PATCH GSoC v16 10/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:28:12Z","receivedAt":"2026-07-14T11:28:16Z","isPatch":true,"body":"On Fri Jul 10, 2026 at 6:41 PM CEST, Pablo Sabater wrote:\n\n[snip]\n\n> diff --git a/transport.c b/transport.c\n> index fc144f0aed..3e0a6558b7 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -1,3 +1,4 @@\n> +#include \"compat/posix.h\"\n>  #define USE_THE_REPOSITORY_VARIABLE\n\nHi while working on another series I realized that I had introduced this\ninclude which is wrong. I didn't notice because strangely it didn't gave\nany problems on the CI.\n\nI'll drop it next reroll.\n\nRegards,\nPablo.\n"},{"id":"548098","messageId":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260710-ps-eric-work-rebase-v16-0-66e07b58a8fe@gmail.com","subject":"[PATCH GSoC v17 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:44:56Z","receivedAt":"2026-07-14T11:45:14Z","isPatch":true,"body":"This patch series is a continuation of Eric Ju's\n(eric.peijian@gmail.com) and Calvin Wan's (calvinwan@google.com) patch\nseries [1] and [2] respectively.\n\nSometimes it is beneficial to retrieve information about an object\nwithout having to download it completely. The server logic for\nretrieving size has already been implemented and merged in a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20) [3].\nThis patch series implement the client option for it.\n\nEric's series adds the remote-object-info command to cat-file\n--batch-command. This command allows the client to make an object-info\ncommand request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info\ncapability, cat-file --batch-command will die.\n\nIf a user attempts to use remote-object-info with protocol v1, cat-file\n--batch-command will die.\n\nCurrently, only the size (%(objectsize)) is supported end to end in this\nimplementation. The type (%(objecttype)) is known by the client's\nallow-list and request path but is not supported on the server side\nnor the response parsing. A follow up series will add full end-to-end\nsupport for %(objecttype).\n\nThe default format for remote-object-info is set to \"%(objectname)\n%(objectsize)\". Once %(objecttype) is supported, the default format will\nbe unified accordingly.\n\nIf the batch command format includes unsupported fields such as\n%(objecttype), %(objectsize:disk), or %(deltabase), the command will\nreturn empty strings for each unsupported field.\n\nThis series completes Eric's work mainly with the refactor of the\nvalidation of the placeholder with an allow-list that filters what the\nclient asks with what the server is capable of provide following Jeff\nKing's idea [4].\n\nGitHub CI: https://github.com/pabloosabaterr/git/actions/runs/28435046129\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n[4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n\nChanges since v16:\n- Droped a wrongly introduced include at transport.c\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n\n---\nCalvin Wan (3):\n      fetch-pack: move fetch initialization\n      serve: advertise object-info feature\n      transport: add client support for object-info\n\nEric Ju (3):\n      cat-file: declare loop counter inside for()\n      t1006: split test utility functions into new 'lib-cat-file.sh'\n      cat-file: add remote-object-info to batch-command\n\nPablo Sabater (7):\n      transport-helper: fix memory leak of helper on disconnect\n      fetch-pack: fix hash_algo variable type\n      fetch-pack: drop static advertise_sid variable\n      fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n      connect: make write_fetch_command_and_capabilities() more generic\n      cat-file: validate remote atoms with an allow-list\n      cat-file: make remote-object-info allow-list dynamic\n\n Documentation/git-cat-file.adoc        |  29 +-\n Documentation/gitprotocol-v2.adoc      |  11 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 221 ++++++++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    | 129 ++++++\n fetch-object-info.h                    |  22 ++\n fetch-pack.c                           |  58 +--\n fetch-pack.h                           |   1 +\n meson.build                            |   1 +\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n serve.c                                |   5 +-\n t/lib-cat-file.sh                      |  16 +\n t/meson.build                          |   1 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 699 +++++++++++++++++++++++++++++++++\n transport-helper.c                     |  15 +-\n transport-internal.h                   |   8 +\n transport.c                            |  45 +++\n transport.h                            |  10 +\n 22 files changed, 1254 insertions(+), 86 deletions(-)\n\n---\nbase-commit: f60db8d575adb79761d363e026fb49bddf330c73\n"},{"id":"548099","messageId":"20260714-ps-eric-work-rebase-v17-1-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 01/13] transport-helper: fix memory leak of helper on disconnect","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:44:57Z","receivedAt":"2026-07-14T11:45:16Z","isPatch":true,"body":"disconnect_helper() only frees data inside of the if(data->helper) block\n[1]. When the transport is disconnected without the helper being fully\nstarted, data->name allocated in transport_helper_init()\nis never freed.\n\nMove FREE_AND_NULL(data->name) outside the conditional block so it's\nalways freed on disconnect.\n\n[1]: https://lore.kernel.org/git/05fbadbae2184479c87c37675dde7bd79b3e32ab.1716465556.git.ps@pks.im/\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n transport-helper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 80f90eb7ba..f195070788 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tres = finish_command(data->helper);\n-\t\tFREE_AND_NULL(data->name);\n \t\tFREE_AND_NULL(data->helper);\n \t}\n+\tFREE_AND_NULL(data->name);\n \treturn res;\n }\n \n\n-- \n2.54.0\n"},{"id":"548101","messageId":"20260714-ps-eric-work-rebase-v17-2-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 02/13] cat-file: declare loop counter inside for()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:44:58Z","receivedAt":"2026-07-14T11:45:17Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSome code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\nWhile at it, we also change its type from int to size_t where the\nlatter makes more sense.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c | 13 ++++---------\n fetch-pack.c       |  3 +--\n 2 files changed, 5 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 60869b8b37..26ad07b62c 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -721,14 +721,12 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n \t\tstruct queued_cmd *cmd,\n-\t\tint nr)\n+\t\tsize_t nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -736,9 +734,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -765,7 +761,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -775,7 +770,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 120e01f3cf..f13951d154 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n\n-- \n2.54.0\n"},{"id":"548100","messageId":"20260714-ps-eric-work-rebase-v17-3-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 03/13] t1006: split test utility functions into new 'lib-cat-file.sh'","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:44:59Z","receivedAt":"2026-07-14T11:45:18Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nThis refactor extracts utility functions from the cat-file's test\nscript 't1006-cat-file.sh' into a new 'lib-cat-file.sh' dedicated\nlibrary file.\n\nA subsequent commit will need this functions, the goal is to improve\ncode reuse and readability,enabling future tests to leverage these\nutilities without duplicating code.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 13 +------------\n 2 files changed, 17 insertions(+), 12 deletions(-)\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..44af232d74\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 8e2c52652c..8360f3bbd9 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-loose.sh\"\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     object_name=\"$2\"\n\n-- \n2.54.0\n"},{"id":"548102","messageId":"20260714-ps-eric-work-rebase-v17-4-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 04/13] fetch-pack: fix hash_algo variable type","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:45:00Z","receivedAt":"2026-07-14T11:45:19Z","isPatch":true,"body":"hash_algo_by_name() returns \"unsigned int\", but the variable that it is\nassigned to is \"int\".\n\nChange hash_algo variable type to match hash_algo_by_name() type, also\nmake it const because it is never modified.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f13951d154..eea72b2500 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1395,7 +1395,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t}\n \n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n \t\t\t    the_hash_algo->name, hash_name);\n\n-- \n2.54.0\n"},{"id":"548103","messageId":"20260714-ps-eric-work-rebase-v17-5-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 05/13] fetch-pack: drop static advertise_sid variable","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:45:01Z","receivedAt":"2026-07-14T11:45:21Z","isPatch":true,"body":"write_fetch_command_and_capabilities() is moved to 'connect.c' in a\nsubsequent commit. To prepare for that, drop the static variable usage\nof advertise_sid. Currently advertise_sid is used in two places:\n\n1. In function do_fetch_pack():\n        if (!server_supports(\"session-id\"))\n               advertise_sid = 0;\n\n2. In function fetch_pack_config():\n        repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, it is only relevant for v0/v1 protocol, move it into\nfind_common().\n\nAbout 2, call repo_config_get_bool() inside of\nwrite_fetch_command_and_capabilities() and find_common() replacing the\nstatic variable.\n\nBecause repo_config_get_bool() leaves advertise_sid as is if it is not\nset, initialize it to 0 matching its default.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 13 +++++++------\n 1 file changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex eea72b2500..8e04db8640 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -49,7 +49,6 @@ static int fetch_fsck_objects = -1;\n static int transfer_fsck_objects = -1;\n static int agent_supported;\n static int server_supports_filtering;\n-static int advertise_sid;\n static struct shallow_lock shallow_lock;\n static const char *alternate_shallow_file;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n@@ -363,6 +362,9 @@ static int find_common(struct fetch_negotiator *negotiator,\n \tsize_t state_len = 0;\n \tstruct packet_reader reader;\n \tstruct oidset negotiation_include_oids = OIDSET_INIT;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tif (args->stateless_rpc && multi_ack == 1)\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--stateless-rpc\", \"multi_ack_detailed\");\n@@ -414,7 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator,\n \t\t\tif (deepen_not_ok)      strbuf_addstr(&c, \" deepen-not\");\n \t\t\tif (agent_supported)    strbuf_addf(&c, \" agent=%s\",\n \t\t\t\t\t\t\t    git_user_agent_sanitized());\n-\t\t\tif (advertise_sid)\n+\t\t\tif (advertise_sid && server_supports(\"session-id\"))\n \t\t\t\tstrbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n \t\t\tif (args->filter_options.choice)\n \t\t\t\tstrbuf_addstr(&c, \" filter\");\n@@ -1160,9 +1162,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\t\t\t      (int)agent_len, agent_feature);\n \t}\n \n-\tif (!server_supports(\"session-id\"))\n-\t\tadvertise_sid = 0;\n-\n \tif (server_supports(\"shallow\"))\n \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n \telse if (args->depth > 0 || is_repository_shallow(r))\n@@ -1380,6 +1379,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t\t\t\t\t\t const struct string_list *server_options)\n {\n \tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tensure_server_supports_v2(\"fetch\");\n \tpacket_buf_write(req_buf, \"command=fetch\");\n@@ -1998,7 +2000,6 @@ static void fetch_pack_config(void)\n \trepo_config_get_bool(the_repository, \"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n \trepo_config_get_bool(the_repository, \"fetch.fsckobjects\", &fetch_fsck_objects);\n \trepo_config_get_bool(the_repository, \"transfer.fsckobjects\", &transfer_fsck_objects);\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \tif (!uri_protocols.nr) {\n \t\tchar *str;\n \n\n-- \n2.54.0\n"},{"id":"548104","messageId":"20260714-ps-eric-work-rebase-v17-6-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 06/13] fetch-pack: move write_fetch_command_and_capabilities() to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:45:02Z","receivedAt":"2026-07-14T11:45:22Z","isPatch":true,"body":"write_fetch_command_and_capabilities() is refactored in a subsequent\ncommit where it becomes a more general-purpose function, making it\nmore accessible to additional commands in the future.\n\nMove write_fetch_command_and_capabilities() to 'connect.c', where\nthere are similar purpose functions.\n\nBecause string_list is only used as a pointer, use a forward\ndeclaration [1].\n\n[1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 34 ----------------------------------\n 3 files changed, 38 insertions(+), 34 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 47e39d2a73..c09947cc56 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,6 +700,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(\"fetch\");\n+\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t    the_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t    the_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n static const char *url_scheme_name(enum url_scheme scheme)\n {\n \tswitch (scheme) {\ndiff --git a/connect.h b/connect.h\nindex aa482a37fb..c4f6ea4b0a 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+struct string_list;\n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 8e04db8640..5e7c4f1d46 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1375,40 +1375,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\tint advertise_sid = 0;\n-\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n\n-- \n2.54.0\n"},{"id":"548105","messageId":"20260714-ps-eric-work-rebase-v17-7-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 07/13] connect: make write_fetch_command_and_capabilities() more generic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:45:03Z","receivedAt":"2026-07-14T11:45:23Z","isPatch":true,"body":"Refactor write_fetch_command_and_capabilities(), enabling it to serve\nboth fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162\n(object-info: support for retrieving object info, 2021-04-20).\n\nRefactor the function signature to accept a command instead of the\nhardcoded \"fetch\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 8 ++++----\n connect.h    | 8 ++++++--\n fetch-pack.c | 4 ++--\n 3 files changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex c09947cc56..127ed4a2e6 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,16 +700,16 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options)\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \tint advertise_sid = 0;\n \n \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\ndiff --git a/connect.h b/connect.h\nindex c4f6ea4b0a..c2bf492ed9 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -35,7 +35,11 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       const char *error);\n \n struct string_list;\n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options);\n+/*\n+ * Writes a command along with the requested server capabilities/features into a\n+ * request buffer.\n+ */\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 5e7c4f1d46..783e3474a6 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1386,7 +1386,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2253,7 +2253,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n\n-- \n2.54.0\n"},{"id":"548106","messageId":"20260714-ps-eric-work-rebase-v17-8-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 08/13] fetch-pack: move fetch initialization","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:45:04Z","receivedAt":"2026-07-14T11:45:24Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized in\nFETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 783e3474a6..2ab5ba108b 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1735,18 +1735,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n\n-- \n2.54.0\n"},{"id":"548107","messageId":"20260714-ps-eric-work-rebase-v17-9-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 09/13] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:45:05Z","receivedAt":"2026-07-14T11:45:25Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This allows a client\nto decide whether to query the server for object-info or fetch as a\nfallback.\n\nWhile at it, update the object-info section in 'gitprotocol-v2.adoc':\n- Require full obj-oid explicitly.\n- Fix parentheses.\n- Define obj-size explicitly.\n- Make obj-size optional in obj-info and document the behavior\n  for unrecognized object IDs.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/gitprotocol-v2.adoc | 11 ++++++++---\n serve.c                           |  5 ++++-\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 2beb70595f..d3530c52ea 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -568,21 +568,26 @@ An `object-info` request takes the following arguments:\n \n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n-\tinformation for.\n+\tinformation for. They must be full object IDs.\n \n The response of `object-info` is a list of the requested object ids\n and associated requested information, each separated by a single space.\n \n \toutput = info flush-pkt\n \n-\tinfo = PKT-LINE(attrs) LF)\n+\tinfo = PKT-LINE(attrs LF)\n \t\t*PKT-LINE(obj-info LF)\n \n \tattrs = attr | attrs SP attrs\n \n+\tobj-size = 1*DIGIT\n+\n \tattr = \"size\"\n \n-\tobj-info = obj-id SP obj-size\n+\tobj-info = obj-id SP [obj-size]\n+\n+\tIf the server does not recognize the object id, the response will be\n+\t`obj-id SP` regardless of the number of attributes requested.\n \n bundle-uri\n ~~~~~~~~~~\ndiff --git a/serve.c b/serve.c\nindex 49a6e39b1d..2b07d922b3 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\t/* Currently only size is supported */\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n\n-- \n2.54.0\n"},{"id":"548108","messageId":"20260714-ps-eric-work-rebase-v17-10-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 10/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:45:06Z","receivedAt":"2026-07-14T11:45:27Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nIntroduce client-side support for the object-info capability.\n\nAdd its own function for object-info separate from existing fetch\ninfrastructure.\n\nCurrently, the client supports requesting a list of object IDs with\nthe size feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client returns an error and exit.\n\nNote that:\n\n1. the entire request is written into req_buf before being sent to the\n   remote. This approach follows the pattern used in the\n   send_fetch_request() logic within 'fetch-pack.c'. Streaming the\n   request is not addressed in this patch.\n\n2. When the server does not recognize an OID, following the v2 protocol,\n   the server returns \"<OID> SP\", when this happens,\n   fetch_object_info() sets the corresponding size pointer to NULL so\n   that callers can detect and handle it.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Makefile             |   1 +\n fetch-object-info.c  | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h  |  22 +++++++++++\n fetch-pack.h         |   1 +\n meson.build          |   1 +\n transport-helper.c   |  13 +++++-\n transport-internal.h |   8 ++++\n transport.c          |  45 +++++++++++++++++++++\n transport.h          |  10 +++++\n 9 files changed, 208 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 1f3f099f5c..cdabdb3771 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1159,6 +1159,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..91685cb355\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,109 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"odb.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\telse\n+\t\tBUG(\"only size should be in object_info_options\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+static size_t parse_object_size(const char *s, size_t *res)\n+{\n+\tuintmax_t uim;\n+\n+\tif (!s[0] || s[strspn(s, \"0123456789\")])\n+\t\treturn -1;\n+\terrno = 0;\n+\tuim = strtoumax(s, NULL, 10);\n+\tif (errno || uim > SIZE_MAX)\n+\t\treturn -1;\n+\t*res = uim;\n+\treturn 0;\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"unsupported protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader,\n+\t\t\t\t\t\t  \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\tsize_index = i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep = xcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t} else {\n+\t\t\tBUG(\"only size is supported\");\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n+\t\tif (size_index >= 0) {\n+\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\")) {\n+\t\t\t\tFREE_AND_NULL(object_info_data[i].sizep);\n+\t\t\t\tstring_list_clear(&object_info_values, 0);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (parse_object_size(object_info_values.items[1 + size_index].string,\n+\t\t\t\t\t      object_info_data[i].sizep))\n+\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t\t    object_info_values.items[0].string,\n+\t\t\t\t    object_info_values.items[1 + size_index].string);\n+\t\t}\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..d35284bd6b\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"odb.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 6d0dec7f41..0fba340a84 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n \n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\ndiff --git a/meson.build b/meson.build\nindex ca235801cf..19fad57da9 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -347,6 +347,7 @@ libgit_sources = [\n   'exec-cmd.c',\n   'fetch-negotiator.c',\n   'fetch-pack.c',\n+  'fetch-object-info.c',\n   'fmt-merge-msg.c',\n   'fsck.c',\n   'fsmonitor.c',\ndiff --git a/transport-helper.c b/transport-helper.c\nindex f195070788..f97e6d7b29 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -727,8 +727,7 @@ static int fetch_refs(struct transport *transport,\n \n \t/*\n \t * If we reach here, then the server, the client, and/or the transport\n-\t * helper does not support protocol v2. --negotiate-only requires\n-\t * protocol v2.\n+\t * helper does not support protocol v2. --negotiate-only.\n \t */\n \tif (data->transport_options.acked_commits) {\n \t\twarning(_(\"--negotiate-only requires protocol v2\"));\n@@ -784,6 +783,15 @@ static int fetch_refs(struct transport *transport,\n \treturn -1;\n }\n \n+static int fetch_object_info_helper(struct transport *transport)\n+{\n+\tget_helper(transport);\n+\tif (process_connect(transport, 0))\n+\t\treturn transport->vtable->fetch_object_info(transport);\n+\n+\tdie(_(\"object-info requires protocol v2\"));\n+}\n+\n struct push_update_ref_state {\n \tstruct ref *hint;\n \tstruct ref_push_report *report;\n@@ -1330,6 +1338,7 @@ static struct transport_vtable vtable = {\n \t.get_refs_list\t= get_refs_list,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs,\n+\t.fetch_object_info = fetch_object_info_helper,\n \t.push_refs\t= push_refs,\n \t.connect\t= connect_helper,\n \t.disconnect\t= release_helper\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 051f3ab0dc..60db0bedcd 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -45,6 +45,14 @@ struct transport_vtable {\n \t **/\n \tint (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);\n \n+\t/*\n+\t * Fetch object info (only size currently) from remote without\n+\t * downloading the objects.\n+\t *\n+\t * Uses object-info capability of v2 protocol.\n+\t */\n+\tint (*fetch_object_info)(struct transport *transport);\n+\n \t/**\n \t * Push the objects and refs. Send the necessary objects, and\n \t * then, for any refs where peer_ref is set and\ndiff --git a/transport.c b/transport.c\nindex fc144f0aed..9342680531 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -432,6 +433,48 @@ static int get_bundle_uri(struct transport *transport)\n \t\t\t\t     transport->bundles, stateless_rpc);\n }\n \n+static int fetch_object_info_via_pack(struct transport *transport)\n+{\n+\tint ret = 0;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct packet_reader reader;\n+\tstruct object_info_args args = { 0 };\n+\n+\targs.server_options = transport->server_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\tstring_list_sort(args.object_info_options);\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tdata->version = discover_version(&reader);\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tret = fetch_object_info(data->version, &args, &reader,\n+\t\t\t\tdata->options.object_info_data,\n+\t\t\t\ttransport->stateless_rpc, data->fd[1]);\n+\n+\tclose(data->fd[0]);\n+\tif (data->fd[1] >= 0)\n+\t\tclose(data->fd[1]);\n+\tif (finish_connect(data->conn))\n+\t\tret = -1;\n+\tdata->conn = NULL;\n+\n+\treturn ret;\n+}\n+\n+int transport_fetch_object_info(struct transport *transport)\n+{\n+\tif (!transport->vtable->fetch_object_info)\n+\t\tdie(_(\"remote does not support object-info\"));\n+\treturn transport->vtable->fetch_object_info(transport);\n+}\n+\n static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t       int nr_heads, struct ref **to_fetch)\n {\n@@ -1004,6 +1047,7 @@ static struct transport_vtable taken_over_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.disconnect\t= disconnect_git\n };\n@@ -1169,6 +1213,7 @@ static struct transport_vtable builtin_smart_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.connect\t= connect_git,\n \t.disconnect\t= disconnect_git\ndiff --git a/transport.h b/transport.h\nindex 7e5867cffa..9e85a4cd35 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -6,6 +6,7 @@\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n #include \"connect.h\"\n+#include \"odb.h\"\n \n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -55,6 +56,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n@@ -309,6 +314,11 @@ int transport_get_remote_bundle_uri(struct transport *transport);\n const struct git_hash_algo *transport_get_hash_algo(struct transport *transport);\n int transport_fetch_refs(struct transport *transport, struct ref *refs);\n \n+/*\n+ * Fetch the object info from remote\n+ */\n+int transport_fetch_object_info(struct transport *transport);\n+\n /*\n  * If this flag is set, unlocking will avoid to call non-async-signal-safe\n  * functions. This will necessarily leave behind some data structures which\n\n-- \n2.54.0\n"},{"id":"548110","messageId":"20260714-ps-eric-work-rebase-v17-11-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 11/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:45:07Z","receivedAt":"2026-07-14T11:45:28Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSince the info command in cat-file --batch-command prints object\ninfo for a given object, it is natural to add another command in\ncat-file --batch-command to print object info for a given object\nfrom a remote.\n\nAdd remote-object-info command to cat-file --batch-command.\n\nWhile info takes object ids one at a time, this creates overhead when\nmaking requests to a server. So remote-object-info instead can take\nmultiple object ids at once.\n\nThe cat-file --batch-command command is generally implemented in the\nfollowing manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, remote-object-info is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is remote-object-info:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode remote-object-info:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is remote-object-info:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, remote-object-info gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  29 +-\n builtin/cat-file.c                     | 144 ++++++-\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n t/meson.build                          |   1 +\n t/t1017-cat-file-remote-object-info.sh | 680 +++++++++++++++++++++++++++++++++\n 6 files changed, 859 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex 86b9181599..a7fa6674c3 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -169,6 +169,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -301,7 +308,8 @@ one per line, and print information based on the command given. With\n `--batch-command`, the `info` command followed by an object will print\n information about the object the same way `--batch-check` would, and the\n `contents` command followed by an object prints contents in the same way\n-`--batch` would.\n+`--batch` would. The `remote-object-info` command followed by a remote and\n+objects IDs prints object info from the remote without downloading the objects.\n \n You can specify the information shown for each object by using a custom\n `<format>`. The `<format>` is copied literally to stdout for each\n@@ -324,15 +332,12 @@ newline. The available atoms are:\n \treports).\n \n `objectsize:disk`::\n-\tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tThe size, in bytes, that the object takes up on disk.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n-\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n-\n+\tOtherwise, expands to the null OID (all zeroes).\n `rest`::\n \tIf this atom is used in the output string, input lines are split\n \tat the first whitespace boundary. All characters before that\n@@ -340,8 +345,14 @@ newline. The available atoms are:\n \tafter that first run of whitespace (i.e., the \"rest\" of the\n \tline) are output in place of the `%(rest)` atom.\n \n+The command `remote-object-info` only supports the `%(objectname)` and\n+`%(objectsize)` placeholders. See `CAVEATS` below for more information.\n+\n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which\n+use `%(objectname) %(objectsize)` because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified,\n+so DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -438,6 +449,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since only `%(objectname)` and `%(objectsize)` are currently\n+supported by the `remote-object-info` command. Using any other placeholder in\n+the format string will raise an error.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 26ad07b62c..77ecccdda3 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -29,6 +29,22 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/*\n+ * Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8 * 1024)\n+\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE \\\n+\t(MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -636,6 +652,80 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt,\n+\t\t\t   int argc,\n+\t\t\t   const char **argv,\n+\t\t\t   struct object_info **remote_object_info,\n+\t\t\t   struct oid_array *object_info_oids)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstruct transport *gtransport;\n+\n+\t/*\n+\t * TODO: Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT.\n+\t */\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid)) {\n+\t\t\tsize_t len = strlen(argv[i]);\n+\n+\t\t\tif (len < the_hash_algo->hexsz && len >= 4) {\n+\t\t\t\tsize_t j;\n+\t\t\t\tfor (j = 0; j < len; j++)\n+\t\t\t\t\tif (!isxdigit(argv[i][j]))\n+\t\t\t\t\t\tbreak;\n+\t\t\t\tif (j == len)\n+\t\t\t\t\tdie(_(\"remote-object-info does not support \"\n+\t\t\t\t\t      \"short oids, %d characters required\"),\n+\t\t\t\t\t    (int)the_hash_algo->hexsz);\n+\t\t\t}\n+\t\t\tdie(_(\"not a valid object name '%s'\"), argv[i]);\n+\t\t}\n+\t\toid_array_append(object_info_oids, &oid);\n+\t}\n+\n+\tif (!object_info_oids->nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\n+\tif (!gtransport->smart_options) {\n+\t\tretval = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n+\tgtransport->smart_options->object_info_oids = object_info_oids;\n+\n+\t/* 'objectsize' is the only option currently supported */\n+\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\tstring_list_append(&object_info_options, \"size\");\n+\n+\tif (object_info_options.nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\t\tretval = transport_fetch_object_info(gtransport);\n+\t}\n+cleanup:\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -717,6 +807,57 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\tstruct object_info *remote_object_info = NULL;\n+\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command too long\"));\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n+\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n+\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n+\t\t    MAX_ALLOWED_OBJ_LIMIT);\n+\n+\tif (get_remote_info(opt, count, argv, &remote_object_info,\n+\t\t\t    &object_info_oids))\n+\t\tgoto cleanup;\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t} else {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+\toid_array_clear(&object_info_oids);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -748,8 +889,9 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1 },\n \t{ \"info\", parse_cmd_info, 1 },\n-\t{ \"flush\", NULL, 0 },\n \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n+\t{ \"flush\", NULL, 0 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex 6453b1d6fa..07f019a0f6 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1694,3 +1694,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)\n \n \treturn &transaction->base;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+}\ndiff --git a/odb.h b/odb.h\nindex 3834a0dcbf..42e3934035 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -573,4 +573,7 @@ void parse_alternates(const char *string,\n \t\t      const char *relative_base,\n \t\t      struct strvec *out);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* ODB_H */\ndiff --git a/t/meson.build b/t/meson.build\nindex 7c3c070426..562923c257 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -171,6 +171,7 @@ integration_tests = [\n   't1014-read-tree-confusing.sh',\n   't1015-read-index-unmerged.sh',\n   't1016-compatObjectFormat.sh',\n+  't1017-cat-file-remote-object-info.sh',\n   't1020-subdirectory.sh',\n   't1022-read-tree-partial-clone.sh',\n   't1050-large.sh',\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..49b6660934\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,680 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n+\n+unstored_content=\"Hello Git\"\n+unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not support short oids' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_short_oid\n+\t\tEOF\n+\t\ttest_grep \"does not support short oids\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not die on missing oid like info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tgit cat-file --batch-command >local <<-EOF &&\n+\t\tinfo $unstored_oid\n+\t\tEOF\n+\t\tgit cat-file --batch-command >remote <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $unstored_oid\n+\t\tEOF\n+\t\ttest_cmp local remote\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n\n-- \n2.54.0\n"},{"id":"548109","messageId":"20260714-ps-eric-work-rebase-v17-12-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 12/13] cat-file: validate remote atoms with an allow-list","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:45:08Z","receivedAt":"2026-07-14T11:45:29Z","isPatch":true,"body":"strstr() is not enough to validate the format placeholders in\nremote-object-info causing two errors:\n\n1. Atoms recognized by expand_atom() but the remote doesn't returns 1,\n   but data->type contains garbage causing segfault.\n\n2. expand_atom() returns 0 for unknown atoms, calling\n   strbuf_expand_bad_format() which ends up dying, blocking local\n   queries if the same format is shared.\n\nAdd an allow-list with the supported atoms at the top of expand_atom().\nIn remote mode, unsupported atoms return 1 leaving the buffer empty,\nhonoring how for-each-ref handles known but inapplicable atoms.\n\nAs extra safety, initialize data->type to OBJ_BAD and add a NULL check\nfor type_name() so uninitialized data doesn't cause segfault.\n\nUpdate tests that expect previous die() behavior to expect an empty\nstring and add an explicit test for empty string return on unknown\nplaceholder.\n\nUpdate cat-file command documentation regarding remote-object-info.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  2 +-\n builtin/cat-file.c                     | 41 +++++++++++++++++++++++++++-------\n t/t1017-cat-file-remote-object-info.sh | 27 ++++++++++++++++++----\n 3 files changed, 57 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex a7fa6674c3..643eac9245 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -451,7 +451,7 @@ CAVEATS\n \n Note that since only `%(objectname)` and `%(objectsize)` are currently\n supported by the `remote-object-info` command. Using any other placeholder in\n-the format string will raise an error.\n+the format string will return an empty string in its position.\n \n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 77ecccdda3..af388b6238 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -333,8 +333,18 @@ struct expand_data {\n \t * optimized out.\n \t */\n \tunsigned skip_object_info : 1;\n+\n+\t/*\n+\t * Flags about when an object info is being fetched from remote.\n+\t */\n+\tunsigned is_remote:1;\n+};\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n+\n+static const char *remote_object_info_atoms[] = {\n+\t\"objectname\",\n+\t\"objectsize\",\n };\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -345,14 +355,31 @@ static int is_atom(const char *atom, const char *s, int slen)\n static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n+\tif (data->is_remote) {\n+\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n+\t\tfor (i = 0; i < allowed_nr; i++)\n+\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * On remote, skip unsupported atoms returning an empty sb,\n+\t\t * honoring how for-each-ref handles known but inapplicable\n+\t\t * atoms (e.g. %(tagger)).\n+\t\t */\n+\t\tif (i == allowed_nr)\n+\t\t\treturn 1;\n+\t}\n+\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n \t\t\tstrbuf_add_oid_hex(sb, &data->oid);\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n-\t\tif (data->mark_query)\n+\t\tif (data->mark_query) {\n \t\t\tdata->info.typep = &data->type;\n-\t\telse\n-\t\t\tstrbuf_addstr(sb, type_name(data->type));\n+\t\t} else {\n+\t\t\tconst char *t = type_name(data->type);\n+\t\t\tstrbuf_addstr(sb, t ? t : \"\");\n+\t\t}\n \t} else if (is_atom(\"objectsize\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.sizep = &data->size;\n@@ -709,10 +736,6 @@ static int get_remote_info(struct batch_options *opt,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\t/* 'objectsize' is the only option currently supported */\n-\tif (!strstr(opt->format, \"%(objectsize)\"))\n-\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n-\n \tstring_list_append(&object_info_options, \"size\");\n \n \tif (object_info_options.nr > 0) {\n@@ -842,7 +865,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n \t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tdata->is_remote = 1;\n \t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t\tdata->is_remote = 0;\n \t\t} else {\n \t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n \t\t}\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 49b6660934..6bc863c391 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -236,6 +236,21 @@ test_expect_success 'remote-object-info does not die on missing oid like info' '\n \t)\n '\n \n+# This tests depends on %(objecttype) not being supported yet, once supported\n+# it needs to be updated.\n+test_expect_success 'unsupported placeholder on remote returns empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"\" >expect &&\n+\t\tgit cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n # Test --batch-command remote-object-info with 'git://' and\n # transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n@@ -575,10 +590,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (obje\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\techo \"$hello_oid \" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize:disk)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n@@ -587,10 +604,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (delt\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(deltabase)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n\n-- \n2.54.0\n"},{"id":"548111","messageId":"20260714-ps-eric-work-rebase-v17-13-afabfc83260e@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v17 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T11:45:09Z","receivedAt":"2026-07-14T11:45:31Z","isPatch":true,"body":"The static allow-list in expand_atom() is hardcoded to only allow\n\"objectname\" and \"objectsize\" for remote queries. This works because\nup to this point all servers will either support object-info with name\nand size or they do not support them at all, but we cannot expect that\nin a future different servers with different git versions to have the\nsame object-info capabilities. Therefore, the allow_list needs to be\ndynamic depending on what the server advertises.\n\nThe client will now:\n\n1. Request the protocol option that the placeholder refers to (i.e.\n   \"size\" when \"%(objectsize)\").\n\n2. Filters the request in fetch_object_info() dropping any option that\n   the server does not advertise.\n\n3. After the fetching, the options that haven't been dropped are the ones\n   fetched and supported by the server, these supported options are\n   mapped and remote_allowed_atoms is populated with the placeholders.\n\n4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n   the static allow_list had.\n\nMove object_info_options out of get_remote_info so the caller which has\ndata can select what options will be requested instead of requesting\nalways size.\nMove batch_object_write() out so there will always be an output even if\nall the placeholders are not supported by the server (returns an empty\nline).\n\nInclude \"type\" in the object_info_options so once the server supports\nit, the clients know already how to request it.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c  | 97 +++++++++++++++++++++++++++++++++++------------------\n fetch-object-info.c | 20 +++++++++++\n 2 files changed, 84 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex af388b6238..20d9705a7f 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -338,13 +338,11 @@ struct expand_data {\n \t * Flags about when an object info is being fetched from remote.\n \t */\n \tunsigned is_remote:1;\n-};\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n \n-static const char *remote_object_info_atoms[] = {\n-\t\"objectname\",\n-\t\"objectsize\",\n+\tstruct string_list remote_allowed_atoms;\n };\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -356,17 +354,11 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n \tif (data->is_remote) {\n-\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n-\t\tfor (i = 0; i < allowed_nr; i++)\n-\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\tsize_t i;\n+\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n \t\t\t\tbreak;\n-\n-\t\t/*\n-\t\t * On remote, skip unsupported atoms returning an empty sb,\n-\t\t * honoring how for-each-ref handles known but inapplicable\n-\t\t * atoms (e.g. %(tagger)).\n-\t\t */\n-\t\tif (i == allowed_nr)\n+\t\tif (i == data->remote_allowed_atoms.nr)\n \t\t\treturn 1;\n \t}\n \n@@ -683,12 +675,12 @@ static int get_remote_info(struct batch_options *opt,\n \t\t\t   int argc,\n \t\t\t   const char **argv,\n \t\t\t   struct object_info **remote_object_info,\n-\t\t\t   struct oid_array *object_info_oids)\n+\t\t\t   struct oid_array *object_info_oids,\n+\t\t\t   struct string_list *object_info_options)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n \tstruct object_id oid;\n-\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tstruct transport *gtransport;\n \n \t/*\n@@ -736,15 +728,12 @@ static int get_remote_info(struct batch_options *opt,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\tstring_list_append(&object_info_options, \"size\");\n-\n-\tif (object_info_options.nr > 0) {\n-\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tif (object_info_options->nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = object_info_options;\n \t\tgtransport->smart_options->object_info_data = *remote_object_info;\n \t\tretval = transport_fetch_object_info(gtransport);\n \t}\n cleanup:\n-\tstring_list_clear(&object_info_options, 0);\n \ttransport_disconnect(gtransport);\n \treturn retval;\n }\n@@ -830,6 +819,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+struct protocol_placeholder_entry {\n+\tconst char *option;\n+\tconst char *atom;\n+};\n+\n+static const struct protocol_placeholder_entry remote_atom_map[] = {\n+\t{\"size\", \"objectsize\"},\n+\t{\"type\", \"objecttype\"},\n+\t/*\n+\t * Add new protocol options here. Even if the server doesn't support\n+\t * them the allow_list will drop them if the server doesn't advertise\n+\t * them.\n+\t */\n+};\n+\n static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n@@ -839,6 +843,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct object_info *remote_object_info = NULL;\n \tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \n \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n \t\tdie(_(\"remote-object-info command too long\"));\n@@ -851,32 +856,57 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n+\tif (data->info.sizep)\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\tif (data->info.typep)\n+\t\tstring_list_append(&object_info_options, \"type\");\n+\n \tif (get_remote_info(opt, count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids))\n+\t\t\t    &object_info_oids, &object_info_options))\n \t\tgoto cleanup;\n \n+\tstring_list_clear(&data->remote_allowed_atoms, 0);\n+\tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n+\tfor (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n+\t\tif (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n+\t\t\tstring_list_append(&data->remote_allowed_atoms,\n+\t\t\t\t\t   remote_atom_map[i].atom);\n+\n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tint found = 0;\n \t\tdata->oid = object_info_oids.oid[i];\n+\t\t/*\n+\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t * information from server without downloading them.\n+\t\t */\n \t\tif (remote_object_info[i].sizep) {\n-\t\t\t/*\n-\t\t\t * When reaching here, it means remote-object-info can retrieve\n-\t\t\t * information from server without downloading them.\n-\t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n-\t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tdata->is_remote = 1;\n-\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n-\t\t\tdata->is_remote = 0;\n-\t\t} else {\n-\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t\tfound = 1;\n \t\t}\n+\n+\t\tif (remote_object_info[i].typep) {\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\t\t\tfound = 1;\n+\t\t}\n+\n+\t\tif (!found && object_info_options.nr > 0) {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid),\n+\t\t\t\t\t     &data->oid, \"missing\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tdata->is_remote = 1;\n+\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n \n cleanup:\n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n+\tstring_list_clear(&object_info_options, 0);\n \tfree(line_to_split);\n \tfree(argv);\n \tfree(remote_object_info);\n@@ -1192,6 +1222,7 @@ static int batch_objects(struct batch_options *opt)\n  cleanup:\n \tstrbuf_release(&input);\n \tstrbuf_release(&output);\n+\tstring_list_clear(&data.remote_allowed_atoms, 0);\n \tcfg->warn_on_object_refname_ambiguity = save_warning;\n \treturn retval;\n }\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 91685cb355..e9b1011d07 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -55,6 +55,26 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \tcase protocol_v2:\n \t\tif (!server_supports_v2(\"object-info\"))\n \t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\t/*\n+\t\t * When removing an element from the list it gets swapped by the\n+\t\t * last element, iterate backwards to prevent elements skipping\n+\t\t * evaluation.\n+\t\t *\n+\t\t * object_info_options->nr can be safely casted without overflow\n+\t\t * beacuse the number of options is a small known number (the\n+\t\t * supported placeholders which currently are size and type).\n+\t\t */\n+\t\tfor (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)\n+\t\t\tif (!server_supports_feature(\"object-info\",\n+\t\t\t\t\t\t     args->object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(args->object_info_options, i, 0);\n+\t\t/*\n+\t\t * If no options are left after the filtering, avoid unnecessary\n+\t\t * request to the server.\n+\t\t */\n+\t\tif (!args->object_info_options->nr)\n+\t\t\treturn 0;\n+\n \t\tsend_object_info_request(fd_out, args);\n \t\tbreak;\n \tcase protocol_v1:\n\n-- \n2.54.0\n"},{"id":"548138","messageId":"xmqqik6htpv4.fsf@gitster.g","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-10-afabfc83260e@gmail.com","subject":"Re: [PATCH GSoC v17 10/13] transport: add client support for object-info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-14T17:58:39Z","receivedAt":"2026-07-14T17:58:43Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> +\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n\nAn overly long line.  Format it like this, perhaps?\n\n\tfor (size_t i = 0;\n\t     packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr;\n\t     i++) {\n\nor even:\n\n\tfor (size_t i = 0;\n\t     packet_reader_read(reader) == PACKET_READ_NORMAL &&\n\t     i < args->oids->nr;\n\t     i++) {\n\n\n> +\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n> +\n> +\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n> +\t\tif (size_index >= 0) {\n> +\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\")) {\n> +\t\t\t\tFREE_AND_NULL(object_info_data[i].sizep);\n> +\t\t\t\tstring_list_clear(&object_info_values, 0);\n> +\t\t\t\tcontinue;\n> +\t\t\t}\n> +\n> +\t\t\tif (parse_object_size(object_info_values.items[1 + size_index].string,\n> +\t\t\t\t\t      object_info_data[i].sizep))\n> +\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n> +\t\t\t\t    object_info_values.items[0].string,\n> +\t\t\t\t    object_info_values.items[1 + size_index].string);\n> +\t\t}\n> +\n> +\t\tstring_list_clear(&object_info_values, 0);\n\nIs this not trusting the other side too much?\n\nIf the other end returns fewer values than expected (e.g., if a\nbuggy or malicious server returns only \"<oid>\" without a trailing\nspace for an unrecognized object, or if we request multiple\nattributes in the future and the server returns fewer values than\nexpected), string_list_split may return a list with fewer elements\nthan size_index + 1.  Accessing object_info_values.items[size_index\n+ 1] will then result in an out-of-bounds read/crash.\n\nBy the way, from a stylistic standpoint, \"size_index + 1\" reads a\nbit more naturally than the \"1 + size_index\" used in the current\npatch.\n"},{"id":"548156","messageId":"xmqq8q7dto8d.fsf@gitster.g","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"Re: [PATCH GSoC v17 00/13] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-14T18:33:54Z","receivedAt":"2026-07-14T18:33:57Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> This patch series is a continuation of Eric Ju's\n> (eric.peijian@gmail.com) and Calvin Wan's (calvinwan@google.com) patch\n> series [1] and [2] respectively.\n\nYuck.  I thought we had this marked as \"Will merge to 'next'?\" for\nsome time and this morning I pushed out a merge to 'next' of v16.\nI'll revert the merge and replace.\n"},{"id":"548160","messageId":"DJYJN18OMC4G.2YYRORCZN519Y@gmail.com","threadId":"61695","inReplyTo":"xmqqik6htpv4.fsf@gitster.g","subject":"Re: [PATCH GSoC v17 10/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T19:33:25Z","receivedAt":"2026-07-14T19:33:28Z","isPatch":true,"body":"On Tue Jul 14, 2026 at 7:58 PM CEST, Junio C Hamano wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> +\tfor (size_t i = 0; packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr; i++) {\n>\n> An overly long line.  Format it like this, perhaps?\n>\n> \tfor (size_t i = 0;\n> \t     packet_reader_read(reader) == PACKET_READ_NORMAL && i < args->oids->nr;\n> \t     i++) {\n>\n> or even:\n>\n> \tfor (size_t i = 0;\n> \t     packet_reader_read(reader) == PACKET_READ_NORMAL &&\n> \t     i < args->oids->nr;\n> \t     i++) {\n>\n\nWill wrap that line, thanks.\n\n>\n>> +\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n>> +\n>> +\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n>> +\t\tif (size_index >= 0) {\n>> +\t\t\tif (!strcmp(object_info_values.items[1 + size_index].string, \"\")) {\n>> +\t\t\t\tFREE_AND_NULL(object_info_data[i].sizep);\n>> +\t\t\t\tstring_list_clear(&object_info_values, 0);\n>> +\t\t\t\tcontinue;\n>> +\t\t\t}\n>> +\n>> +\t\t\tif (parse_object_size(object_info_values.items[1 + size_index].string,\n>> +\t\t\t\t\t      object_info_data[i].sizep))\n>> +\t\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n>> +\t\t\t\t    object_info_values.items[0].string,\n>> +\t\t\t\t    object_info_values.items[1 + size_index].string);\n>> +\t\t}\n>> +\n>> +\t\tstring_list_clear(&object_info_values, 0);\n>\n> Is this not trusting the other side too much?\n>\n> If the other end returns fewer values than expected (e.g., if a\n> buggy or malicious server returns only \"<oid>\" without a trailing\n> space for an unrecognized object, or if we request multiple\n> attributes in the future and the server returns fewer values than\n> expected), string_list_split may return a list with fewer elements\n> than size_index + 1.  Accessing object_info_values.items[size_index\n> + 1] will then result in an out-of-bounds read/crash.\n\nI will add a check for a malformed response from the server so \"<oid>\" is\nconsidered corrupted, similar to the size values a few lines below.\n\nA subsequent commit in this series (13) adds a filter that drops\nattributes requested by the client but that the server doesn't support so\nwe should expect full return of the attributes asked or \"<oid> SP\".\n\nI'll add a guard just in case in a future what we expect changes.\n\n>\n> By the way, from a stylistic standpoint, \"size_index + 1\" reads a\n> bit more naturally than the \"1 + size_index\" used in the current\n> patch.\n\n\nWill change it.\n\nThanks for the feedback,\nPablo.\n\n"},{"id":"548185","messageId":"DJYNU7D4A7C8.3Q2Q4DX27RXC0@gmail.com","threadId":"61695","inReplyTo":"xmqq8q7dto8d.fsf@gitster.g","subject":"Re: [PATCH GSoC v17 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-14T22:50:51Z","receivedAt":"2026-07-14T22:50:55Z","isPatch":true,"body":"On Tue Jul 14, 2026 at 8:33 PM CEST, Junio C Hamano wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> This patch series is a continuation of Eric Ju's\n>> (eric.peijian@gmail.com) and Calvin Wan's (calvinwan@google.com) patch\n>> series [1] and [2] respectively.\n>\n> Yuck.  I thought we had this marked as \"Will merge to 'next'?\" for\n> some time and this morning I pushed out a merge to 'next' of v16.\n> I'll revert the merge and replace.\n\nHi!\n\nSorry, I'm confused about the last line about 'next' and the replace.\n\nYou gave me feedback for v17 10th commit:\n\nhttps://lore.kernel.org/git/xmqqik6htpv4.fsf@gitster.g/\n\nShould I send a v18 or a new patch on top of 'next'?\n\nThe fix is simple and I already have it on my local, I just want to do\nwhatever is better.\n\nRegards,\nPablo.\n"},{"id":"548188","messageId":"xmqqpl0pqdag.fsf@gitster.g","threadId":"61695","inReplyTo":"DJYNU7D4A7C8.3Q2Q4DX27RXC0@gmail.com","subject":"Re: [PATCH GSoC v17 00/13] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-15T00:58:31Z","receivedAt":"2026-07-15T00:58:36Z","isPatch":true,"body":"\"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n\n> You gave me feedback for v17 10th commit:\n>\n> https://lore.kernel.org/git/xmqqik6htpv4.fsf@gitster.g/\n>\n> Should I send a v18 or a new patch on top of 'next'?\n>\n> The fix is simple and I already have it on my local, I just want to do\n> whatever is better.\n\nI had v16 merged (prematurely) to 'next' and then saw v17, so I\nreverted the merge, which means 'next' no longer has your topic.\n\nAnd v17, as a brand new iteration, is not in, and will stay out of,\n'next' until we are happy with it.  If you have an updated v18,\nplease send it as a whole replacement.\n\nThanks.  How close are we to the finish line, by the way?\n\n"},{"id":"548243","messageId":"DJZ0JXIP13UO.DH1ONBDEVF3N@gmail.com","threadId":"61695","inReplyTo":"xmqqpl0pqdag.fsf@gitster.g","subject":"Re: [PATCH GSoC v17 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T08:48:40Z","receivedAt":"2026-07-15T08:48:47Z","isPatch":true,"body":"On Wed Jul 15, 2026 at 2:58 AM CEST, Junio C Hamano wrote:\n> \"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n>\n>> You gave me feedback for v17 10th commit:\n>>\n>> https://lore.kernel.org/git/xmqqik6htpv4.fsf@gitster.g/\n>>\n>> Should I send a v18 or a new patch on top of 'next'?\n>>\n>> The fix is simple and I already have it on my local, I just want to do\n>> whatever is better.\n>\n> I had v16 merged (prematurely) to 'next' and then saw v17, so I\n> reverted the merge, which means 'next' no longer has your topic.\n>\n> And v17, as a brand new iteration, is not in, and will stay out of,\n> 'next' until we are happy with it.  If you have an updated v18,\n> please send it as a whole replacement.\n\nI'll send v18 very soon.\n\n>\n> Thanks.  How close are we to the finish line, by the way?\n\nThere's one month left. Final evaluation ends on 17th August (more weeks\ncan be asked, if it seems too rushed, but I think it will fit just\nright). I have the other series that supports objecttype but I wanted\nthis to be on 'next' before sending it.\nI expect it to go faster because it's on top of all the\ninfrastructure built in this series and it's ~150 lines.\n\n"},{"id":"548255","messageId":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260714-ps-eric-work-rebase-v17-0-afabfc83260e@gmail.com","subject":"[PATCH GSoC v18 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:08Z","receivedAt":"2026-07-15T09:52:27Z","isPatch":true,"body":"This patch series is a continuation of Eric Ju's\n(eric.peijian@gmail.com) and Calvin Wan's (calvinwan@google.com) patch\nseries [1] and [2] respectively.\n\nSometimes it is beneficial to retrieve information about an object\nwithout having to download it completely. The server logic for\nretrieving size has already been implemented and merged in a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20) [3].\nThis patch series implement the client option for it.\n\nEric's series adds the remote-object-info command to cat-file\n--batch-command. This command allows the client to make an object-info\ncommand request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info\ncapability, cat-file --batch-command will die.\n\nIf a user attempts to use remote-object-info with protocol v1, cat-file\n--batch-command will die.\n\nCurrently, only the size (%(objectsize)) is supported end to end in this\nimplementation. The type (%(objecttype)) is known by the client's\nallow-list and request path but is not supported on the server side\nnor the response parsing. A follow up series will add full end-to-end\nsupport for %(objecttype).\n\nThe default format for remote-object-info is set to \"%(objectname)\n%(objectsize)\". Once %(objecttype) is supported, the default format will\nbe unified accordingly.\n\nIf the batch command format includes unsupported fields such as\n%(objecttype), %(objectsize:disk), or %(deltabase), the command will\nreturn empty strings for each unsupported field.\n\nThis series completes Eric's work mainly with the refactor of the\nvalidation of the placeholder with an allow-list that filters what the\nclient asks with what the server is capable of provide following Jeff\nKing's idea [4].\n\nGitHub CI: https://github.com/pabloosabaterr/git/actions/runs/29404390713\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n[4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n\nChanges since v17:\n\nAt 10th commit: transport: add client support for object-info\n- style\n- enforced the server response handling, not allowing bare \"<oid>\" or\n  responses with a different number of attributes different from the\n  number asked.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n\n---\nCalvin Wan (3):\n      fetch-pack: move fetch initialization\n      serve: advertise object-info feature\n      transport: add client support for object-info\n\nEric Ju (3):\n      cat-file: declare loop counter inside for()\n      t1006: split test utility functions into new 'lib-cat-file.sh'\n      cat-file: add remote-object-info to batch-command\n\nPablo Sabater (7):\n      transport-helper: fix memory leak of helper on disconnect\n      fetch-pack: fix hash_algo variable type\n      fetch-pack: drop static advertise_sid variable\n      fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n      connect: make write_fetch_command_and_capabilities() more generic\n      cat-file: validate remote atoms with an allow-list\n      cat-file: make remote-object-info allow-list dynamic\n\n Documentation/git-cat-file.adoc        |  29 +-\n Documentation/gitprotocol-v2.adoc      |  11 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 221 ++++++++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    | 141 +++++++\n fetch-object-info.h                    |  22 ++\n fetch-pack.c                           |  58 +--\n fetch-pack.h                           |   1 +\n meson.build                            |   1 +\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n serve.c                                |   5 +-\n t/lib-cat-file.sh                      |  16 +\n t/meson.build                          |   1 +\n t/t1006-cat-file.sh                    |  13 +-\n t/t1017-cat-file-remote-object-info.sh | 699 +++++++++++++++++++++++++++++++++\n transport-helper.c                     |  12 +-\n transport-internal.h                   |   8 +\n transport.c                            |  45 +++\n transport.h                            |  10 +\n 22 files changed, 1265 insertions(+), 84 deletions(-)\n\n---\nbase-commit: f60db8d575adb79761d363e026fb49bddf330c73\n"},{"id":"548256","messageId":"20260715-ps-eric-work-rebase-v18-1-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 01/13] transport-helper: fix memory leak of helper on disconnect","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:09Z","receivedAt":"2026-07-15T09:52:28Z","isPatch":true,"body":"disconnect_helper() only frees data inside of the if(data->helper) block\n[1]. When the transport is disconnected without the helper being fully\nstarted, data->name allocated in transport_helper_init()\nis never freed.\n\nMove FREE_AND_NULL(data->name) outside the conditional block so it's\nalways freed on disconnect.\n\n[1]: https://lore.kernel.org/git/05fbadbae2184479c87c37675dde7bd79b3e32ab.1716465556.git.ps@pks.im/\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n transport-helper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 80f90eb7ba..f195070788 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tres = finish_command(data->helper);\n-\t\tFREE_AND_NULL(data->name);\n \t\tFREE_AND_NULL(data->helper);\n \t}\n+\tFREE_AND_NULL(data->name);\n \treturn res;\n }\n \n\n-- \n2.54.0\n"},{"id":"548257","messageId":"20260715-ps-eric-work-rebase-v18-2-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 02/13] cat-file: declare loop counter inside for()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:10Z","receivedAt":"2026-07-15T09:52:30Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSome code used in this series declares variable i and only uses it\nin a for loop, not in any other logic outside the loop.\n\nChange the declaration of i to be inside the for loop for readability.\nWhile at it, we also change its type from int to size_t where the\nlatter makes more sense.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c | 13 ++++---------\n fetch-pack.c       |  3 +--\n 2 files changed, 5 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 60869b8b37..26ad07b62c 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -721,14 +721,12 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n \t\tstruct queued_cmd *cmd,\n-\t\tint nr)\n+\t\tsize_t nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -736,9 +734,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -765,7 +761,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -775,7 +770,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 120e01f3cf..f13951d154 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n\n-- \n2.54.0\n"},{"id":"548259","messageId":"20260715-ps-eric-work-rebase-v18-3-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 03/13] t1006: split test utility functions into new 'lib-cat-file.sh'","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:11Z","receivedAt":"2026-07-15T09:52:31Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nThis refactor extracts utility functions from the cat-file's test\nscript 't1006-cat-file.sh' into a new 'lib-cat-file.sh' dedicated\nlibrary file.\n\nA subsequent commit will need this functions, the goal is to improve\ncode reuse and readability,enabling future tests to leverage these\nutilities without duplicating code.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 13 +------------\n 2 files changed, 17 insertions(+), 12 deletions(-)\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..44af232d74\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NULL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 8e2c52652c..8360f3bbd9 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -4,6 +4,7 @@ test_description='git cat-file'\n \n . ./test-lib.sh\n . \"$TEST_DIRECTORY/lib-loose.sh\"\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     object_name=\"$2\"\n\n-- \n2.54.0\n"},{"id":"548260","messageId":"20260715-ps-eric-work-rebase-v18-4-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 04/13] fetch-pack: fix hash_algo variable type","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:12Z","receivedAt":"2026-07-15T09:52:32Z","isPatch":true,"body":"hash_algo_by_name() returns \"unsigned int\", but the variable that it is\nassigned to is \"int\".\n\nChange hash_algo variable type to match hash_algo_by_name() type, also\nmake it const because it is never modified.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f13951d154..eea72b2500 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1395,7 +1395,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t}\n \n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n \t\t\t    the_hash_algo->name, hash_name);\n\n-- \n2.54.0\n"},{"id":"548261","messageId":"20260715-ps-eric-work-rebase-v18-5-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 05/13] fetch-pack: drop static advertise_sid variable","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:13Z","receivedAt":"2026-07-15T09:52:34Z","isPatch":true,"body":"write_fetch_command_and_capabilities() is moved to 'connect.c' in a\nsubsequent commit. To prepare for that, drop the static variable usage\nof advertise_sid. Currently advertise_sid is used in two places:\n\n1. In function do_fetch_pack():\n        if (!server_supports(\"session-id\"))\n               advertise_sid = 0;\n\n2. In function fetch_pack_config():\n        repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n\nAbout 1, it is only relevant for v0/v1 protocol, move it into\nfind_common().\n\nAbout 2, call repo_config_get_bool() inside of\nwrite_fetch_command_and_capabilities() and find_common() replacing the\nstatic variable.\n\nBecause repo_config_get_bool() leaves advertise_sid as is if it is not\nset, initialize it to 0 matching its default.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 13 +++++++------\n 1 file changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex eea72b2500..8e04db8640 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -49,7 +49,6 @@ static int fetch_fsck_objects = -1;\n static int transfer_fsck_objects = -1;\n static int agent_supported;\n static int server_supports_filtering;\n-static int advertise_sid;\n static struct shallow_lock shallow_lock;\n static const char *alternate_shallow_file;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n@@ -363,6 +362,9 @@ static int find_common(struct fetch_negotiator *negotiator,\n \tsize_t state_len = 0;\n \tstruct packet_reader reader;\n \tstruct oidset negotiation_include_oids = OIDSET_INIT;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tif (args->stateless_rpc && multi_ack == 1)\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--stateless-rpc\", \"multi_ack_detailed\");\n@@ -414,7 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator,\n \t\t\tif (deepen_not_ok)      strbuf_addstr(&c, \" deepen-not\");\n \t\t\tif (agent_supported)    strbuf_addf(&c, \" agent=%s\",\n \t\t\t\t\t\t\t    git_user_agent_sanitized());\n-\t\t\tif (advertise_sid)\n+\t\t\tif (advertise_sid && server_supports(\"session-id\"))\n \t\t\t\tstrbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n \t\t\tif (args->filter_options.choice)\n \t\t\t\tstrbuf_addstr(&c, \" filter\");\n@@ -1160,9 +1162,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\t\t\t      (int)agent_len, agent_feature);\n \t}\n \n-\tif (!server_supports(\"session-id\"))\n-\t\tadvertise_sid = 0;\n-\n \tif (server_supports(\"shallow\"))\n \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n \telse if (args->depth > 0 || is_repository_shallow(r))\n@@ -1380,6 +1379,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t\t\t\t\t\t const struct string_list *server_options)\n {\n \tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tensure_server_supports_v2(\"fetch\");\n \tpacket_buf_write(req_buf, \"command=fetch\");\n@@ -1998,7 +2000,6 @@ static void fetch_pack_config(void)\n \trepo_config_get_bool(the_repository, \"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n \trepo_config_get_bool(the_repository, \"fetch.fsckobjects\", &fetch_fsck_objects);\n \trepo_config_get_bool(the_repository, \"transfer.fsckobjects\", &transfer_fsck_objects);\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \tif (!uri_protocols.nr) {\n \t\tchar *str;\n \n\n-- \n2.54.0\n"},{"id":"548262","messageId":"20260715-ps-eric-work-rebase-v18-6-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 06/13] fetch-pack: move write_fetch_command_and_capabilities() to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:14Z","receivedAt":"2026-07-15T09:52:36Z","isPatch":true,"body":"write_fetch_command_and_capabilities() is refactored in a subsequent\ncommit where it becomes a more general-purpose function, making it\nmore accessible to additional commands in the future.\n\nMove write_fetch_command_and_capabilities() to 'connect.c', where\nthere are similar purpose functions.\n\nBecause string_list is only used as a pointer, use a forward\ndeclaration [1].\n\n[1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 34 ----------------------------------\n 3 files changed, 38 insertions(+), 34 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 47e39d2a73..c09947cc56 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,6 +700,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(\"fetch\");\n+\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t    the_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t    the_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n static const char *url_scheme_name(enum url_scheme scheme)\n {\n \tswitch (scheme) {\ndiff --git a/connect.h b/connect.h\nindex aa482a37fb..c4f6ea4b0a 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+struct string_list;\n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 8e04db8640..5e7c4f1d46 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1375,40 +1375,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\tint advertise_sid = 0;\n-\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n\n-- \n2.54.0\n"},{"id":"548263","messageId":"20260715-ps-eric-work-rebase-v18-7-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 07/13] connect: make write_fetch_command_and_capabilities() more generic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:15Z","receivedAt":"2026-07-15T09:52:37Z","isPatch":true,"body":"Refactor write_fetch_command_and_capabilities(), enabling it to serve\nboth fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol https://git-scm.com/docs/protocol-v2, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162\n(object-info: support for retrieving object info, 2021-04-20).\n\nRefactor the function signature to accept a command instead of the\nhardcoded \"fetch\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 8 ++++----\n connect.h    | 8 ++++++--\n fetch-pack.c | 4 ++--\n 3 files changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex c09947cc56..127ed4a2e6 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,16 +700,16 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options)\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \tint advertise_sid = 0;\n \n \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\ndiff --git a/connect.h b/connect.h\nindex c4f6ea4b0a..c2bf492ed9 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -35,7 +35,11 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       const char *error);\n \n struct string_list;\n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options);\n+/*\n+ * Writes a command along with the requested server capabilities/features into a\n+ * request buffer.\n+ */\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 5e7c4f1d46..783e3474a6 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1386,7 +1386,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2253,7 +2253,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n\n-- \n2.54.0\n"},{"id":"548264","messageId":"20260715-ps-eric-work-rebase-v18-8-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 08/13] fetch-pack: move fetch initialization","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:16Z","receivedAt":"2026-07-15T09:52:38Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized in\nFETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 783e3474a6..2ab5ba108b 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1735,18 +1735,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n\n-- \n2.54.0\n"},{"id":"548265","messageId":"20260715-ps-eric-work-rebase-v18-9-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 09/13] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:17Z","receivedAt":"2026-07-15T09:52:39Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This allows a client\nto decide whether to query the server for object-info or fetch as a\nfallback.\n\nWhile at it, update the object-info section in 'gitprotocol-v2.adoc':\n- Require full obj-oid explicitly.\n- Fix parentheses.\n- Define obj-size explicitly.\n- Make obj-size optional in obj-info and document the behavior\n  for unrecognized object IDs.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/gitprotocol-v2.adoc | 11 ++++++++---\n serve.c                           |  5 ++++-\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 2beb70595f..d3530c52ea 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -568,21 +568,26 @@ An `object-info` request takes the following arguments:\n \n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n-\tinformation for.\n+\tinformation for. They must be full object IDs.\n \n The response of `object-info` is a list of the requested object ids\n and associated requested information, each separated by a single space.\n \n \toutput = info flush-pkt\n \n-\tinfo = PKT-LINE(attrs) LF)\n+\tinfo = PKT-LINE(attrs LF)\n \t\t*PKT-LINE(obj-info LF)\n \n \tattrs = attr | attrs SP attrs\n \n+\tobj-size = 1*DIGIT\n+\n \tattr = \"size\"\n \n-\tobj-info = obj-id SP obj-size\n+\tobj-info = obj-id SP [obj-size]\n+\n+\tIf the server does not recognize the object id, the response will be\n+\t`obj-id SP` regardless of the number of attributes requested.\n \n bundle-uri\n ~~~~~~~~~~\ndiff --git a/serve.c b/serve.c\nindex 49a6e39b1d..2b07d922b3 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\t/* Currently only size is supported */\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n\n-- \n2.54.0\n"},{"id":"548266","messageId":"20260715-ps-eric-work-rebase-v18-10-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 10/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:18Z","receivedAt":"2026-07-15T09:52:41Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nIntroduce client-side support for the object-info capability.\n\nAdd its own function for object-info separate from existing fetch\ninfrastructure.\n\nCurrently, the client supports requesting a list of object IDs with\nthe size feature from a v2 server. If the server does not advertise\nthis feature (i.e., transfer.advertiseobjectinfo is set to false),\nthe client returns an error and exit.\n\nNote that:\n\n1. the entire request is written into req_buf before being sent to the\n   remote. This approach follows the pattern used in the\n   send_fetch_request() logic within 'fetch-pack.c'. Streaming the\n   request is not addressed in this patch.\n\n2. When the server does not recognize an OID, following the v2 protocol,\n   the server returns \"<OID> SP\", when this happens,\n   fetch_object_info() sets the corresponding size pointer to NULL so\n   that callers can detect and handle it.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Makefile             |   1 +\n fetch-object-info.c  | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h  |  22 ++++++++++\n fetch-pack.h         |   1 +\n meson.build          |   1 +\n transport-helper.c   |  10 +++++\n transport-internal.h |   8 ++++\n transport.c          |  45 +++++++++++++++++++\n transport.h          |  10 +++++\n 9 files changed, 219 insertions(+)\n\ndiff --git a/Makefile b/Makefile\nindex 1f3f099f5c..cdabdb3771 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1159,6 +1159,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n LIB_OBJS += fetch-pack.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\n LIB_OBJS += fsmonitor.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..5f98840c7c\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,121 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"odb.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\telse\n+\t\tBUG(\"only size should be in object_info_options\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+static size_t parse_object_size(const char *s, size_t *res)\n+{\n+\tuintmax_t uim;\n+\n+\tif (!s[0] || s[strspn(s, \"0123456789\")])\n+\t\treturn -1;\n+\terrno = 0;\n+\tuim = strtoumax(s, NULL, 10);\n+\tif (errno || uim > SIZE_MAX)\n+\t\treturn -1;\n+\t*res = uim;\n+\treturn 0;\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"unsupported protocol version. expected v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader,\n+\t\t\t\t\t\t  \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\tsize_index = i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep =\n+\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t} else {\n+\t\t\tBUG(\"only size is supported\");\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0;\n+\t     packet_reader_read(reader) == PACKET_READ_NORMAL &&\n+\t     i < args->oids->nr;\n+\t     i++) {\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n+\n+\t\tif (object_info_values.nr < 2)\n+\t\t\tdie(\"object-info: malformed response from the server: %s\",\n+\t\t\t    reader->line);\n+\n+\t\tif (!strcmp(object_info_values.items[1].string, \"\")) {\n+\t\t\tFREE_AND_NULL(object_info_data[i].sizep);\n+\t\t\tstring_list_clear(&object_info_values, 0);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (args->object_info_options->nr + 1 != object_info_values.nr)\n+\t\t\tdie(\"object-info: unexpected number of attributes: %s\",\n+\t\t\t    reader->line);\n+\n+\t\tif (size_index >= 0 &&\n+\t\t    parse_object_size(object_info_values.items[size_index + 1].string,\n+\t\t\t\t      object_info_data[i].sizep))\n+\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t    object_info_values.items[0].string,\n+\t\t\t    object_info_values.items[size_index + 1].string);\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..d35284bd6b\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+#include \"odb.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/fetch-pack.h b/fetch-pack.h\nindex 6d0dec7f41..0fba340a84 100644\n--- a/fetch-pack.h\n+++ b/fetch-pack.h\n@@ -16,6 +16,7 @@ struct fetch_pack_args {\n \tconst struct string_list *deepen_not;\n \tstruct list_objects_filter_options filter_options;\n \tconst struct string_list *server_options;\n+\tstruct object_info *object_info_data;\n\n \t/*\n \t * If not NULL, during packfile negotiation, fetch-pack will send \"have\"\ndiff --git a/meson.build b/meson.build\nindex ca235801cf..19fad57da9 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -347,6 +347,7 @@ libgit_sources = [\n   'exec-cmd.c',\n   'fetch-negotiator.c',\n   'fetch-pack.c',\n+  'fetch-object-info.c',\n   'fmt-merge-msg.c',\n   'fsck.c',\n   'fsmonitor.c',\ndiff --git a/transport-helper.c b/transport-helper.c\nindex f195070788..623463dcea 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -784,6 +784,15 @@ static int fetch_refs(struct transport *transport,\n \treturn -1;\n }\n\n+static int fetch_object_info_helper(struct transport *transport)\n+{\n+\tget_helper(transport);\n+\tif (process_connect(transport, 0))\n+\t\treturn transport->vtable->fetch_object_info(transport);\n+\n+\tdie(_(\"object-info requires protocol v2\"));\n+}\n+\n struct push_update_ref_state {\n \tstruct ref *hint;\n \tstruct ref_push_report *report;\n@@ -1330,6 +1339,7 @@ static struct transport_vtable vtable = {\n \t.get_refs_list\t= get_refs_list,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs,\n+\t.fetch_object_info = fetch_object_info_helper,\n \t.push_refs\t= push_refs,\n \t.connect\t= connect_helper,\n \t.disconnect\t= release_helper\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 051f3ab0dc..60db0bedcd 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -45,6 +45,14 @@ struct transport_vtable {\n \t **/\n \tint (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);\n\n+\t/*\n+\t * Fetch object info (only size currently) from remote without\n+\t * downloading the objects.\n+\t *\n+\t * Uses object-info capability of v2 protocol.\n+\t */\n+\tint (*fetch_object_info)(struct transport *transport);\n+\n \t/**\n \t * Push the objects and refs. Send the necessary objects, and\n \t * then, for any refs where peer_ref is set and\ndiff --git a/transport.c b/transport.c\nindex fc144f0aed..9342680531 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -432,6 +433,48 @@ static int get_bundle_uri(struct transport *transport)\n \t\t\t\t     transport->bundles, stateless_rpc);\n }\n\n+static int fetch_object_info_via_pack(struct transport *transport)\n+{\n+\tint ret = 0;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct packet_reader reader;\n+\tstruct object_info_args args = { 0 };\n+\n+\targs.server_options = transport->server_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\tstring_list_sort(args.object_info_options);\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tdata->version = discover_version(&reader);\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tret = fetch_object_info(data->version, &args, &reader,\n+\t\t\t\tdata->options.object_info_data,\n+\t\t\t\ttransport->stateless_rpc, data->fd[1]);\n+\n+\tclose(data->fd[0]);\n+\tif (data->fd[1] >= 0)\n+\t\tclose(data->fd[1]);\n+\tif (finish_connect(data->conn))\n+\t\tret = -1;\n+\tdata->conn = NULL;\n+\n+\treturn ret;\n+}\n+\n+int transport_fetch_object_info(struct transport *transport)\n+{\n+\tif (!transport->vtable->fetch_object_info)\n+\t\tdie(_(\"remote does not support object-info\"));\n+\treturn transport->vtable->fetch_object_info(transport);\n+}\n+\n static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t       int nr_heads, struct ref **to_fetch)\n {\n@@ -1004,6 +1047,7 @@ static struct transport_vtable taken_over_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.disconnect\t= disconnect_git\n };\n@@ -1169,6 +1213,7 @@ static struct transport_vtable builtin_smart_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.connect\t= connect_git,\n \t.disconnect\t= disconnect_git\ndiff --git a/transport.h b/transport.h\nindex 7e5867cffa..9e85a4cd35 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -6,6 +6,7 @@\n #include \"list-objects-filter-options.h\"\n #include \"string-list.h\"\n #include \"connect.h\"\n+#include \"odb.h\"\n\n struct git_transport_options {\n \tunsigned thin : 1;\n@@ -55,6 +56,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n\n enum transport_family {\n@@ -309,6 +314,11 @@ int transport_get_remote_bundle_uri(struct transport *transport);\n const struct git_hash_algo *transport_get_hash_algo(struct transport *transport);\n int transport_fetch_refs(struct transport *transport, struct ref *refs);\n\n+/*\n+ * Fetch the object info from remote\n+ */\n+int transport_fetch_object_info(struct transport *transport);\n+\n /*\n  * If this flag is set, unlocking will avoid to call non-async-signal-safe\n  * functions. This will necessarily leave behind some data structures which\n\n--\n2.54.0\n"},{"id":"548268","messageId":"20260715-ps-eric-work-rebase-v18-11-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 11/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:19Z","receivedAt":"2026-07-15T09:52:42Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSince the info command in cat-file --batch-command prints object\ninfo for a given object, it is natural to add another command in\ncat-file --batch-command to print object info for a given object\nfrom a remote.\n\nAdd remote-object-info command to cat-file --batch-command.\n\nWhile info takes object ids one at a time, this creates overhead when\nmaking requests to a server. So remote-object-info instead can take\nmultiple object ids at once.\n\nThe cat-file --batch-command command is generally implemented in the\nfollowing manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, remote-object-info is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is remote-object-info:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode remote-object-info:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is remote-object-info:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, remote-object-info gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  29 +-\n builtin/cat-file.c                     | 144 ++++++-\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n t/meson.build                          |   1 +\n t/t1017-cat-file-remote-object-info.sh | 680 +++++++++++++++++++++++++++++++++\n 6 files changed, 859 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex 86b9181599..a7fa6674c3 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -169,6 +169,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -301,7 +308,8 @@ one per line, and print information based on the command given. With\n `--batch-command`, the `info` command followed by an object will print\n information about the object the same way `--batch-check` would, and the\n `contents` command followed by an object prints contents in the same way\n-`--batch` would.\n+`--batch` would. The `remote-object-info` command followed by a remote and\n+objects IDs prints object info from the remote without downloading the objects.\n \n You can specify the information shown for each object by using a custom\n `<format>`. The `<format>` is copied literally to stdout for each\n@@ -324,15 +332,12 @@ newline. The available atoms are:\n \treports).\n \n `objectsize:disk`::\n-\tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tThe size, in bytes, that the object takes up on disk.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n-\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n-\n+\tOtherwise, expands to the null OID (all zeroes).\n `rest`::\n \tIf this atom is used in the output string, input lines are split\n \tat the first whitespace boundary. All characters before that\n@@ -340,8 +345,14 @@ newline. The available atoms are:\n \tafter that first run of whitespace (i.e., the \"rest\" of the\n \tline) are output in place of the `%(rest)` atom.\n \n+The command `remote-object-info` only supports the `%(objectname)` and\n+`%(objectsize)` placeholders. See `CAVEATS` below for more information.\n+\n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which\n+use `%(objectname) %(objectsize)` because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified,\n+so DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -438,6 +449,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that since only `%(objectname)` and `%(objectsize)` are currently\n+supported by the `remote-object-info` command. Using any other placeholder in\n+the format string will raise an error.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 26ad07b62c..77ecccdda3 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -29,6 +29,22 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/*\n+ * Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8 * 1024)\n+\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE \\\n+\t(MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -636,6 +652,80 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(struct batch_options *opt,\n+\t\t\t   int argc,\n+\t\t\t   const char **argv,\n+\t\t\t   struct object_info **remote_object_info,\n+\t\t\t   struct oid_array *object_info_oids)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstruct transport *gtransport;\n+\n+\t/*\n+\t * TODO: Change the format to \"%(objectname) %(objectsize)\" when\n+\t * remote-object-info command is used. Once we start supporting objecttype\n+\t * the default format should change to DEFAULT_FORMAT.\n+\t */\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid)) {\n+\t\t\tsize_t len = strlen(argv[i]);\n+\n+\t\t\tif (len < the_hash_algo->hexsz && len >= 4) {\n+\t\t\t\tsize_t j;\n+\t\t\t\tfor (j = 0; j < len; j++)\n+\t\t\t\t\tif (!isxdigit(argv[i][j]))\n+\t\t\t\t\t\tbreak;\n+\t\t\t\tif (j == len)\n+\t\t\t\t\tdie(_(\"remote-object-info does not support \"\n+\t\t\t\t\t      \"short oids, %d characters required\"),\n+\t\t\t\t\t    (int)the_hash_algo->hexsz);\n+\t\t\t}\n+\t\t\tdie(_(\"not a valid object name '%s'\"), argv[i]);\n+\t\t}\n+\t\toid_array_append(object_info_oids, &oid);\n+\t}\n+\n+\tif (!object_info_oids->nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\n+\tif (!gtransport->smart_options) {\n+\t\tretval = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n+\tgtransport->smart_options->object_info_oids = object_info_oids;\n+\n+\t/* 'objectsize' is the only option currently supported */\n+\tif (!strstr(opt->format, \"%(objectsize)\"))\n+\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n+\n+\tstring_list_append(&object_info_options, \"size\");\n+\n+\tif (object_info_options.nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\t\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\t\tretval = transport_fetch_object_info(gtransport);\n+\t}\n+cleanup:\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -717,6 +807,57 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\tstruct object_info *remote_object_info = NULL;\n+\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command too long\"));\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n+\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n+\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n+\t\t    MAX_ALLOWED_OBJ_LIMIT);\n+\n+\tif (get_remote_info(opt, count, argv, &remote_object_info,\n+\t\t\t    &object_info_oids))\n+\t\tgoto cleanup;\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t} else {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\n+cleanup:\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+\toid_array_clear(&object_info_oids);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -748,8 +889,9 @@ static const struct parse_cmd {\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1 },\n \t{ \"info\", parse_cmd_info, 1 },\n-\t{ \"flush\", NULL, 0 },\n \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n+\t{ \"flush\", NULL, 0 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex 6453b1d6fa..07f019a0f6 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1694,3 +1694,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)\n \n \treturn &transaction->base;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+}\ndiff --git a/odb.h b/odb.h\nindex 3834a0dcbf..42e3934035 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -573,4 +573,7 @@ void parse_alternates(const char *string,\n \t\t      const char *relative_base,\n \t\t      struct strvec *out);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* ODB_H */\ndiff --git a/t/meson.build b/t/meson.build\nindex 7c3c070426..562923c257 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -171,6 +171,7 @@ integration_tests = [\n   't1014-read-tree-confusing.sh',\n   't1015-read-index-unmerged.sh',\n   't1016-compatObjectFormat.sh',\n+  't1017-cat-file-remote-object-info.sh',\n   't1020-subdirectory.sh',\n   't1022-read-tree-partial-clone.sh',\n   't1050-large.sh',\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..49b6660934\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,680 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n+\n+unstored_content=\"Hello Git\"\n+unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all --enable=receive-pack\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\t\tGIT_TRACE_PACKET=1 git cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not support short oids' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_short_oid\n+\t\tEOF\n+\t\ttest_grep \"does not support short oids\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not die on missing oid like info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tgit cat-file --batch-command >local <<-EOF &&\n+\t\tinfo $unstored_oid\n+\t\tEOF\n+\t\tgit cat-file --batch-command >remote <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $unstored_oid\n+\t\tEOF\n+\t\ttest_cmp local remote\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n\n-- \n2.54.0\n"},{"id":"548267","messageId":"20260715-ps-eric-work-rebase-v18-12-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 12/13] cat-file: validate remote atoms with an allow-list","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:20Z","receivedAt":"2026-07-15T09:52:44Z","isPatch":true,"body":"strstr() is not enough to validate the format placeholders in\nremote-object-info causing two errors:\n\n1. Atoms recognized by expand_atom() but the remote doesn't returns 1,\n   but data->type contains garbage causing segfault.\n\n2. expand_atom() returns 0 for unknown atoms, calling\n   strbuf_expand_bad_format() which ends up dying, blocking local\n   queries if the same format is shared.\n\nAdd an allow-list with the supported atoms at the top of expand_atom().\nIn remote mode, unsupported atoms return 1 leaving the buffer empty,\nhonoring how for-each-ref handles known but inapplicable atoms.\n\nAs extra safety, initialize data->type to OBJ_BAD and add a NULL check\nfor type_name() so uninitialized data doesn't cause segfault.\n\nUpdate tests that expect previous die() behavior to expect an empty\nstring and add an explicit test for empty string return on unknown\nplaceholder.\n\nUpdate cat-file command documentation regarding remote-object-info.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  2 +-\n builtin/cat-file.c                     | 41 +++++++++++++++++++++++++++-------\n t/t1017-cat-file-remote-object-info.sh | 27 ++++++++++++++++++----\n 3 files changed, 57 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex a7fa6674c3..643eac9245 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -451,7 +451,7 @@ CAVEATS\n \n Note that since only `%(objectname)` and `%(objectsize)` are currently\n supported by the `remote-object-info` command. Using any other placeholder in\n-the format string will raise an error.\n+the format string will return an empty string in its position.\n \n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 77ecccdda3..af388b6238 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -333,8 +333,18 @@ struct expand_data {\n \t * optimized out.\n \t */\n \tunsigned skip_object_info : 1;\n+\n+\t/*\n+\t * Flags about when an object info is being fetched from remote.\n+\t */\n+\tunsigned is_remote:1;\n+};\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n+\n+static const char *remote_object_info_atoms[] = {\n+\t\"objectname\",\n+\t\"objectsize\",\n };\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -345,14 +355,31 @@ static int is_atom(const char *atom, const char *s, int slen)\n static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n+\tif (data->is_remote) {\n+\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n+\t\tfor (i = 0; i < allowed_nr; i++)\n+\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * On remote, skip unsupported atoms returning an empty sb,\n+\t\t * honoring how for-each-ref handles known but inapplicable\n+\t\t * atoms (e.g. %(tagger)).\n+\t\t */\n+\t\tif (i == allowed_nr)\n+\t\t\treturn 1;\n+\t}\n+\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n \t\t\tstrbuf_add_oid_hex(sb, &data->oid);\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n-\t\tif (data->mark_query)\n+\t\tif (data->mark_query) {\n \t\t\tdata->info.typep = &data->type;\n-\t\telse\n-\t\t\tstrbuf_addstr(sb, type_name(data->type));\n+\t\t} else {\n+\t\t\tconst char *t = type_name(data->type);\n+\t\t\tstrbuf_addstr(sb, t ? t : \"\");\n+\t\t}\n \t} else if (is_atom(\"objectsize\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.sizep = &data->size;\n@@ -709,10 +736,6 @@ static int get_remote_info(struct batch_options *opt,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\t/* 'objectsize' is the only option currently supported */\n-\tif (!strstr(opt->format, \"%(objectsize)\"))\n-\t\tdie(_(\"%s is currently not supported with remote-object-info\"), opt->format);\n-\n \tstring_list_append(&object_info_options, \"size\");\n \n \tif (object_info_options.nr > 0) {\n@@ -842,7 +865,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n \t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tdata->is_remote = 1;\n \t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t\tdata->is_remote = 0;\n \t\t} else {\n \t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n \t\t}\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 49b6660934..6bc863c391 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -236,6 +236,21 @@ test_expect_success 'remote-object-info does not die on missing oid like info' '\n \t)\n '\n \n+# This tests depends on %(objecttype) not being supported yet, once supported\n+# it needs to be updated.\n+test_expect_success 'unsupported placeholder on remote returns empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"\" >expect &&\n+\t\tgit cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n # Test --batch-command remote-object-info with 'git://' and\n # transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n@@ -575,10 +590,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (obje\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(objectsize:disk)\" 2>err <<-EOF &&\n+\t\techo \"$hello_oid \" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize:disk)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(objectsize:disk) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n@@ -587,10 +604,12 @@ test_expect_success 'remote-object-info fails on unsupported filter option (delt\n \t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n \n-\t\ttest_must_fail git cat-file --batch-command=\"%(deltabase)\" 2>err <<-EOF &&\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(deltabase)\" >actual <<-EOF &&\n \t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n \t\tEOF\n-\t\ttest_grep \"%(deltabase) is currently not supported with remote-object-info\" err\n+\t\ttest_cmp expect actual\n \t)\n '\n \n\n-- \n2.54.0\n"},{"id":"548269","messageId":"20260715-ps-eric-work-rebase-v18-13-34d7adb051bb@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v18 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T09:52:21Z","receivedAt":"2026-07-15T09:52:45Z","isPatch":true,"body":"The static allow-list in expand_atom() is hardcoded to only allow\n\"objectname\" and \"objectsize\" for remote queries. This works because\nup to this point all servers will either support object-info with name\nand size or they do not support them at all, but we cannot expect that\nin a future different servers with different git versions to have the\nsame object-info capabilities. Therefore, the allow_list needs to be\ndynamic depending on what the server advertises.\n\nThe client will now:\n\n1. Request the protocol option that the placeholder refers to (i.e.\n   \"size\" when \"%(objectsize)\").\n\n2. Filters the request in fetch_object_info() dropping any option that\n   the server does not advertise.\n\n3. After the fetching, the options that haven't been dropped are the ones\n   fetched and supported by the server, these supported options are\n   mapped and remote_allowed_atoms is populated with the placeholders.\n\n4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n   the static allow_list had.\n\nMove object_info_options out of get_remote_info so the caller which has\ndata can select what options will be requested instead of requesting\nalways size.\nMove batch_object_write() out so there will always be an output even if\nall the placeholders are not supported by the server (returns an empty\nline).\n\nInclude \"type\" in the object_info_options so once the server supports\nit, the clients know already how to request it.\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c  | 97 +++++++++++++++++++++++++++++++++++------------------\n fetch-object-info.c | 20 +++++++++++\n 2 files changed, 84 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex af388b6238..20d9705a7f 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -338,13 +338,11 @@ struct expand_data {\n \t * Flags about when an object info is being fetched from remote.\n \t */\n \tunsigned is_remote:1;\n-};\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n \n-static const char *remote_object_info_atoms[] = {\n-\t\"objectname\",\n-\t\"objectsize\",\n+\tstruct string_list remote_allowed_atoms;\n };\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -356,17 +354,11 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n \tif (data->is_remote) {\n-\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n-\t\tfor (i = 0; i < allowed_nr; i++)\n-\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\tsize_t i;\n+\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n \t\t\t\tbreak;\n-\n-\t\t/*\n-\t\t * On remote, skip unsupported atoms returning an empty sb,\n-\t\t * honoring how for-each-ref handles known but inapplicable\n-\t\t * atoms (e.g. %(tagger)).\n-\t\t */\n-\t\tif (i == allowed_nr)\n+\t\tif (i == data->remote_allowed_atoms.nr)\n \t\t\treturn 1;\n \t}\n \n@@ -683,12 +675,12 @@ static int get_remote_info(struct batch_options *opt,\n \t\t\t   int argc,\n \t\t\t   const char **argv,\n \t\t\t   struct object_info **remote_object_info,\n-\t\t\t   struct oid_array *object_info_oids)\n+\t\t\t   struct oid_array *object_info_oids,\n+\t\t\t   struct string_list *object_info_options)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n \tstruct object_id oid;\n-\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tstruct transport *gtransport;\n \n \t/*\n@@ -736,15 +728,12 @@ static int get_remote_info(struct batch_options *opt,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\tstring_list_append(&object_info_options, \"size\");\n-\n-\tif (object_info_options.nr > 0) {\n-\t\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tif (object_info_options->nr > 0) {\n+\t\tgtransport->smart_options->object_info_options = object_info_options;\n \t\tgtransport->smart_options->object_info_data = *remote_object_info;\n \t\tretval = transport_fetch_object_info(gtransport);\n \t}\n cleanup:\n-\tstring_list_clear(&object_info_options, 0);\n \ttransport_disconnect(gtransport);\n \treturn retval;\n }\n@@ -830,6 +819,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+struct protocol_placeholder_entry {\n+\tconst char *option;\n+\tconst char *atom;\n+};\n+\n+static const struct protocol_placeholder_entry remote_atom_map[] = {\n+\t{\"size\", \"objectsize\"},\n+\t{\"type\", \"objecttype\"},\n+\t/*\n+\t * Add new protocol options here. Even if the server doesn't support\n+\t * them the allow_list will drop them if the server doesn't advertise\n+\t * them.\n+\t */\n+};\n+\n static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n@@ -839,6 +843,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct object_info *remote_object_info = NULL;\n \tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \n \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n \t\tdie(_(\"remote-object-info command too long\"));\n@@ -851,32 +856,57 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n+\tif (data->info.sizep)\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\tif (data->info.typep)\n+\t\tstring_list_append(&object_info_options, \"type\");\n+\n \tif (get_remote_info(opt, count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids))\n+\t\t\t    &object_info_oids, &object_info_options))\n \t\tgoto cleanup;\n \n+\tstring_list_clear(&data->remote_allowed_atoms, 0);\n+\tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n+\tfor (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n+\t\tif (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n+\t\t\tstring_list_append(&data->remote_allowed_atoms,\n+\t\t\t\t\t   remote_atom_map[i].atom);\n+\n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tint found = 0;\n \t\tdata->oid = object_info_oids.oid[i];\n+\t\t/*\n+\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t * information from server without downloading them.\n+\t\t */\n \t\tif (remote_object_info[i].sizep) {\n-\t\t\t/*\n-\t\t\t * When reaching here, it means remote-object-info can retrieve\n-\t\t\t * information from server without downloading them.\n-\t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n-\t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tdata->is_remote = 1;\n-\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n-\t\t\tdata->is_remote = 0;\n-\t\t} else {\n-\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t\tfound = 1;\n \t\t}\n+\n+\t\tif (remote_object_info[i].typep) {\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\t\t\tfound = 1;\n+\t\t}\n+\n+\t\tif (!found && object_info_options.nr > 0) {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid),\n+\t\t\t\t\t     &data->oid, \"missing\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tdata->is_remote = 1;\n+\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n \n cleanup:\n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n+\tstring_list_clear(&object_info_options, 0);\n \tfree(line_to_split);\n \tfree(argv);\n \tfree(remote_object_info);\n@@ -1192,6 +1222,7 @@ static int batch_objects(struct batch_options *opt)\n  cleanup:\n \tstrbuf_release(&input);\n \tstrbuf_release(&output);\n+\tstring_list_clear(&data.remote_allowed_atoms, 0);\n \tcfg->warn_on_object_refname_ambiguity = save_warning;\n \treturn retval;\n }\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 5f98840c7c..160c29507a 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -55,6 +55,26 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \tcase protocol_v2:\n \t\tif (!server_supports_v2(\"object-info\"))\n \t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\t/*\n+\t\t * When removing an element from the list it gets swapped by the\n+\t\t * last element, iterate backwards to prevent elements skipping\n+\t\t * evaluation.\n+\t\t *\n+\t\t * object_info_options->nr can be safely casted without overflow\n+\t\t * beacuse the number of options is a small known number (the\n+\t\t * supported placeholders which currently are size and type).\n+\t\t */\n+\t\tfor (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)\n+\t\t\tif (!server_supports_feature(\"object-info\",\n+\t\t\t\t\t\t     args->object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(args->object_info_options, i, 0);\n+\t\t/*\n+\t\t * If no options are left after the filtering, avoid unnecessary\n+\t\t * request to the server.\n+\t\t */\n+\t\tif (!args->object_info_options->nr)\n+\t\t\treturn 0;\n+\n \t\tsend_object_info_request(fd_out, args);\n \t\tbreak;\n \tcase protocol_v1:\n\n-- \n2.54.0\n"},{"id":"548299","messageId":"xmqqfr1kp98u.fsf@gitster.g","threadId":"61695","inReplyTo":"DJZ0JXIP13UO.DH1ONBDEVF3N@gmail.com","subject":"Re: [PATCH GSoC v17 00/13] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-15T15:23:29Z","receivedAt":"2026-07-15T15:23:32Z","isPatch":true,"body":"\"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n\n>> Thanks.  How close are we to the finish line, by the way?\n>\n> There's one month left. Final evaluation ends on 17th August (more weeks\n> can be asked, if it seems too rushed, ...\n\nThat is the deadline to wrap up your work, which is not quite what\nI was asking.  I meant to ask how close you assess this topic is\nto completion at iteration #18.  Are all remaining issues just\nminor nits?  Are there still large gaps between the desired and\nactual behavior of the new feature?  That sort of thing.\n\nThanks.\n"},{"id":"548301","messageId":"DJZ97T45NIBC.11YZMEJI0NNEG@gmail.com","threadId":"61695","inReplyTo":"xmqqfr1kp98u.fsf@gitster.g","subject":"Re: [PATCH GSoC v17 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T15:36:00Z","receivedAt":"2026-07-15T15:36:04Z","isPatch":true,"body":"On Wed Jul 15, 2026 at 5:23 PM CEST, Junio C Hamano wrote:\n> \"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n>\n>>> Thanks.  How close are we to the finish line, by the way?\n>>\n>> There's one month left. Final evaluation ends on 17th August (more weeks\n>> can be asked, if it seems too rushed, ...\n>\n> That is the deadline to wrap up your work, which is not quite what\n> I was asking.  I meant to ask how close you assess this topic is\n> to completion at iteration #18.  Are all remaining issues just\n> minor nits?  Are there still large gaps between the desired and\n> actual behavior of the new feature?  That sort of thing.\n\nOh, sorry.\n\nIt already does what it's supposed to do, the last rerolls have been\nmostly cleanups.\n\nIf there's anything more to do it should only be minor nits.\n\n>\n> Thanks.\n\nRegards,\nPablo\n"},{"id":"548308","messageId":"xmqqpl0onp5d.fsf@gitster.g","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-10-34d7adb051bb@gmail.com","subject":"Re: [PATCH GSoC v18 10/13] transport: add client support for object-info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-15T17:22:54Z","receivedAt":"2026-07-15T17:22:58Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> +static size_t parse_object_size(const char *s, size_t *res)\n> +{\n> +\tuintmax_t uim;\n> +\n> +\tif (!s[0] || s[strspn(s, \"0123456789\")])\n> +\t\treturn -1;\n> +\terrno = 0;\n> +\tuim = strtoumax(s, NULL, 10);\n> +\tif (errno || uim > SIZE_MAX)\n> +\t\treturn -1;\n> +\t*res = uim;\n> +\treturn 0;\n> +}\n\nSince size_t is unsigned, returning -1 is a bit problematic,\nisn't it?  Perhaps this should return a plain 'int' instead.\n\nThe sole caller only cares about a boolean \"did we succeed or\nfail?\" result, and more importantly, the actual size parsed\nis already returned via the out-parameter.\n\nThanks.\n"},{"id":"548309","messageId":"xmqqjyqwnoqf.fsf@gitster.g","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-11-34d7adb051bb@gmail.com","subject":"Re: [PATCH GSoC v18 11/13] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-15T17:31:52Z","receivedAt":"2026-07-15T17:31:55Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> +\t\t\t\t\t const char *line, struct strbuf *output,\n> +\t\t\t\t\t struct expand_data *data)\n> +{\n> +\tint count;\n> +\tconst char **argv;\n> +\tchar *line_to_split;\n> +\tstruct object_info *remote_object_info = NULL;\n> +\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n> +\n> +\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n> +\t\tdie(_(\"remote-object-info command too long\"));\n> +\n> +\tline_to_split = xstrdup(line);\n> +\tcount = split_cmdline(line_to_split, &argv);\n> +\tif (count < 0)\n> +\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n> +\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n> +\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n> +\t\t    MAX_ALLOWED_OBJ_LIMIT);\n> +\n> +\tif (get_remote_info(opt, count, argv, &remote_object_info,\n> +\t\t\t    &object_info_oids))\n> +\t\tgoto cleanup;\n\nSince this function does not return a value, the caller cannot\neven tell if there was an error if we just silently return like\nthis.  Is it really OK to silently ignore such a failure?  Should\nwe not die() loudly to report it instead?\n\nThanks.\n"},{"id":"548310","messageId":"xmqqcxwonnkx.fsf@gitster.g","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-13-34d7adb051bb@gmail.com","subject":"Re: [PATCH GSoC v18 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-15T17:56:46Z","receivedAt":"2026-07-15T17:56:49Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> The static allow-list in expand_atom() is hardcoded to only allow\n> \"objectname\" and \"objectsize\" for remote queries. This works because\n> up to this point all servers will either support object-info with name\n> and size or they do not support them at all, but we cannot expect that\n> in a future different servers with different git versions to have the\n> same object-info capabilities. Therefore, the allow_list needs to be\n> dynamic depending on what the server advertises.\n>\n> The client will now:\n>\n> 1. Request the protocol option that the placeholder refers to (i.e.\n>    \"size\" when \"%(objectsize)\").\n\n\"when\" -> \"for\"?\n\n>\n> 2. Filters the request in fetch_object_info() dropping any option that\n>    the server does not advertise.\n>\n> 3. After the fetching, the options that haven't been dropped are the ones\n>    fetched and supported by the server, these supported options are\n>    mapped and remote_allowed_atoms is populated with the placeholders.\n>\n> 4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n>    the static allow_list had.\n\nI am not sure I follow the above entirely.  Could you add a\nconcrete example to the commit message?\n\nFor instance, if the client wants \"%(objectsize) %(objectcolor)\" and\nthe server only supports 'size' but not 'color', the filtering in\nstep (2) prevents the client from asking about the color, requesting\nonly the size instead.  When the server says the size is 42, step (3)\nuses that to substitute '%(objectsize)'.  Would the end result then\nbe \"42 %(objectcolor)\"?\n\n> -static const char *remote_object_info_atoms[] = {\n> -\t\"objectname\",\n> -\t\"objectsize\",\n> +\tstruct string_list remote_allowed_atoms;\n>  };\n> +#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n> +\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n\nHmph, is this list expected to change over time?  One-line-per-item\nformat would be more suited for updates if it is the case.\n\n> @@ -683,12 +675,12 @@ static int get_remote_info(struct batch_options *opt,\n>  \t\t\t   int argc,\n>  \t\t\t   const char **argv,\n>  \t\t\t   struct object_info **remote_object_info,\n> -\t\t\t   struct oid_array *object_info_oids)\n> +\t\t\t   struct oid_array *object_info_oids,\n> +\t\t\t   struct string_list *object_info_options)\n>  {\n>  \tint retval = 0;\n>  \tstruct remote *remote = NULL;\n>  \tstruct object_id oid;\n> -\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n>  \tstruct transport *gtransport;\n>  \n>  \t/*\n> @@ -736,15 +728,12 @@ static int get_remote_info(struct batch_options *opt,\n>  \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n>  \tgtransport->smart_options->object_info_oids = object_info_oids;\n>  \n> -\tstring_list_append(&object_info_options, \"size\");\n> -\n> -\tif (object_info_options.nr > 0) {\n> -\t\tgtransport->smart_options->object_info_options = &object_info_options;\n> +\tif (object_info_options->nr > 0) {\n> +\t\tgtransport->smart_options->object_info_options = object_info_options;\n>  \t\tgtransport->smart_options->object_info_data = *remote_object_info;\n>  \t\tretval = transport_fetch_object_info(gtransport);\n>  \t}\n\nThis is not a new issue, but if the caller does not ask for\nanything in object_info_options, no call to\ntransport_fetch_object_info() is made here.  This is so even\nthough we went through quite a lot of work, including the\nconnection establishment and teardown below.\n\nBy failing to contact the remote side, we wouldn't even know if\nthe objects being queried actually exist there, which is\nprobably even worse.\n\n>  static void parse_cmd_remote_object_info(struct batch_options *opt,\n>  \t\t\t\t\t const char *line, struct strbuf *output,\n>  \t\t\t\t\t struct expand_data *data)\n> @@ -839,6 +843,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>  \tchar *line_to_split;\n>  \tstruct object_info *remote_object_info = NULL;\n>  \tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n> +\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n>  \n>  \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n>  \t\tdie(_(\"remote-object-info command too long\"));\n> @@ -851,32 +856,57 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>  \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n>  \t\t    MAX_ALLOWED_OBJ_LIMIT);\n>  \n> +\tif (data->info.sizep)\n> +\t\tstring_list_append(&object_info_options, \"size\");\n> +\tif (data->info.typep)\n> +\t\tstring_list_append(&object_info_options, \"type\");\n\nAnd if the request is only for \"%(objectname)\", an empty\nobject_info_options is given to get_remote_info().\n\n>  \tif (get_remote_info(opt, count, argv, &remote_object_info,\n> -\t\t\t    &object_info_oids))\n> +\t\t\t    &object_info_oids, &object_info_options))\n>  \t\tgoto cleanup;\n\n"},{"id":"548311","messageId":"DJZCF0EAPPM0.3KTS5DZRJKUR3@gmail.com","threadId":"61695","inReplyTo":"xmqqjyqwnoqf.fsf@gitster.g","subject":"Re: [PATCH GSoC v18 11/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T18:06:28Z","receivedAt":"2026-07-15T18:06:36Z","isPatch":true,"body":">\n>> +static void parse_cmd_remote_object_info(struct batch_options *opt,\n>> +\t\t\t\t\t const char *line, struct strbuf *output,\n>> +\t\t\t\t\t struct expand_data *data)\n>> +{\n>> +\tint count;\n>> +\tconst char **argv;\n>> +\tchar *line_to_split;\n>> +\tstruct object_info *remote_object_info = NULL;\n>> +\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n>> +\n>> +\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n>> +\t\tdie(_(\"remote-object-info command too long\"));\n>> +\n>> +\tline_to_split = xstrdup(line);\n>> +\tcount = split_cmdline(line_to_split, &argv);\n>> +\tif (count < 0)\n>> +\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n>> +\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n>> +\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n>> +\t\t    MAX_ALLOWED_OBJ_LIMIT);\n>> +\n>> +\tif (get_remote_info(opt, count, argv, &remote_object_info,\n>> +\t\t\t    &object_info_oids))\n>> +\t\tgoto cleanup;\n>\n> Since this function does not return a value, the caller cannot\n> even tell if there was an error if we just silently return like\n> this.  Is it really OK to silently ignore such a failure?  Should\n> we not die() loudly to report it instead?\n\nTrue, this comes from the v11 before I got into, I think Eric tried to do\nsomething like what 'info' does (it doesn't die) prints \"<oid> missing\"\nbut this comes from failing fetching.\n\n'remote-object-info' prints \"<oid> missing\" when the fetching works but\nthe oid is unrecognized.\n\nI will add a die instead of the goto. Thanks.\n\n>\n> Thanks.\n\nRegards,\nPablo\n\n"},{"id":"548312","messageId":"DJZCGKYT43SW.BJZSQ8TO1GF@gmail.com","threadId":"61695","inReplyTo":"xmqqpl0onp5d.fsf@gitster.g","subject":"Re: [PATCH GSoC v18 10/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T18:08:31Z","receivedAt":"2026-07-15T18:08:36Z","isPatch":true,"body":"On Wed Jul 15, 2026 at 7:22 PM CEST, Junio C Hamano wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> +static size_t parse_object_size(const char *s, size_t *res)\n>> +{\n>> +\tuintmax_t uim;\n>> +\n>> +\tif (!s[0] || s[strspn(s, \"0123456789\")])\n>> +\t\treturn -1;\n>> +\terrno = 0;\n>> +\tuim = strtoumax(s, NULL, 10);\n>> +\tif (errno || uim > SIZE_MAX)\n>> +\t\treturn -1;\n>> +\t*res = uim;\n>> +\treturn 0;\n>> +}\n>\n> Since size_t is unsigned, returning -1 is a bit problematic,\n> isn't it?  Perhaps this should return a plain 'int' instead.\n>\n> The sole caller only cares about a boolean \"did we succeed or\n> fail?\" result, and more importantly, the actual size parsed\n> is already returned via the out-parameter.\n>\n> Thanks.\n\nCompletly true, when I changed this from beign strtoumax_szt() I must have\nbeen thinking too much about size_t.\n\nI will change it to return int. Thanks.\n\nRegards,\nPablo\n"},{"id":"548317","messageId":"DJZDEE0G6ZRS.2RT8JTQQ6CUXB@gmail.com","threadId":"61695","inReplyTo":"xmqqcxwonnkx.fsf@gitster.g","subject":"Re: [PATCH GSoC v18 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T18:52:40Z","receivedAt":"2026-07-15T18:52:45Z","isPatch":true,"body":"On Wed Jul 15, 2026 at 7:56 PM CEST, Junio C Hamano wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> The static allow-list in expand_atom() is hardcoded to only allow\n>> \"objectname\" and \"objectsize\" for remote queries. This works because\n>> up to this point all servers will either support object-info with name\n>> and size or they do not support them at all, but we cannot expect that\n>> in a future different servers with different git versions to have the\n>> same object-info capabilities. Therefore, the allow_list needs to be\n>> dynamic depending on what the server advertises.\n>>\n>> The client will now:\n>>\n>> 1. Request the protocol option that the placeholder refers to (i.e.\n>>    \"size\" when \"%(objectsize)\").\n>\n> \"when\" -> \"for\"?\n\nWill change.\n\n>\n>>\n>> 2. Filters the request in fetch_object_info() dropping any option that\n>>    the server does not advertise.\n>>\n>> 3. After the fetching, the options that haven't been dropped are the ones\n>>    fetched and supported by the server, these supported options are\n>>    mapped and remote_allowed_atoms is populated with the placeholders.\n>>\n>> 4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n>>    the static allow_list had.\n>\n> I am not sure I follow the above entirely.  Could you add a\n> concrete example to the commit message?\n>\n> For instance, if the client wants \"%(objectsize) %(objectcolor)\" and\n> the server only supports 'size' but not 'color', the filtering in\n> step (2) prevents the client from asking about the color, requesting\n> only the size instead.  When the server says the size is 42, step (3)\n> uses that to substitute '%(objectsize)'.  Would the end result then\n> be \"42 %(objectcolor)\"?\n\nYou've gotten everything right until the last step, because we have only\nsize from the server there is no data to match %(objectcolor) and the\nend result is an empty string for %(objeccolor):\n\n\"42 \"\n\nNote that %(objectcolor) doesn't exists and it would have die(), the\nempty string is only for known but unsupported placeholders.\n\nThis is what for-each-ref does for known but unaplicable placeholders (atoms).\n\nI'll add an example to the commmit log so it is clearer.\n\n>\n>> -static const char *remote_object_info_atoms[] = {\n>> -\t\"objectname\",\n>> -\t\"objectsize\",\n>> +\tstruct string_list remote_allowed_atoms;\n>>  };\n>> +#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n>> +\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n>\n> Hmph, is this list expected to change over time?  One-line-per-item\n> format would be more suited for updates if it is the case.\n\nWill format like so.\n\n>\n>> @@ -683,12 +675,12 @@ static int get_remote_info(struct batch_options *opt,\n>>  \t\t\t   int argc,\n>>  \t\t\t   const char **argv,\n>>  \t\t\t   struct object_info **remote_object_info,\n>> -\t\t\t   struct oid_array *object_info_oids)\n>> +\t\t\t   struct oid_array *object_info_oids,\n>> +\t\t\t   struct string_list *object_info_options)\n>>  {\n>>  \tint retval = 0;\n>>  \tstruct remote *remote = NULL;\n>>  \tstruct object_id oid;\n>> -\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n>>  \tstruct transport *gtransport;\n>>\n>>  \t/*\n>> @@ -736,15 +728,12 @@ static int get_remote_info(struct batch_options *opt,\n>>  \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n>>  \tgtransport->smart_options->object_info_oids = object_info_oids;\n>>\n>> -\tstring_list_append(&object_info_options, \"size\");\n>> -\n>> -\tif (object_info_options.nr > 0) {\n>> -\t\tgtransport->smart_options->object_info_options = &object_info_options;\n>> +\tif (object_info_options->nr > 0) {\n>> +\t\tgtransport->smart_options->object_info_options = object_info_options;\n>>  \t\tgtransport->smart_options->object_info_data = *remote_object_info;\n>>  \t\tretval = transport_fetch_object_info(gtransport);\n>>  \t}\n>\n> This is not a new issue, but if the caller does not ask for\n> anything in object_info_options, no call to\n> transport_fetch_object_info() is made here.  This is so even\n> though we went through quite a lot of work, including the\n> connection establishment and teardown below.\n>\n> By failing to contact the remote side, we wouldn't even know if\n> the objects being queried actually exist there, which is\n> probably even worse.\n\n[Answered below]\n\n>\n>>  static void parse_cmd_remote_object_info(struct batch_options *opt,\n>>  \t\t\t\t\t const char *line, struct strbuf *output,\n>>  \t\t\t\t\t struct expand_data *data)\n>> @@ -839,6 +843,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>>  \tchar *line_to_split;\n>>  \tstruct object_info *remote_object_info = NULL;\n>>  \tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n>> +\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n>>\n>>  \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n>>  \t\tdie(_(\"remote-object-info command too long\"));\n>> @@ -851,32 +856,57 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>>  \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n>>  \t\t    MAX_ALLOWED_OBJ_LIMIT);\n>>\n>> +\tif (data->info.sizep)\n>> +\t\tstring_list_append(&object_info_options, \"size\");\n>> +\tif (data->info.typep)\n>> +\t\tstring_list_append(&object_info_options, \"type\");\n>\n> And if the request is only for \"%(objectname)\", an empty\n> object_info_options is given to get_remote_info().\n\nRight now 'name' is not part of the protocol as 'type' or 'size' are,\n'objectname' is always allowed but only shown if it's present on the\nformat.\nIf the format is only \"%(objectname)\" then there's nothing to ask the\nserver for.\n\nThe current code avoids making the request if there's only objectname or\nnothing supported, but still goes through the connection work. I will\nadd an early return to just output the oid back without any connection.\n\nReturning the oid is what the client expects for which we don't need a\nconnection, but this means that we skip the existence check that\nwe would get if we asked for size.\n\nThe server is not ready to support a bare oid for existence check, it\ncould be done but that work belongs to a future series.\n\n>\n>>  \tif (get_remote_info(opt, count, argv, &remote_object_info,\n>> -\t\t\t    &object_info_oids))\n>> +\t\t\t    &object_info_oids, &object_info_options))\n>>  \t\tgoto cleanup;\n\nRegards,\nPablo\n"},{"id":"548323","messageId":"xmqqwluwj8of.fsf@gitster.g","threadId":"61695","inReplyTo":"DJZDEE0G6ZRS.2RT8JTQQ6CUXB@gmail.com","subject":"Re: [PATCH GSoC v18 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-15T20:32:16Z","receivedAt":"2026-07-15T20:32:19Z","isPatch":true,"body":"\"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n\n>>> 2. Filters the request in fetch_object_info() dropping any option that\n>>>    the server does not advertise.\n>>>\n>>> 3. After the fetching, the options that haven't been dropped are the ones\n>>>    fetched and supported by the server, these supported options are\n>>>    mapped and remote_allowed_atoms is populated with the placeholders.\n>>>\n>>> 4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n>>>    the static allow_list had.\n>>\n>> I am not sure I follow the above entirely.  Could you add a\n>> concrete example to the commit message?\n>>\n>> For instance, if the client wants \"%(objectsize) %(objectcolor)\" and\n>> the server only supports 'size' but not 'color', the filtering in\n>> step (2) prevents the client from asking about the color, requesting\n>> only the size instead.  When the server says the size is 42, step (3)\n>> uses that to substitute '%(objectsize)'.  Would the end result then\n>> be \"42 %(objectcolor)\"?\n>\n> You've gotten everything right until the last step, because we have only\n> size from the server there is no data to match %(objectcolor) and the\n> end result is an empty string for %(objeccolor):\n>\n> \"42 \"\n>\n> Note that %(objectcolor) doesn't exists and it would have die(), the\n> empty string is only for known but unsupported placeholders.\n\nIt was not clear there is a distinction between \"unknown\" and\n\"known but unsupported\".  The proposed log message needs to be\nclarified to make this distinction obvious.\n\n>> And if the request is only for \"%(objectname)\", an empty\n>> object_info_options is given to get_remote_info().\n>\n> Right now 'name' is not part of the protocol as 'type' or 'size' are,\n> 'objectname' is always allowed but only shown if it's present on the\n> format.\n> If the format is only \"%(objectname)\" then there's nothing to ask the\n> server for.\n>\n> The current code avoids making the request if there's only objectname or\n> nothing supported, but still goes through the connection work. I will\n> add an early return to just output the oid back without any connection.\n\nI think you are heading in the opposite direction.  Rather, when\nonly the object name is requested, I was hoping we would pick\nsomething cheap to retrieve and ask the remote side for it, if\nonly to catch a bogus or missing object name.\n\nThanks.\n"},{"id":"548336","messageId":"DJZH1PLDC08G.1XTK39BO8YOVS@gmail.com","threadId":"61695","inReplyTo":"xmqqwluwj8of.fsf@gitster.g","subject":"Re: [PATCH GSoC v18 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-15T21:44:11Z","receivedAt":"2026-07-15T21:44:15Z","isPatch":true,"body":"On Wed Jul 15, 2026 at 10:32 PM CEST, Junio C Hamano wrote:\n> \"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n>\n>>>> 2. Filters the request in fetch_object_info() dropping any option that\n>>>>    the server does not advertise.\n>>>>\n>>>> 3. After the fetching, the options that haven't been dropped are the ones\n>>>>    fetched and supported by the server, these supported options are\n>>>>    mapped and remote_allowed_atoms is populated with the placeholders.\n>>>>\n>>>> 4. expand_atom() checks remote_allowed_atoms with the same behaviour as\n>>>>    the static allow_list had.\n>>>\n>>> I am not sure I follow the above entirely.  Could you add a\n>>> concrete example to the commit message?\n>>>\n>>> For instance, if the client wants \"%(objectsize) %(objectcolor)\" and\n>>> the server only supports 'size' but not 'color', the filtering in\n>>> step (2) prevents the client from asking about the color, requesting\n>>> only the size instead.  When the server says the size is 42, step (3)\n>>> uses that to substitute '%(objectsize)'.  Would the end result then\n>>> be \"42 %(objectcolor)\"?\n>>\n>> You've gotten everything right until the last step, because we have only\n>> size from the server there is no data to match %(objectcolor) and the\n>> end result is an empty string for %(objeccolor):\n>>\n>> \"42 \"\n>>\n>> Note that %(objectcolor) doesn't exists and it would have die(), the\n>> empty string is only for known but unsupported placeholders.\n>\n> It was not clear there is a distinction between \"unknown\" and\n> \"known but unsupported\".  The proposed log message needs to be\n> clarified to make this distinction obvious.\n\nSorry, I'll make sure to write it clear next reroll.\n\n>\n>>> And if the request is only for \"%(objectname)\", an empty\n>>> object_info_options is given to get_remote_info().\n>>\n>> Right now 'name' is not part of the protocol as 'type' or 'size' are,\n>> 'objectname' is always allowed but only shown if it's present on the\n>> format.\n>> If the format is only \"%(objectname)\" then there's nothing to ask the\n>> server for.\n>>\n>> The current code avoids making the request if there's only objectname or\n>> nothing supported, but still goes through the connection work. I will\n>> add an early return to just output the oid back without any connection.\n>\n> I think you are heading in the opposite direction.  Rather, when\n> only the object name is requested, I was hoping we would pick\n> something cheap to retrieve and ask the remote side for it, if\n> only to catch a bogus or missing object name.\n\nLooking at send_info() in 'protocol-caps.c', the server only calls\nodb_read_object_info() inside the `if (info->size)`. A bare OID without\nany requested attribute gets echoed back with no existence check.\n\nWe can force \"size\" when only %(objectname) is requested so the\nserver validates the OID, and discard the size on the client side.\n\nBecause this is a cheap fix, I'll add a NEEDSWORK for the existence\ncheck to be done regardless of the attributes requested.\n\nThis can be done on a future series focused on the server side.\n\n>\n> Thanks.\n\nRegards,\nPablo\n\n"},{"id":"548337","messageId":"CAOLa=ZSy1Z-R38cqFiz-Ejj9CNJkp4x_6rFk_wdfhyBytYH9fw@mail.gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-5-34d7adb051bb@gmail.com","subject":"Re: [PATCH GSoC v18 05/13] fetch-pack: drop static advertise_sid variable","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-07-15T21:48:42Z","receivedAt":"2026-07-15T21:48:44Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> write_fetch_command_and_capabilities() is moved to 'connect.c' in a\n> subsequent commit. To prepare for that, drop the static variable usage\n> of advertise_sid. Currently advertise_sid is used in two places:\n>\n> 1. In function do_fetch_pack():\n>         if (!server_supports(\"session-id\"))\n>                advertise_sid = 0;\n>\n> 2. In function fetch_pack_config():\n>         repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n>\n\nNit: But #2 isn't a usecase, it's where it is set no? Looking at the\nusecase, it seems like we have two:\n\n  #1 like you stated\n  #2 within `write_fetch_command_and_capabilities()`. But the flow is\n    that, the variable is set in `fetch_pack_config()`, right?\n\n> About 1, it is only relevant for v0/v1 protocol, move it into\n> find_common().\n>\n> About 2, call repo_config_get_bool() inside of\n> write_fetch_command_and_capabilities() and find_common() replacing the\n> static variable.\n>\n> Because repo_config_get_bool() leaves advertise_sid as is if it is not\n> set, initialize it to 0 matching its default.\n>\n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  fetch-pack.c | 13 +++++++------\n>  1 file changed, 7 insertions(+), 6 deletions(-)\n>\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index eea72b2500..8e04db8640 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -49,7 +49,6 @@ static int fetch_fsck_objects = -1;\n>  static int transfer_fsck_objects = -1;\n>  static int agent_supported;\n>  static int server_supports_filtering;\n> -static int advertise_sid;\n>  static struct shallow_lock shallow_lock;\n>  static const char *alternate_shallow_file;\n>  static struct strbuf fsck_msg_types = STRBUF_INIT;\n> @@ -363,6 +362,9 @@ static int find_common(struct fetch_negotiator *negotiator,\n>  \tsize_t state_len = 0;\n>  \tstruct packet_reader reader;\n>  \tstruct oidset negotiation_include_oids = OIDSET_INIT;\n> +\tint advertise_sid = 0;\n> +\n> +\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>\n>  \tif (args->stateless_rpc && multi_ack == 1)\n>  \t\tdie(_(\"the option '%s' requires '%s'\"), \"--stateless-rpc\", \"multi_ack_detailed\");\n> @@ -414,7 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator,\n>  \t\t\tif (deepen_not_ok)      strbuf_addstr(&c, \" deepen-not\");\n>  \t\t\tif (agent_supported)    strbuf_addf(&c, \" agent=%s\",\n>  \t\t\t\t\t\t\t    git_user_agent_sanitized());\n> -\t\t\tif (advertise_sid)\n> +\t\t\tif (advertise_sid && server_supports(\"session-id\"))\n>  \t\t\t\tstrbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n>  \t\t\tif (args->filter_options.choice)\n>  \t\t\t\tstrbuf_addstr(&c, \" filter\");\n> @@ -1160,9 +1162,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n>  \t\t\t\t      (int)agent_len, agent_feature);\n>  \t}\n>\n> -\tif (!server_supports(\"session-id\"))\n> -\t\tadvertise_sid = 0;\n> -\n\nSo earlier we'd set the `advertise_sid` to 0 if the server didn't support\n'session-id'. But we could directly check where it is needed, which is\n`find_common()`. Is `find_common()` only called in v0/v1 as stated in\nthe commit message?\n\n>  \tif (server_supports(\"shallow\"))\n>  \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n>  \telse if (args->depth > 0 || is_repository_shallow(r))\n> @@ -1380,6 +1379,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>  \t\t\t\t\t\t const struct string_list *server_options)\n>  {\n>  \tconst char *hash_name;\n> +\tint advertise_sid = 0;\n> +\n> +\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>\n>  \tensure_server_supports_v2(\"fetch\");\n>  \tpacket_buf_write(req_buf, \"command=fetch\");\n> @@ -1998,7 +2000,6 @@ static void fetch_pack_config(void)\n>  \trepo_config_get_bool(the_repository, \"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n>  \trepo_config_get_bool(the_repository, \"fetch.fsckobjects\", &fetch_fsck_objects);\n>  \trepo_config_get_bool(the_repository, \"transfer.fsckobjects\", &transfer_fsck_objects);\n> -\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>  \tif (!uri_protocols.nr) {\n>  \t\tchar *str;\n>\n>\n> --\n> 2.54.0\n"},{"id":"548338","messageId":"CAOLa=ZSzgsxA0ysJ6hMv9OJNVHco0YHmwQWwirD0ZTDJKMSWfQ@mail.gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-11-34d7adb051bb@gmail.com","subject":"Re: [PATCH GSoC v18 11/13] cat-file: add remote-object-info to batch-command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-07-15T22:27:58Z","receivedAt":"2026-07-15T22:28:01Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\n> index 86b9181599..a7fa6674c3 100644\n> --- a/Documentation/git-cat-file.adoc\n> +++ b/Documentation/git-cat-file.adoc\n> @@ -169,6 +169,13 @@ info <object>::\n>  \tPrint object info for object reference `<object>`. This corresponds to the\n>  \toutput of `--batch-check`.\n>\n> +remote-object-info <remote> <object>...::\n> +\tPrint object info for object references `<object>` at specified\n> +\t`<remote>` without downloading objects from the remote.\n> +\tRaise an error when the `object-info` capability is not supported by the remote.\n> +\tRaise an error when no object references are provided.\n> +\tThis command may be combined with `--buffer`.\n> +\n>  flush::\n>  \tUsed with `--buffer` to execute all preceding commands that were issued\n>  \tsince the beginning or since the last flush was issued. When `--buffer`\n> @@ -301,7 +308,8 @@ one per line, and print information based on the command given. With\n>  `--batch-command`, the `info` command followed by an object will print\n>  information about the object the same way `--batch-check` would, and the\n>  `contents` command followed by an object prints contents in the same way\n> -`--batch` would.\n> +`--batch` would. The `remote-object-info` command followed by a remote and\n> +objects IDs prints object info from the remote without downloading the objects.\n>\n>  You can specify the information shown for each object by using a custom\n>  `<format>`. The `<format>` is copied literally to stdout for each\n> @@ -324,15 +332,12 @@ newline. The available atoms are:\n>  \treports).\n>\n>  `objectsize:disk`::\n> -\tThe size, in bytes, that the object takes up on disk. See the\n> -\tnote about on-disk sizes in the `CAVEATS` section below.\n> +\tThe size, in bytes, that the object takes up on disk.\n>\n\nWhy did we remove the second sentence here? Cause the caveat still\nexists and holds good.\n\n>  `deltabase`::\n>  \tIf the object is stored as a delta on-disk, this expands to the\n>  \tfull hex representation of the delta base object name.\n> -\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n> -\tbelow.\n> -\n> +\tOtherwise, expands to the null OID (all zeroes).\n\nWe also remove the newline here...\n\n>  `rest`::\n>  \tIf this atom is used in the output string, input lines are split\n>  \tat the first whitespace boundary. All characters before that\n> @@ -340,8 +345,14 @@ newline. The available atoms are:\n>  \tafter that first run of whitespace (i.e., the \"rest\" of the\n>  \tline) are output in place of the `%(rest)` atom.\n>\n> +The command `remote-object-info` only supports the `%(objectname)` and\n> +`%(objectsize)` placeholders. See `CAVEATS` below for more information.\n> +\n>  If no format is specified, the default format is `%(objectname)\n> -%(objecttype) %(objectsize)`.\n> +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which\n> +use `%(objectname) %(objectsize)` because \"%(objecttype)\" is not supported yet.\n> +WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified,\n> +so DO NOT RELY on the current default format to stay the same!!!\n>\n>  If `--batch` is specified, or if `--batch-command` is used with the `contents`\n>  command, the object information is followed by the object contents (consisting\n> @@ -438,6 +449,10 @@ scripting purposes.\n>  CAVEATS\n>  -------\n>\n> +Note that since only `%(objectname)` and `%(objectsize)` are currently\n> +supported by the `remote-object-info` command. Using any other placeholder in\n> +the format string will raise an error.\n> +\n>  Note that the sizes of objects on disk are reported accurately, but care\n>  should be taken in drawing conclusions about which refs or objects are\n>  responsible for disk usage. The size of a packed non-delta object may\n> be\n>\n\n[snip]\n\n\n> +static void parse_cmd_remote_object_info(struct batch_options *opt,\n> +\t\t\t\t\t const char *line, struct strbuf *output,\n> +\t\t\t\t\t struct expand_data *data)\n> +{\n> +\tint count;\n> +\tconst char **argv;\n> +\tchar *line_to_split;\n> +\tstruct object_info *remote_object_info = NULL;\n> +\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n> +\n> +\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n> +\t\tdie(_(\"remote-object-info command too long\"));\n> +\n> +\tline_to_split = xstrdup(line);\n> +\tcount = split_cmdline(line_to_split, &argv);\n> +\tif (count < 0)\n> +\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n\nNit: Should we provide some context here? Otherwise no point being\nmarked for translation?\n\n> +\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n> +\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n> +\t\t    MAX_ALLOWED_OBJ_LIMIT);\n> +\n> +\tif (get_remote_info(opt, count, argv, &remote_object_info,\n> +\t\t\t    &object_info_oids))\n> +\t\tgoto cleanup;\n> +\n> +\tdata->skip_object_info = 1;\n> +\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n> +\t\tdata->oid = object_info_oids.oid[i];\n> +\t\tif (remote_object_info[i].sizep) {\n> +\t\t\t/*\n> +\t\t\t * When reaching here, it means remote-object-info can retrieve\n> +\t\t\t * information from server without downloading them.\n> +\t\t\t */\n> +\t\t\tdata->size = *remote_object_info[i].sizep;\n> +\t\t\topt->batch_mode = BATCH_MODE_INFO;\n> +\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n> +\t\t} else {\n> +\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n> +\t\t}\n> +\t}\n> +\tdata->skip_object_info = 0;\n> +\n> +cleanup:\n> +\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n> +\t\tfree_object_info_contents(&remote_object_info[i]);\n> +\tfree(line_to_split);\n> +\tfree(argv);\n> +\tfree(remote_object_info);\n> +\toid_array_clear(&object_info_oids);\n> +}\n> +\n>  static void dispatch_calls(struct batch_options *opt,\n>  \t\tstruct strbuf *output,\n>  \t\tstruct expand_data *data,\n> @@ -748,8 +889,9 @@ static const struct parse_cmd {\n>  } commands[] = {\n>  \t{ \"contents\", parse_cmd_contents, 1 },\n>  \t{ \"info\", parse_cmd_info, 1 },\n> -\t{ \"flush\", NULL, 0 },\n>  \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n> +\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n> +\t{ \"flush\", NULL, 0 },\n\nWhy do we move this? we remove the alphabetical ordering by doing s\n\n>  };\n>\n>  static void batch_objects_command(struct batch_options *opt,\n> diff --git a/object-file.c b/object-file.c\n> index 6453b1d6fa..07f019a0f6 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -1694,3 +1694,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)\n>\n>  \treturn &transaction->base;\n>  }\n> +\n> +void free_object_info_contents(struct object_info *object_info)\n> +{\n> +\tif (!object_info)\n> +\t\treturn;\n> +\tfree(object_info->typep);\n> +\tfree(object_info->sizep);\n> +\tfree(object_info->disk_sizep);\n> +\tfree(object_info->delta_base_oid);\n> +}\n\n[snip]\n"},{"id":"548339","messageId":"CAOLa=ZSoAPCG5R3+By4N1z5AdoJwsKnv1s96rYeMheo-H310DQ@mail.gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"Re: [PATCH GSoC v18 00/13] cat-file: add remote-object-info to batch-command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-07-15T22:35:03Z","receivedAt":"2026-07-15T22:35:04Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> This patch series is a continuation of Eric Ju's\n> (eric.peijian@gmail.com) and Calvin Wan's (calvinwan@google.com) patch\n> series [1] and [2] respectively.\n>\n> Sometimes it is beneficial to retrieve information about an object\n> without having to download it completely. The server logic for\n> retrieving size has already been implemented and merged in a2ba162cda\n> (object-info: support for retrieving object info, 2021-04-20) [3].\n> This patch series implement the client option for it.\n>\n> Eric's series adds the remote-object-info command to cat-file\n> --batch-command. This command allows the client to make an object-info\n> command request to a server that supports protocol v2.\n>\n> If the server uses protocol v2 but does not support the object-info\n> capability, cat-file --batch-command will die.\n>\n> If a user attempts to use remote-object-info with protocol v1, cat-file\n> --batch-command will die.\n>\n> Currently, only the size (%(objectsize)) is supported end to end in this\n> implementation. The type (%(objecttype)) is known by the client's\n> allow-list and request path but is not supported on the server side\n> nor the response parsing. A follow up series will add full end-to-end\n> support for %(objecttype).\n>\n> The default format for remote-object-info is set to \"%(objectname)\n> %(objectsize)\". Once %(objecttype) is supported, the default format will\n> be unified accordingly.\n>\n> If the batch command format includes unsupported fields such as\n> %(objecttype), %(objectsize:disk), or %(deltabase), the command will\n> return empty strings for each unsupported field.\n>\n> This series completes Eric's work mainly with the refactor of the\n> validation of the placeholder with an allow-list that filters what the\n> client asks with what the server is capable of provide following Jeff\n> King's idea [4].\n>\n> GitHub CI: https://github.com/pabloosabaterr/git/actions/runs/29404390713\n>\n> [1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n> [2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n> [3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n> [4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n>\n> Changes since v17:\n>\n> At 10th commit: transport: add client support for object-info\n> - style\n> - enforced the server response handling, not allowing bare \"<oid>\" or\n>   responses with a different number of attributes different from the\n>   number asked.\n>\n\nSome small comments/nits from my side, mostly looks good otherwise :)\n"},{"id":"548348","messageId":"xmqqmrvrk9ge.fsf@gitster.g","threadId":"61695","inReplyTo":"DJZH1PLDC08G.1XTK39BO8YOVS@gmail.com","subject":"Re: [PATCH GSoC v18 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-16T01:30:09Z","receivedAt":"2026-07-16T01:30:14Z","isPatch":true,"body":"\"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n\n> We can force \"size\" when only %(objectname) is requested so the\n> server validates the OID, and discard the size on the client side.\n>\n> Because this is a cheap fix, I'll add a NEEDSWORK for the existence\n> check to be done regardless of the attributes requested.\n\nHmph, why NEEDSWORK?  Not doing so would mean that the result\nlacks correctness.  Why should the first version of this series\ndeliberately produce an incorrect result?\n\n"},{"id":"548349","messageId":"DJZM80YDCMQG.SXP22P43W1J3@gmail.com","threadId":"61695","inReplyTo":"xmqqmrvrk9ge.fsf@gitster.g","subject":"Re: [PATCH GSoC v18 13/13] cat-file: make remote-object-info allow-list dynamic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-16T01:47:32Z","receivedAt":"2026-07-16T01:47:35Z","isPatch":true,"body":"On Thu Jul 16, 2026 at 3:30 AM CEST, Junio C Hamano wrote:\n> \"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n>\n>> We can force \"size\" when only %(objectname) is requested so the\n>> server validates the OID, and discard the size on the client side.\n>>\n>> Because this is a cheap fix, I'll add a NEEDSWORK for the existence\n>> check to be done regardless of the attributes requested.\n>\n> Hmph, why NEEDSWORK?  Not doing so would mean that the result\n> lacks correctness.  Why should the first version of this series\n> deliberately produce an incorrect result?\n\nI was trying to keep the scope of this series to client-side only.\n\nBut you are right, I'll add a prep commit in this series that fixes the\nbehavior. no NEEDSWORK.\n\nThanks,\nPablo\n"},{"id":"548421","messageId":"DK02X0ULSX7S.24CCVGDZC6W9Y@gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZSy1Z-R38cqFiz-Ejj9CNJkp4x_6rFk_wdfhyBytYH9fw@mail.gmail.com","subject":"Re: [PATCH GSoC v18 05/13] fetch-pack: drop static advertise_sid variable","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-16T14:52:28Z","receivedAt":"2026-07-16T14:52:32Z","isPatch":true,"body":"On Wed Jul 15, 2026 at 11:48 PM CEST, Karthik Nayak wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> write_fetch_command_and_capabilities() is moved to 'connect.c' in a\n>> subsequent commit. To prepare for that, drop the static variable usage\n>> of advertise_sid. Currently advertise_sid is used in two places:\n>>\n>> 1. In function do_fetch_pack():\n>>         if (!server_supports(\"session-id\"))\n>>                advertise_sid = 0;\n>>\n>> 2. In function fetch_pack_config():\n>>         repo_config_get_bool(\"transfer.advertisesid\", &advertise_sid);\n>>\n>\n> Nit: But #2 isn't a usecase, it's where it is set no? Looking at the\n> usecase, it seems like we have two:\n>\n>   #1 like you stated\n>   #2 within `write_fetch_command_and_capabilities()`. But the flow is\n>     that, the variable is set in `fetch_pack_config()`, right?\n\nCompletly true, fetch_pack_config() is where it's set, but not where\nit's used.\n\nI'll fix #2 to be where it is used.\n\nThanks.\n\n>\n>> About 1, it is only relevant for v0/v1 protocol, move it into\n>> find_common().\n>>\n>> About 2, call repo_config_get_bool() inside of\n>> write_fetch_command_and_capabilities() and find_common() replacing the\n>> static variable.\n>>\n>> Because repo_config_get_bool() leaves advertise_sid as is if it is not\n>> set, initialize it to 0 matching its default.\n>>\n>> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n>> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n>> Signed-off-by: Calvin Wan <calvinwan@google.com>\n>> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n>> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n>> ---\n>>  fetch-pack.c | 13 +++++++------\n>>  1 file changed, 7 insertions(+), 6 deletions(-)\n>>\n>> diff --git a/fetch-pack.c b/fetch-pack.c\n>> index eea72b2500..8e04db8640 100644\n>> --- a/fetch-pack.c\n>> +++ b/fetch-pack.c\n>> @@ -49,7 +49,6 @@ static int fetch_fsck_objects = -1;\n>>  static int transfer_fsck_objects = -1;\n>>  static int agent_supported;\n>>  static int server_supports_filtering;\n>> -static int advertise_sid;\n>>  static struct shallow_lock shallow_lock;\n>>  static const char *alternate_shallow_file;\n>>  static struct strbuf fsck_msg_types = STRBUF_INIT;\n>> @@ -363,6 +362,9 @@ static int find_common(struct fetch_negotiator *negotiator,\n>>  \tsize_t state_len = 0;\n>>  \tstruct packet_reader reader;\n>>  \tstruct oidset negotiation_include_oids = OIDSET_INIT;\n>> +\tint advertise_sid = 0;\n>> +\n>> +\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>>\n>>  \tif (args->stateless_rpc && multi_ack == 1)\n>>  \t\tdie(_(\"the option '%s' requires '%s'\"), \"--stateless-rpc\", \"multi_ack_detailed\");\n>> @@ -414,7 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator,\n>>  \t\t\tif (deepen_not_ok)      strbuf_addstr(&c, \" deepen-not\");\n>>  \t\t\tif (agent_supported)    strbuf_addf(&c, \" agent=%s\",\n>>  \t\t\t\t\t\t\t    git_user_agent_sanitized());\n>> -\t\t\tif (advertise_sid)\n>> +\t\t\tif (advertise_sid && server_supports(\"session-id\"))\n>>  \t\t\t\tstrbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n>>  \t\t\tif (args->filter_options.choice)\n>>  \t\t\t\tstrbuf_addstr(&c, \" filter\");\n>> @@ -1160,9 +1162,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n>>  \t\t\t\t      (int)agent_len, agent_feature);\n>>  \t}\n>>\n>> -\tif (!server_supports(\"session-id\"))\n>> -\t\tadvertise_sid = 0;\n>> -\n>\n> So earlier we'd set the `advertise_sid` to 0 if the server didn't support\n> 'session-id'. But we could directly check where it is needed, which is\n> `find_common()`. Is `find_common()` only called in v0/v1 as stated in\n> the commit message?\n\nYes, find_common() is only called from do_fetch_pack() which is the v0/v1\npath, if we take a look at fetch_pack():\n\n\tif (version == protocol_v2) {\n\t\t[snip]\n\t\tref_cpy = do_fetch_pack_v2(args, fd, ref, sought, nr_sought,\n\t\t\t\t\t   &shallows_scratch, &si,\n\t\t\t\t\t   pack_lockfiles);\n\t} else {\n\t\t[snip]\n\t\tref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,\n\t\t\t\t\t&si, pack_lockfiles);\n\t}\n\n>\n>>  \tif (server_supports(\"shallow\"))\n>>  \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n>>  \telse if (args->depth > 0 || is_repository_shallow(r))\n>> @@ -1380,6 +1379,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>>  \t\t\t\t\t\t const struct string_list *server_options)\n>>  {\n>>  \tconst char *hash_name;\n>> +\tint advertise_sid = 0;\n>> +\n>> +\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>>\n>>  \tensure_server_supports_v2(\"fetch\");\n>>  \tpacket_buf_write(req_buf, \"command=fetch\");\n>> @@ -1998,7 +2000,6 @@ static void fetch_pack_config(void)\n>>  \trepo_config_get_bool(the_repository, \"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n>>  \trepo_config_get_bool(the_repository, \"fetch.fsckobjects\", &fetch_fsck_objects);\n>>  \trepo_config_get_bool(the_repository, \"transfer.fsckobjects\", &transfer_fsck_objects);\n>> -\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>>  \tif (!uri_protocols.nr) {\n>>  \t\tchar *str;\n>>\n>>\n>> --\n>> 2.54.0\n\nThanks for the feedback, will make it clear next reroll,\nPablo\n"},{"id":"548427","messageId":"xmqqcxwmj3gl.fsf@gitster.g","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-10-34d7adb051bb@gmail.com","subject":"Re: [PATCH GSoC v18 10/13] transport: add client support for object-info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-16T16:37:14Z","receivedAt":"2026-07-16T16:37:20Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> @@ -1159,6 +1159,7 @@ LIB_OBJS += ewah/ewah_rlw.o\n>  LIB_OBJS += exec-cmd.o\n>  LIB_OBJS += fetch-negotiator.o\n>  LIB_OBJS += fetch-pack.o\n> +LIB_OBJS += fetch-object-info.o\n>  LIB_OBJS += fmt-merge-msg.o\n>  LIB_OBJS += fsck.o\n>  LIB_OBJS += fsmonitor.o\n\nNoticed while preparing evil merge for Patrick's patch to move\neverything under a new lib/ directory, but you inserted the new\nentry in a wrong place, i.e. 'o' < 'p'.  Keep the list sorted.\n"},{"id":"548519","messageId":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com","subject":"[PATCH GSoC v19 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:10Z","receivedAt":"2026-07-17T15:05:33Z","isPatch":true,"body":"This patch series is a continuation of Eric Ju's\n(eric.peijian@gmail.com) and Calvin Wan's (calvinwan@google.com) patch\nseries [1] and [2] respectively.\n\nSometimes it is beneficial to retrieve information about an object\nwithout having to download it completely. The server logic for\nretrieving size has already been implemented and merged in a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20) [3].\nThis patch series implements the client option for it.\n\nEric's series adds the remote-object-info command to cat-file\n--batch-command. This command allows the client to make an object-info\ncommand request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info\ncapability, cat-file --batch-command will die.\nIf a user attempts to use remote-object-info with protocol v1, cat-file\n--batch-command will die.\n\nCurrently, only the size (%(objectsize)) is supported end to end in this\nimplementation. The type (%(objecttype)) is known by the client's\nallow-list and request path but is not supported on the server side\nnor the response parsing. A follow up series will add full end-to-end\nsupport for %(objecttype).\n\nThe default format for remote-object-info is set to \"%(objectname)\n%(objectsize)\". Once %(objecttype) is supported, the default format will\nbe unified accordingly.\n\nIf the batch command format includes unsupported fields such as\n%(objecttype), %(objectsize:disk), or %(deltabase), the command will\nreturn empty strings for each unsupported field.\n\nThis series completes Eric's work mainly with the refactor of the\nvalidation of the placeholders with an allow-list that filters what the\nclient asks with what the server is capable of providing, following Jeff\nKing's idea [4].\n\nGithub CI: https://github.com/pabloosabaterr/git/actions/runs/29586719871\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n[4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n\nChanges in v19:\n- Changed the commit structure:\n\t- squashed v18 10th and 11th commits into:\n\t  cat-file: add remote-object-info to batch-command\n\t- Moved after the refactor and renamed:\n\t  connect: use unsigned int for hash_algo_by_name() calls.\n\t- Added a new commit:\n\t  protocol-caps: ...\n\n- protocol-caps: check object existence regardless of the attributes\n  requested; a bare OID request now gets an existence check\n\n  Don't lazy-fetch on the server to answer object-info\n  requests (OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK)\n\n- gitprotocol-v2: correct the object-info response grammar to match\n  the implementations\n\n- fetch-object-info: validate that each response line echoes the\n  requested object ID at that position\n\n- cat-file: the remote-object-info default format no longer leaks into\n  subsequent info commands in the same session\n\n- commit message and documentation fixes\n\n---\nCalvin Wan (3):\n      fetch-pack: move fetch initialization\n      serve: advertise object-info feature\n      transport: add client support for object-info\n\nEric Ju (3):\n      cat-file: declare loop counter inside for()\n      t1006: extract helper functions into new 'lib-cat-file.sh'\n      cat-file: add remote-object-info to batch-command\n\nPablo Sabater (7):\n      transport-helper: fix memory leak of helper on disconnect\n      fetch-pack: drop the static advertise_sid variable\n      fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n      connect: use unsigned int for hash_algo_by_name() calls\n      connect: make write_fetch_command_and_capabilities() more generic\n      protocol-caps: check object existence regardless of the attributes requested\n      cat-file: make remote-object-info allow-list adapt to the server\n\n Documentation/git-cat-file.adoc        |  28 +-\n Documentation/gitprotocol-v2.adoc      |  21 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 214 +++++++++-\n connect.c                              |  38 +-\n connect.h                              |   8 +\n fetch-object-info.c                    | 156 +++++++\n fetch-object-info.h                    |  25 ++\n fetch-pack.c                           |  58 +--\n meson.build                            |   1 +\n object-file.c                          |  10 +\n odb.h                                  |   9 +\n protocol-caps.c                        |  45 +-\n serve.c                                |   5 +-\n t/lib-cat-file.sh                      |  16 +\n t/meson.build                          |   1 +\n t/t1006-cat-file.sh                    |  15 +-\n t/t1017-cat-file-remote-object-info.sh | 747 +++++++++++++++++++++++++++++++++\n t/t5701-git-serve.sh                   |  63 +++\n transport-helper.c                     |  12 +-\n transport-internal.h                   |   8 +\n transport.c                            |  44 ++\n transport.h                            |   9 +\n 23 files changed, 1437 insertions(+), 97 deletions(-)\n\n---\nbase-commit: 44de1520f08d1dfebc3ab2d9f644208eaa5ac925\n"},{"id":"548520","messageId":"20260717-ps-eric-work-rebase-v19-1-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 01/13] transport-helper: fix memory leak of helper on disconnect","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:11Z","receivedAt":"2026-07-17T15:05:33Z","isPatch":true,"body":"disconnect_helper() only frees data inside of the if(data->helper) block\n[1]. When the transport is disconnected without the helper being fully\nstarted, data->name allocated in transport_helper_init()\nis never freed.\n\nMove FREE_AND_NULL(data->name) outside the conditional block so it's\nalways freed on disconnect.\n\n[1]: https://lore.kernel.org/git/05fbadbae2184479c87c37675dde7bd79b3e32ab.1716465556.git.ps@pks.im/\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n transport-helper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 80f90eb7ba..f195070788 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tres = finish_command(data->helper);\n-\t\tFREE_AND_NULL(data->name);\n \t\tFREE_AND_NULL(data->helper);\n \t}\n+\tFREE_AND_NULL(data->name);\n \treturn res;\n }\n \n\n-- \n2.54.0\n"},{"id":"548523","messageId":"20260717-ps-eric-work-rebase-v19-2-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 02/13] cat-file: declare loop counter inside for()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:12Z","receivedAt":"2026-07-17T15:05:36Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nDeclare loop counters in the for statement when they are only used\nwithin the loop body, limiting their scope and improving readability.\n\nWhile updating the loop counters, use size_t instead of int for counters\nthat iterate over object counts.\n\nUpdate the 'nr' parameter of dispatch_calls() to size_t as all callers\nalready pass a value of that type.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c | 13 ++++---------\n fetch-pack.c       |  3 +--\n 2 files changed, 5 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex b4b99a73da..03afc44c5e 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -721,14 +721,12 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n \t\tstruct queued_cmd *cmd,\n-\t\tint nr)\n+\t\tsize_t nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -736,9 +734,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -765,7 +761,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -775,7 +770,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 29c41132ee..9eb8fc5399 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n\n-- \n2.54.0\n"},{"id":"548521","messageId":"20260717-ps-eric-work-rebase-v19-4-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 04/13] fetch-pack: drop the static advertise_sid variable","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:14Z","receivedAt":"2026-07-17T15:05:37Z","isPatch":true,"body":"write_fetch_command_and_capabilities() is moved to 'connect.c' in a\nsubsequent commit. To prepare for that, drop the static variable usage\nof advertise_sid.\n\nCurrently advertise_sid is set in fetch_pack_config() by reading\n\"transfer.advertisesid\". It is used in three places:\n\n1. In do_fetch_pack(), to clear it when the server lacks support:\n\n        if (!server_supports(\"session-id\"))\n               advertise_sid = 0;\n\n2. In find_common(), to advertise the session id over protocol v0/v1:\n\n        if (advertise_sid)\n                strbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n\n3. In write_fetch_command_and_capabilities(), to advertise it over\n   protocol v2:\n\n        if (advertise_sid && server_supports_v2(\"session-id\"))\n                packet_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n\nAbout 1, the check only guards the v0/v1 path, and the v2 path\nalready checks server support inline in its condition. Follow the\nsame pattern and fold the check into the condition in find_common().\n\nAbout 2 and 3, replace the static variable with a local read via\nrepo_config_get_bool() in each function.\n\nBecause repo_config_get_bool() leaves advertise_sid as is if it is not\nset, initialize it to 0, matching its default.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 13 +++++++------\n 1 file changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 9eb8fc5399..65ebfec09f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -49,7 +49,6 @@ static int fetch_fsck_objects = -1;\n static int transfer_fsck_objects = -1;\n static int agent_supported;\n static int server_supports_filtering;\n-static int advertise_sid;\n static struct shallow_lock shallow_lock;\n static const char *alternate_shallow_file;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n@@ -363,6 +362,9 @@ static int find_common(struct fetch_negotiator *negotiator,\n \tsize_t state_len = 0;\n \tstruct packet_reader reader;\n \tstruct oidset negotiation_include_oids = OIDSET_INIT;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tif (args->stateless_rpc && multi_ack == 1)\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--stateless-rpc\", \"multi_ack_detailed\");\n@@ -414,7 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator,\n \t\t\tif (deepen_not_ok)      strbuf_addstr(&c, \" deepen-not\");\n \t\t\tif (agent_supported)    strbuf_addf(&c, \" agent=%s\",\n \t\t\t\t\t\t\t    git_user_agent_sanitized());\n-\t\t\tif (advertise_sid)\n+\t\t\tif (advertise_sid && server_supports(\"session-id\"))\n \t\t\t\tstrbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n \t\t\tif (args->filter_options.choice)\n \t\t\t\tstrbuf_addstr(&c, \" filter\");\n@@ -1160,9 +1162,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\t\t\t      (int)agent_len, agent_feature);\n \t}\n \n-\tif (!server_supports(\"session-id\"))\n-\t\tadvertise_sid = 0;\n-\n \tif (server_supports(\"shallow\"))\n \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n \telse if (args->depth > 0 || is_repository_shallow(r))\n@@ -1380,6 +1379,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t\t\t\t\t\t const struct string_list *server_options)\n {\n \tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tensure_server_supports_v2(\"fetch\");\n \tpacket_buf_write(req_buf, \"command=fetch\");\n@@ -1998,7 +2000,6 @@ static void fetch_pack_config(void)\n \trepo_config_get_bool(the_repository, \"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n \trepo_config_get_bool(the_repository, \"fetch.fsckobjects\", &fetch_fsck_objects);\n \trepo_config_get_bool(the_repository, \"transfer.fsckobjects\", &transfer_fsck_objects);\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \tif (!uri_protocols.nr) {\n \t\tchar *str;\n \n\n-- \n2.54.0\n"},{"id":"548524","messageId":"20260717-ps-eric-work-rebase-v19-3-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 03/13] t1006: extract helper functions into new 'lib-cat-file.sh'","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:13Z","receivedAt":"2026-07-17T15:05:37Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nExtract utility functions from the cat-file's test script\n't1006-cat-file.sh' into a new 'lib-cat-file.sh' dedicated library file.\n\nA subsequent commit will need these functions. This improves the code\nreuse and readability, enabling future cat-file tests to share these\nhelpers without duplicating code.\n\nWhile at it update the style of this line to follow coding\nguidelines:\n\n. \"$TEST_DIRECTORY/lib-loose.sh\"\n\nto\n\n. \"$TEST_DIRECTORY\"/lib-loose.sh\n\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 15 ++-------------\n 2 files changed, 18 insertions(+), 13 deletions(-)\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..7c2e877016\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NUL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 8e2c52652c..cf65bfc88f 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -3,7 +3,8 @@\n test_description='git cat-file'\n \n . ./test-lib.sh\n-. \"$TEST_DIRECTORY/lib-loose.sh\"\n+. \"$TEST_DIRECTORY\"/lib-loose.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     object_name=\"$2\"\n\n-- \n2.54.0\n"},{"id":"548522","messageId":"20260717-ps-eric-work-rebase-v19-5-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 05/13] fetch-pack: move write_fetch_command_and_capabilities() to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:15Z","receivedAt":"2026-07-17T15:05:39Z","isPatch":true,"body":"In a subsequent commit write_fetch_command_and_capabilities() will be\nrefactored to a more general-purpose function, making it more accessible\nto additional commands in the future.\n\nMove write_fetch_command_and_capabilities() to 'connect.c', where\nthere are similar purpose functions.\n\nBecause string_list is only used as a pointer, use a forward\ndeclaration [1].\n\n[1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 34 ----------------------------------\n 3 files changed, 38 insertions(+), 34 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 47e39d2a73..31e5ab958b 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,6 +700,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(\"fetch\");\n+\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t    the_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t    the_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n static const char *url_scheme_name(enum url_scheme scheme)\n {\n \tswitch (scheme) {\ndiff --git a/connect.h b/connect.h\nindex aa482a37fb..c4f6ea4b0a 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+struct string_list;\n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 65ebfec09f..f7789e8456 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1375,40 +1375,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\tint advertise_sid = 0;\n-\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n\n-- \n2.54.0\n"},{"id":"548525","messageId":"20260717-ps-eric-work-rebase-v19-6-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 06/13] connect: use unsigned int for hash_algo_by_name() calls","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:16Z","receivedAt":"2026-07-17T15:05:40Z","isPatch":true,"body":"hash_algo_by_name() returns \"unsigned int\", but multiple variables that\nstore its return are assigned as \"int\".\n\nChange hash_algo_by_name() variables type to match its return type, also\nmake it const because they are never modified.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 31e5ab958b..9d236e7bba 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -248,7 +248,7 @@ static void process_capabilities(struct packet_reader *reader, size_t *linelen)\n \tfeat_val = server_feature_value(\"object-format\", &feat_len);\n \tif (feat_val) {\n \t\tchar *hash_name = xstrndup(feat_val, feat_len);\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo != GIT_HASH_UNKNOWN)\n \t\t\treader->hash_algo = &hash_algos[hash_algo];\n \t\tfree(hash_name);\n@@ -496,7 +496,7 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)\n \t\tpacket_write_fmt(fd_out, \"agent=%s\", git_user_agent_sanitized());\n \n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo == GIT_HASH_UNKNOWN)\n \t\t\tdie(_(\"unknown object format '%s' specified by server\"), hash_name);\n \t\treader->hash_algo = &hash_algos[hash_algo];\n@@ -722,7 +722,7 @@ void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t}\n \n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n \t\t\t    the_hash_algo->name, hash_name);\n\n-- \n2.54.0\n"},{"id":"548526","messageId":"20260717-ps-eric-work-rebase-v19-7-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 07/13] connect: make write_fetch_command_and_capabilities() more generic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:17Z","receivedAt":"2026-07-17T15:05:40Z","isPatch":true,"body":"Refactor write_fetch_command_and_capabilities(), enabling it to serve\nboth fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol Documentation/gitprotocol-v2.adoc, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20).\n\nRefactor the function signature to accept a command instead of the\nhardcoded \"fetch\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 8 ++++----\n connect.h    | 8 ++++++--\n fetch-pack.c | 4 ++--\n 3 files changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 9d236e7bba..9d54d40770 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,16 +700,16 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options)\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \tint advertise_sid = 0;\n \n \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\ndiff --git a/connect.h b/connect.h\nindex c4f6ea4b0a..957e5fe2b9 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -35,7 +35,11 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       const char *error);\n \n struct string_list;\n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options);\n+/*\n+ * Write a protocol v2 command request, along with the capability\n+ * advertisements, into req_buf.\n+ */\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f7789e8456..3695059cd5 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1386,7 +1386,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2253,7 +2253,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n\n-- \n2.54.0\n"},{"id":"548529","messageId":"20260717-ps-eric-work-rebase-v19-8-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 08/13] fetch-pack: move fetch initialization","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:18Z","receivedAt":"2026-07-17T15:05:42Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized in\nFETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3695059cd5..922a9b2581 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1735,18 +1735,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n\n-- \n2.54.0\n"},{"id":"548527","messageId":"20260717-ps-eric-work-rebase-v19-9-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 09/13] protocol-caps: check object existence regardless of the attributes requested","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:19Z","receivedAt":"2026-07-17T15:05:43Z","isPatch":true,"body":"Currently, send_info() only checks for existence when the attribute\n'size' is also requested. Requesting a bare OID, without attributes only\nechoes back the OID.\n\nExtract the existence check to be done regardless of the number of\nattributes requested.\n\nWhile at it, introduce a wrapper called get_object_info() similar to\nodb_read_object_info() that returns OBJ_BAD on fail and adds\nOBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK flags.\nOBJECT_INFO_SKIP_FETCH_OBJECT is so a server with a partial clone\ndoesn't trigger fetching objects when it gets an object-info request\nwith an OID that is not available locally. A server should only report\nwhat it has locally.\n\nTighten the condition used to determine whether an object is\nrecognized. get_object_info() returns OBJ_BAD for unknown objects,\nbut OBJ_NONE (0) can also mean \"not found\". Change the check from '< 0'\nto '<= OBJ_NONE' to cover both as unrecognized.\n\nWith this patch, a bare OID has two possible responses:\n\n1. Recognized OID: the server answers with \"<OID>\"\n\n2. Unrecognized OID: the server answers with \"<OID> SP\"\n\nUpdate the object-info section in 'gitprotocol-v2.adoc':\n- Require full obj-oid explicitly.\n- Fix parentheses.\n- Define obj-size explicitly.\n- Make obj-size optional in obj-info and document the behavior\n  for unrecognized object IDs.\n- Describe the attr header as zero or more pkt-lines, one per attribute,\n  matching what the server implements. A request with no attributes gets\n  no header.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/gitprotocol-v2.adoc | 21 ++++++++-----\n protocol-caps.c                   | 45 ++++++++++++++++++++++++----\n t/t5701-git-serve.sh              | 63 +++++++++++++++++++++++++++++++++++++++\n 3 files changed, 115 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 2beb70595f..7bf62014c3 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -568,21 +568,26 @@ An `object-info` request takes the following arguments:\n \n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n-\tinformation for.\n+\tinformation for. They must be full OIDs.\n \n-The response of `object-info` is a list of the requested object ids\n-and associated requested information, each separated by a single space.\n+The response of `object-info` consists of one pkt-line per requested attribute,\n+echoing the attributes the server will report, followed by one pkt-line per\n+requested object id with its information, each field separated by a single\n+space.\n \n \toutput = info flush-pkt\n \n-\tinfo = PKT-LINE(attrs) LF)\n-\t\t*PKT-LINE(obj-info LF)\n-\n-\tattrs = attr | attrs SP attrs\n+\tinfo = *PKT-LINE(attr LF)\n+\t       *PKT-LINE(obj-info LF)\n \n \tattr = \"size\"\n \n-\tobj-info = obj-id SP obj-size\n+\tobj-size = 1*DIGIT\n+\n+\tobj-info = obj-id [SP [obj-size]]\n+\n+If the server does not recognize the OID, the response will be `<oid> SP`\n+regardless of the number of attributes requested.\n \n bundle-uri\n ~~~~~~~~~~\ndiff --git a/protocol-caps.c b/protocol-caps.c\nindex 8858ea4489..02261be14d 100644\n--- a/protocol-caps.c\n+++ b/protocol-caps.c\n@@ -30,6 +30,32 @@ static int parse_oid(const char *line, struct string_list *oid_str_list)\n \treturn 1;\n }\n \n+/*\n+ * odb_read_object_info_extended() wrapper. Similar to odb_read_object_info()\n+ * but uses the flags:\n+ *\n+ * - OBJECT_INFO_SKIP_FETCH_OBJECT so a server won't fetch an object when a\n+ *   object-info request asks for an OID that it doesn't have.\n+ *\n+ * - OBJECT_INFO_QUICK to avoid re-scanning packs when the object is not found.\n+ */\n+static enum object_type get_object_info(struct object_database *odb,\n+\t\t\t   const struct object_id *oid,\n+\t\t\t   size_t *sizep)\n+{\n+\tenum object_type type;\n+\tstruct object_info oi = OBJECT_INFO_INIT;\n+\n+\toi.typep = &type;\n+\toi.sizep = sizep;\n+\tif (odb_read_object_info_extended(odb, oid, &oi,\n+\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE |\n+\t\t\t\t\t  OBJECT_INFO_SKIP_FETCH_OBJECT |\n+\t\t\t\t\t  OBJECT_INFO_QUICK) < 0)\n+\t\treturn OBJ_BAD;\n+\treturn type;\n+}\n+\n /*\n  * Validates and send requested info back to the client. Any errors detected\n  * are returned as they are detected.\n@@ -62,15 +88,22 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \n \t\tstrbuf_addstr(&send_buffer, oid_str);\n \n+\t\t/*\n+\t\t * Check the existence of the object first.\n+\t\t * If an object is not recognized by the server append SP to\n+\t\t * the response.\n+\t\t */\n+\t\tif (get_object_info(r->objects, &oid, &object_size) <= OBJ_NONE) {\n+\t\t\tstrbuf_addstr(&send_buffer, \" \");\n+\t\t\tgoto write;\n+\t\t}\n+\n \t\tif (info->size) {\n-\t\t\tif (odb_read_object_info(r->objects, &oid, &object_size) < 0) {\n-\t\t\t\tstrbuf_addstr(&send_buffer, \" \");\n-\t\t\t} else {\n-\t\t\t\tstrbuf_addf(&send_buffer, \" %\"PRIuMAX,\n-\t\t\t\t\t    (uintmax_t)object_size);\n-\t\t\t}\n+\t\t\tstrbuf_addf(&send_buffer, \" %\"PRIuMAX,\n+\t\t\t\t    (uintmax_t)object_size);\n \t\t}\n \n+write:\n \t\tpacket_writer_write(writer, \"%s\", send_buffer.buf);\n \t\tstrbuf_reset(&send_buffer);\n \t}\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex d4c28bae39..cacff4456c 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -7,6 +7,8 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n \n+unknown_oid=$(printf \"test\" | git hash-object --stdin)\n+\n test_expect_success 'setup to generate files with expected content' '\n \tprintf \"agent=git/%s\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n \n@@ -364,6 +366,67 @@ test_expect_success 'basics of object-info' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'bare OID request' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\toid $(git rev-parse two:two.t)\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\t$(git rev-parse two:two.t)\n+\t0000\n+\tEOF\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'object-info with bare unrecognized OID' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\toid $unknown_oid\n+\t0000\n+\tEOF\n+\n+\tprintf \"%s \\n\" \"$unknown_oid\" >expect &&\n+\tprintf \"0000\\n\" >>expect &&\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'object-info with size for unrecognized OID' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\tsize\n+\toid $unknown_oid\n+\t0000\n+\tEOF\n+\n+\tprintf \"size\\n\" >expect &&\n+\tprintf \"%s \\n\" \"$unknown_oid\" >>expect &&\n+\tprintf \"0000\\n\" >>expect &&\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'test capability advertisement with uploadpack.advertiseBundleURIs' '\n \ttest_config uploadpack.advertiseBundleURIs true &&\n \n\n-- \n2.54.0\n"},{"id":"548528","messageId":"20260717-ps-eric-work-rebase-v19-10-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 10/13] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:20Z","receivedAt":"2026-07-17T15:05:44Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This allows a client\nto decide whether to query the server for object-info or fetch as a\nfallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n serve.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex 49a6e39b1d..2b07d922b3 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\t/* Currently only size is supported */\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n\n-- \n2.54.0\n"},{"id":"548530","messageId":"20260717-ps-eric-work-rebase-v19-11-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 11/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:21Z","receivedAt":"2026-07-17T15:05:45Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nIntroduce client-side support for the object-info capability.\n\nAdd its own function for object-info separate from existing fetch\ninfrastructure.\n\nCurrently, the client supports requesting a list of OIDs with the size\nattribute from a v2 server. If the server does not advertise this\nfeature (i.e., transfer.advertiseobjectinfo is set to false), the client\nreturns an error and exits.\n\nNote that:\n\n1. The entire request is written into req_buf before being sent to the\n   remote. This approach follows the pattern used in the\n   send_fetch_request() logic within 'fetch-pack.c'. Streaming the\n   request is not addressed in this patch.\n\n2. A new field 'unrecognized' has been added to object_info. This new\n   field is set at fetch_object_info() when the object is unrecognized\n   by the server.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Makefile             |   1 +\n fetch-object-info.c  | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h  |  22 ++++++++\n meson.build          |   1 +\n odb.h                |   6 +++\n transport-helper.c   |  10 ++++\n transport-internal.h |   8 +++\n transport.c          |  45 +++++++++++++++++\n transport.h          |   9 ++++\n 9 files changed, 240 insertions(+)\n\ndiff --git a/Makefile b/Makefile\nindex 1f3f099f5c..d450e0277e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1158,6 +1158,7 @@ LIB_OBJS += ewah/ewah_io.o\n LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fetch-pack.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..30475a1e87\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,138 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"odb.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\telse if (args->object_info_options->nr)\n+\t\tBUG(\"only size should be in object_info_options\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+static int parse_object_size(const char *s, size_t *res)\n+{\n+\tuintmax_t uim;\n+\n+\tif (!s[0] || s[strspn(s, \"0123456789\")])\n+\t\treturn -1;\n+\terrno = 0;\n+\tuim = strtoumax(s, NULL, 10);\n+\tif (errno || uim > SIZE_MAX)\n+\t\treturn -1;\n+\t*res = uim;\n+\treturn 0;\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"object-info requires protocol v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader,\n+\t\t\t\t\t\t  \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\t/*\n+\t\t\t * i is the number of supported options which currently\n+\t\t\t * is only size. No risk of overflow.\n+\t\t\t */\n+\t\t\tsize_index = (int)i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep =\n+\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t} else {\n+\t\t\tBUG(\"only size is supported\");\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0;\n+\t     packet_reader_read(reader) == PACKET_READ_NORMAL &&\n+\t     i < args->oids->nr;\n+\t     i++) {\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n+\n+\t\tif (strcmp(object_info_values.items[0].string,\n+\t\t\t   oid_to_hex(&args->oids->oid[i])))\n+\t\t\tdie(_(\"object-info: expected OID: %s, got %s\"),\n+\t\t\t    oid_to_hex(&args->oids->oid[i]),\n+\t\t\t    object_info_values.items[0].string);\n+\n+\t\t/*\n+\t\t * If the response is two elements but the second one is an\n+\t\t * empty string, that means that the OID is unrecognized by the\n+\t\t * server.\n+\t\t */\n+\t\tif (object_info_values.nr >= 2 &&\n+\t\t    !strcmp(object_info_values.items[1].string, \"\")) {\n+\t\t\tobject_info_data[i].unrecognized = 1;\n+\t\t\tstring_list_clear(&object_info_values, 0);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Because we filter the options to be only the supported by\n+\t\t * the server we expect the server to answer with the same\n+\t\t * number of attributes requested.\n+\t\t */\n+\t\tif (args->object_info_options->nr + 1 != object_info_values.nr)\n+\t\t\tdie(\"object-info: unexpected number of attributes: %s\",\n+\t\t\t    reader->line);\n+\n+\t\tif (size_index >= 0 &&\n+\t\t    parse_object_size(object_info_values.items[size_index + 1].string,\n+\t\t\t\t      object_info_data[i].sizep))\n+\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t    object_info_values.items[0].string,\n+\t\t\t    object_info_values.items[size_index + 1].string);\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..31aad98408\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+struct object_info;\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/meson.build b/meson.build\nindex 9434b56960..dfefcd3475 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -359,6 +359,7 @@ libgit_sources = [\n   'ewah/ewah_rlw.c',\n   'exec-cmd.c',\n   'fetch-negotiator.c',\n+  'fetch-object-info.c',\n   'fetch-pack.c',\n   'fmt-merge-msg.c',\n   'fsck.c',\ndiff --git a/odb.h b/odb.h\nindex 94754643d2..88a37febbf 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -339,6 +339,12 @@ struct object_info {\n \t * or multiple times in the same source.\n \t */\n \tstruct odb_source_info *source_infop;\n+\n+\t/*\n+\t * object-info protocol specific. Set by the protocol when the remote\n+\t * does not recognize the requested object.\n+\t */\n+\tunsigned int unrecognized:1;\n };\n \n /*\ndiff --git a/transport-helper.c b/transport-helper.c\nindex f195070788..623463dcea 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -784,6 +784,15 @@ static int fetch_refs(struct transport *transport,\n \treturn -1;\n }\n \n+static int fetch_object_info_helper(struct transport *transport)\n+{\n+\tget_helper(transport);\n+\tif (process_connect(transport, 0))\n+\t\treturn transport->vtable->fetch_object_info(transport);\n+\n+\tdie(_(\"object-info requires protocol v2\"));\n+}\n+\n struct push_update_ref_state {\n \tstruct ref *hint;\n \tstruct ref_push_report *report;\n@@ -1330,6 +1339,7 @@ static struct transport_vtable vtable = {\n \t.get_refs_list\t= get_refs_list,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs,\n+\t.fetch_object_info = fetch_object_info_helper,\n \t.push_refs\t= push_refs,\n \t.connect\t= connect_helper,\n \t.disconnect\t= release_helper\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 051f3ab0dc..60db0bedcd 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -45,6 +45,14 @@ struct transport_vtable {\n \t **/\n \tint (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);\n \n+\t/*\n+\t * Fetch object info (only size currently) from remote without\n+\t * downloading the objects.\n+\t *\n+\t * Uses object-info capability of v2 protocol.\n+\t */\n+\tint (*fetch_object_info)(struct transport *transport);\n+\n \t/**\n \t * Push the objects and refs. Send the necessary objects, and\n \t * then, for any refs where peer_ref is set and\ndiff --git a/transport.c b/transport.c\nindex fc144f0aed..9342680531 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -432,6 +433,48 @@ static int get_bundle_uri(struct transport *transport)\n \t\t\t\t     transport->bundles, stateless_rpc);\n }\n \n+static int fetch_object_info_via_pack(struct transport *transport)\n+{\n+\tint ret = 0;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct packet_reader reader;\n+\tstruct object_info_args args = { 0 };\n+\n+\targs.server_options = transport->server_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\tstring_list_sort(args.object_info_options);\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tdata->version = discover_version(&reader);\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tret = fetch_object_info(data->version, &args, &reader,\n+\t\t\t\tdata->options.object_info_data,\n+\t\t\t\ttransport->stateless_rpc, data->fd[1]);\n+\n+\tclose(data->fd[0]);\n+\tif (data->fd[1] >= 0)\n+\t\tclose(data->fd[1]);\n+\tif (finish_connect(data->conn))\n+\t\tret = -1;\n+\tdata->conn = NULL;\n+\n+\treturn ret;\n+}\n+\n+int transport_fetch_object_info(struct transport *transport)\n+{\n+\tif (!transport->vtable->fetch_object_info)\n+\t\tdie(_(\"remote does not support object-info\"));\n+\treturn transport->vtable->fetch_object_info(transport);\n+}\n+\n static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t       int nr_heads, struct ref **to_fetch)\n {\n@@ -1004,6 +1047,7 @@ static struct transport_vtable taken_over_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.disconnect\t= disconnect_git\n };\n@@ -1169,6 +1213,7 @@ static struct transport_vtable builtin_smart_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.connect\t= connect_git,\n \t.disconnect\t= disconnect_git\ndiff --git a/transport.h b/transport.h\nindex 7e5867cffa..a7869d18e0 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -55,6 +55,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n@@ -309,6 +313,11 @@ int transport_get_remote_bundle_uri(struct transport *transport);\n const struct git_hash_algo *transport_get_hash_algo(struct transport *transport);\n int transport_fetch_refs(struct transport *transport, struct ref *refs);\n \n+/*\n+ * Fetch the object info from remote\n+ */\n+int transport_fetch_object_info(struct transport *transport);\n+\n /*\n  * If this flag is set, unlocking will avoid to call non-async-signal-safe\n  * functions. This will necessarily leave behind some data structures which\n\n-- \n2.54.0\n"},{"id":"548532","messageId":"20260717-ps-eric-work-rebase-v19-12-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 12/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:22Z","receivedAt":"2026-07-17T15:05:46Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSince the info command in cat-file --batch-command prints object\ninfo for a given object, it is natural to add another command in\ncat-file --batch-command to print object info for a given object\nfrom a remote.\n\nAdd remote-object-info command to cat-file --batch-command.\n\nWhile info takes object ids one at a time, this creates overhead when\nmaking requests to a server. So remote-object-info instead can take\nmultiple object ids at once.\n\nThe cat-file --batch-command command is generally implemented in the\nfollowing manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, remote-object-info is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is remote-object-info:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode remote-object-info:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is remote-object-info:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, remote-object-info gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nPlaceholders in the format are validated against an allow-list of the\natoms the remote path supports: \"objectname\" and \"objectsize\".\nUnsupported atoms expand to an empty string, honoring how for-each-ref\nhandles known but inapplicable atoms.\nWithout this, atoms like %(objecttype) would mark data->info.typep and\nbecause the server only sends size, type_name() would later crash.\nAs extra safety, even outside of the remote path, initialize\nexpand_data's type to OBJ_BAD and handle type_name() returning NULL.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n[pablo: added the atom allow-list validation]\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  28 +-\n builtin/cat-file.c                     | 179 +++++++-\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n t/meson.build                          |   1 +\n t/t1017-cat-file-remote-object-info.sh | 719 +++++++++++++++++++++++++++++++++\n 6 files changed, 929 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex 86b9181599..7522e24874 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -169,6 +169,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -301,7 +308,8 @@ one per line, and print information based on the command given. With\n `--batch-command`, the `info` command followed by an object will print\n information about the object the same way `--batch-check` would, and the\n `contents` command followed by an object prints contents in the same way\n-`--batch` would.\n+`--batch` would. The `remote-object-info` command followed by a remote and\n+objects IDs prints object info from the remote without downloading the objects.\n \n You can specify the information shown for each object by using a custom\n `<format>`. The `<format>` is copied literally to stdout for each\n@@ -324,14 +332,12 @@ newline. The available atoms are:\n \treports).\n \n `objectsize:disk`::\n-\tThe size, in bytes, that the object takes up on disk. See the\n-\tnote about on-disk sizes in the `CAVEATS` section below.\n+\tThe size, in bytes, that the object takes up on disk.\n \n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n-\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n-\tbelow.\n+\tOtherwise, expands to the null OID (all zeroes).\n \n `rest`::\n \tIf this atom is used in the output string, input lines are split\n@@ -340,8 +346,14 @@ newline. The available atoms are:\n \tafter that first run of whitespace (i.e., the \"rest\" of the\n \tline) are output in place of the `%(rest)` atom.\n \n+The command `remote-object-info` only supports the `%(objectname)` and\n+`%(objectsize)` placeholders. See `CAVEATS` below for more information.\n+\n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which\n+use `%(objectname) %(objectsize)` because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified,\n+so DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -438,6 +450,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that only `%(objectname)` and `%(objectsize)` are currently\n+supported by the `remote-object-info` command. Using any other placeholder in\n+the format string will return an empty string in its position.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 03afc44c5e..f5e5528a21 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -29,6 +29,22 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/*\n+ * Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8 * 1024)\n+\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE \\\n+\t(MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -317,8 +333,18 @@ struct expand_data {\n \t * optimized out.\n \t */\n \tunsigned skip_object_info : 1;\n+\n+\t/*\n+\t * Flags about when an object info is being fetched from remote.\n+\t */\n+\tunsigned is_remote:1;\n+};\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n+\n+static const char *remote_object_info_atoms[] = {\n+\t\"objectname\",\n+\t\"objectsize\",\n };\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -329,14 +355,31 @@ static int is_atom(const char *atom, const char *s, int slen)\n static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n+\tif (data->is_remote) {\n+\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n+\t\tfor (i = 0; i < allowed_nr; i++)\n+\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * On remote, skip unsupported atoms returning an empty sb,\n+\t\t * honoring how for-each-ref handles known but inapplicable\n+\t\t * atoms (e.g. %(tagger)).\n+\t\t */\n+\t\tif (i == allowed_nr)\n+\t\t\treturn 1;\n+\t}\n+\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n \t\t\tstrbuf_add_oid_hex(sb, &data->oid);\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n-\t\tif (data->mark_query)\n+\t\tif (data->mark_query) {\n \t\t\tdata->info.typep = &data->type;\n-\t\telse\n-\t\t\tstrbuf_addstr(sb, type_name(data->type));\n+\t\t} else {\n+\t\t\tconst char *t = type_name(data->type);\n+\t\t\tstrbuf_addstr(sb, t ? t : \"\");\n+\t\t}\n \t} else if (is_atom(\"objectsize\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.sizep = &data->size;\n@@ -636,6 +679,65 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(int argc,\n+\t\t\t   const char **argv,\n+\t\t\t   struct object_info **remote_object_info,\n+\t\t\t   struct oid_array *object_info_oids)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstruct transport *gtransport;\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid)) {\n+\t\t\tsize_t len = strlen(argv[i]);\n+\n+\t\t\tif (len < the_hash_algo->hexsz && len >= 4) {\n+\t\t\t\tsize_t j;\n+\t\t\t\tfor (j = 0; j < len; j++)\n+\t\t\t\t\tif (!isxdigit(argv[i][j]))\n+\t\t\t\t\t\tbreak;\n+\t\t\t\tif (j == len)\n+\t\t\t\t\tdie(_(\"remote-object-info does not support \"\n+\t\t\t\t\t      \"short oids, %d characters required\"),\n+\t\t\t\t\t    (int)the_hash_algo->hexsz);\n+\t\t\t}\n+\t\t\tdie(_(\"not a valid object name '%s'\"), argv[i]);\n+\t\t}\n+\t\toid_array_append(object_info_oids, &oid);\n+\t}\n+\n+\tif (!object_info_oids->nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\n+\tif (!gtransport->smart_options) {\n+\t\tretval = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n+\tgtransport->smart_options->object_info_oids = object_info_oids;\n+\n+\tstring_list_append(&object_info_options, \"size\");\n+\n+\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\tretval = transport_fetch_object_info(gtransport);\n+cleanup:\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -717,6 +819,72 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\tstruct object_info *remote_object_info = NULL;\n+\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tconst char *saved_format = opt->format;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command too long\"));\n+\t/*\n+\t * TODO: Use the default format once %(objecttype) is supported.\n+\t */\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n+\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n+\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n+\t\t    MAX_ALLOWED_OBJ_LIMIT);\n+\n+\tif (get_remote_info(count, argv, &remote_object_info,\n+\t\t\t    &object_info_oids))\n+\t\tdie(_(\"failed to get object info from the remote: %s\"), argv[0]);\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\n+\t\tif (remote_object_info[i].unrecognized) {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid),\n+\t\t\t\t\t     &data->oid, \"missing\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tdata->is_remote = 1;\n+\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t\tdata->is_remote = 0;\n+\t\t} else {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\topt->format = saved_format;\n+\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+\toid_array_clear(&object_info_oids);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -747,9 +915,10 @@ static const struct parse_cmd {\n \tunsigned takes_args;\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1 },\n-\t{ \"info\", parse_cmd_info, 1 },\n \t{ \"flush\", NULL, 0 },\n+\t{ \"info\", parse_cmd_info, 1 },\n \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex 6453b1d6fa..07f019a0f6 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1694,3 +1694,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)\n \n \treturn &transaction->base;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+}\ndiff --git a/odb.h b/odb.h\nindex 88a37febbf..92fa414e2c 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -623,4 +623,7 @@ void parse_alternates(const char *string,\n \t\t      const char *relative_base,\n \t\t      struct strvec *out);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* ODB_H */\ndiff --git a/t/meson.build b/t/meson.build\nindex 8ae6ab6c5f..10241e3dcc 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -171,6 +171,7 @@ integration_tests = [\n   't1014-read-tree-confusing.sh',\n   't1015-read-index-unmerged.sh',\n   't1016-compatObjectFormat.sh',\n+  't1017-cat-file-remote-object-info.sh',\n   't1020-subdirectory.sh',\n   't1022-read-tree-partial-clone.sh',\n   't1050-large.sh',\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..edc20394d8\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,719 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n+\n+unstored_content=\"Hello Git\"\n+unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not change the default format of info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tlocal_content=\"local object\" &&\n+\t\tlocal_oid=$(echo_without_newline \"$local_content\" | git hash-object -w --stdin) &&\n+\t\tlocal_size=$(strlen \"$local_content\") &&\n+\n+\t\techo \"$local_oid blob $local_size\" >expect &&\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$local_oid blob $local_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tinfo $local_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tinfo $local_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not support short oids' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_short_oid\n+\t\tEOF\n+\t\ttest_grep \"does not support short oids\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not die on missing oid like info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tgit cat-file --batch-command >local <<-EOF &&\n+\t\tinfo $unstored_oid\n+\t\tEOF\n+\t\tgit cat-file --batch-command >remote <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $unstored_oid\n+\t\tEOF\n+\t\ttest_cmp local remote\n+\t)\n+'\n+\n+# This tests depends on %(objecttype) not being supported yet, once supported\n+# it needs to be updated.\n+test_expect_success 'unsupported placeholder on remote returns empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"\" >expect &&\n+\t\tgit cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\techo \"$hello_oid \" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize:disk)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(deltabase)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n\n-- \n2.54.0\n"},{"id":"548531","messageId":"20260717-ps-eric-work-rebase-v19-13-d4faee35764b@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v19 13/13] cat-file: make remote-object-info allow-list adapt to the server","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-17T15:05:23Z","receivedAt":"2026-07-17T15:05:47Z","isPatch":true,"body":"The static allow-list in expand_atom() is hardcoded to allow only\n\"objectname\" and \"objectsize\" for remote queries. This works because,\nup to this point, servers will either support object-info with name\nand size or they do not support them at all.\n\nAs object-info gains new capabilities, we cannot expect different\nservers with different Git versions to have the same object-info\ncapabilities. Therefore, the client needs to adapt its allow-list to\nwhat the server advertises.\n\nThe client now:\n\n1. Requests the protocol option that the placeholder refers to (i.e.\n   \"size\" for \"%(objectsize)\").\n\n2. Drops any requested option that the server does not advertise in\n   fetch_object_info().\n\n3. Maps the remaining advertised options back to their placeholders and\n   populates remote_allowed_atoms.\n\n4. Uses remote_allowed_atoms in expand_atom(), preserving the previous\n   behavior for supported placeholders.\n\nFor example, if the client requests \"%(objectsize) %(objecttype)\" and\nthe server only supports 'size', then the client only requests 'size'.\nThe server returns the size (i.e \"42\") \"%(objectsize)\" is expanded\nnormally while \"%(objecttype)\" expands to an empty string:\n\n\t\"42 \"\n\nNote that the empty string expansion is only for known but unsupported\nplaceholders. \"%(objectcolor)\" which doesn't exist would die().\n\nThis honors what for-each-ref does for known but inapplicable atoms\n(placeholders).\n\nMove object_info_options out of get_remote_info() so the caller which\nhas data can select what options will be requested instead of requesting\nalways size.\n\nMove batch_object_write() out so output is always produced.\nIf there are no supported attributes, the output is a blank line.\n\nInclude \"type\" in the object_info_options even though the client does\nnot yet know how to parse the server's \"type\" capability.\n\nAs a result, \"type\" is always filtered out, allowing the tests to verify\nthat known but unsupported placeholders expand to an empty string.\n\nSince the filter removes options by swapping with the last element,\nthe list is no longer kept sorted. Drop the pre-sort in\nfetch_object_info_via_pack() and use the unsorted string_list lookup\nfor the response header. This has no effect in performance as the list\ncan only be two entries long ('size' and 'type').\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c                     | 86 +++++++++++++++++++++-------------\n fetch-object-info.c                    | 20 +++++++-\n fetch-object-info.h                    |  3 ++\n t/t1017-cat-file-remote-object-info.sh | 28 +++++++++++\n transport.c                            |  1 -\n 5 files changed, 104 insertions(+), 34 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex f5e5528a21..7a0431bf2b 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -338,13 +338,11 @@ struct expand_data {\n \t * Flags about when an object info is being fetched from remote.\n \t */\n \tunsigned is_remote:1;\n-};\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n \n-static const char *remote_object_info_atoms[] = {\n-\t\"objectname\",\n-\t\"objectsize\",\n+\tstruct string_list remote_allowed_atoms;\n };\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -356,17 +354,11 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n \tif (data->is_remote) {\n-\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n-\t\tfor (i = 0; i < allowed_nr; i++)\n-\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\tsize_t i;\n+\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n \t\t\t\tbreak;\n-\n-\t\t/*\n-\t\t * On remote, skip unsupported atoms returning an empty sb,\n-\t\t * honoring how for-each-ref handles known but inapplicable\n-\t\t * atoms (e.g. %(tagger)).\n-\t\t */\n-\t\tif (i == allowed_nr)\n+\t\tif (i == data->remote_allowed_atoms.nr)\n \t\t\treturn 1;\n \t}\n \n@@ -682,12 +674,12 @@ static void batch_one_object(const char *obj_name,\n static int get_remote_info(int argc,\n \t\t\t   const char **argv,\n \t\t\t   struct object_info **remote_object_info,\n-\t\t\t   struct oid_array *object_info_oids)\n+\t\t\t   struct oid_array *object_info_oids,\n+\t\t\t   struct string_list *object_info_options)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n \tstruct object_id oid;\n-\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tstruct transport *gtransport;\n \n \tremote = remote_get(argv[0]);\n@@ -727,13 +719,10 @@ static int get_remote_info(int argc,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\tstring_list_append(&object_info_options, \"size\");\n-\n-\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tgtransport->smart_options->object_info_options = object_info_options;\n \tgtransport->smart_options->object_info_data = *remote_object_info;\n \tretval = transport_fetch_object_info(gtransport);\n cleanup:\n-\tstring_list_clear(&object_info_options, 0);\n \ttransport_disconnect(gtransport);\n \treturn retval;\n }\n@@ -819,6 +808,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+struct protocol_placeholder_entry {\n+\tconst char *option;\n+\tconst char *atom;\n+};\n+\n+static const struct protocol_placeholder_entry remote_atom_map[] = {\n+\t{\"size\", \"objectsize\"},\n+\t{\"type\", \"objecttype\"},\n+\t/*\n+\t * Add new protocol options here. Even if the server doesn't support\n+\t * them the allow_list will drop them if the server doesn't advertise\n+\t * them.\n+\t */\n+};\n+\n static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n@@ -828,6 +832,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct object_info *remote_object_info = NULL;\n \tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tconst char *saved_format = opt->format;\n \n \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n@@ -846,10 +851,22 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n+\tif (data->info.sizep)\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\tif (data->info.typep)\n+\t\tstring_list_append(&object_info_options, \"type\");\n+\n \tif (get_remote_info(count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids))\n+\t\t\t    &object_info_oids, &object_info_options))\n \t\tdie(_(\"failed to get object info from the remote: %s\"), argv[0]);\n \n+\tstring_list_clear(&data->remote_allowed_atoms, 0);\n+\tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n+\tfor (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n+\t\tif (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n+\t\t\tstring_list_append(&data->remote_allowed_atoms,\n+\t\t\t\t\t   remote_atom_map[i].atom);\n+\n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n \t\tdata->oid = object_info_oids.oid[i];\n@@ -860,25 +877,29 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\tcontinue;\n \t\t}\n \n+\t\t/*\n+\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t * information from server without downloading them.\n+\t\t */\n \t\tif (remote_object_info[i].sizep) {\n-\t\t\t/*\n-\t\t\t * When reaching here, it means remote-object-info can retrieve\n-\t\t\t * information from server without downloading them.\n-\t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n-\t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tdata->is_remote = 1;\n-\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n-\t\t\tdata->is_remote = 0;\n-\t\t} else {\n-\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n \t\t}\n+\n+\t\tif (remote_object_info[i].typep) {\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tdata->is_remote = 1;\n+\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n \topt->format = saved_format;\n \n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n+\tstring_list_clear(&object_info_options, 0);\n \tfree(line_to_split);\n \tfree(argv);\n \tfree(remote_object_info);\n@@ -1198,6 +1219,7 @@ static int batch_objects(struct batch_options *opt)\n  cleanup:\n \tstrbuf_release(&input);\n \tstrbuf_release(&output);\n+\tstring_list_clear(&data.remote_allowed_atoms, 0);\n \tcfg->warn_on_object_refname_ambiguity = save_warning;\n \treturn retval;\n }\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 30475a1e87..ba7e179c44 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -55,6 +55,24 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \tcase protocol_v2:\n \t\tif (!server_supports_v2(\"object-info\"))\n \t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\t/*\n+\t\t * When removing an element from the list it gets swapped by the\n+\t\t * last element, iterate backwards to prevent elements skipping\n+\t\t * evaluation.\n+\t\t *\n+\t\t * object_info_options->nr can be safely casted without overflow\n+\t\t * because the number of options is a small known number (the\n+\t\t * supported placeholders which currently are size and type).\n+\t\t */\n+\t\tfor (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)\n+\t\t\tif (!server_supports_feature(\"object-info\",\n+\t\t\t\t\t\t     args->object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(args->object_info_options, i, 0);\n+\n+\t\t/*\n+\t\t * Even if no options are left, we still send the oid so we get\n+\t\t * at least an existence check.\n+\t\t */\n \t\tsend_object_info_request(fd_out, args);\n \t\tbreak;\n \tcase protocol_v1:\n@@ -71,7 +89,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\t\treturn -1;\n \t\t}\n \n-\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\tif (!unsorted_string_list_has_string(args->object_info_options, reader->line))\n \t\t\treturn -1;\n \n \t\tif (!strcmp(reader->line, \"size\")) {\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 31aad98408..269cebb3f7 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -14,6 +14,9 @@ struct object_info;\n /*\n  * Sends git-cat-file object-info command into the request buf and read the\n  * results from packets.\n+ *\n+ * Modifies args->object_info_options, on return it contains only the supported\n+ * options by the server.\n  */\n int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n \t\t      struct packet_reader *reader, struct object_info *object_info_data,\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex edc20394d8..116862f9d0 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -271,6 +271,34 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '\n \t)\n '\n \n+test_expect_success 'requesting only objectname echoes back' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo $hello_oid >expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'objectname goes through existence check' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$unstored_oid missing\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $unstored_oid\n+\t\tEOF\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n # Test --batch-command remote-object-info with 'git://' and\n # transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\ndiff --git a/transport.c b/transport.c\nindex 9342680531..f0a6a45547 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -443,7 +443,6 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \targs.server_options = transport->server_options;\n \targs.oids = transport->smart_options->object_info_oids;\n \targs.object_info_options = transport->smart_options->object_info_options;\n-\tstring_list_sort(args.object_info_options);\n \n \tconnect_setup(transport, 0);\n \tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n\n-- \n2.54.0\n"},{"id":"548591","messageId":"alvHUX68McwlEau2@szeder.dev","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-5-d4faee35764b@gmail.com","subject":"Re: [PATCH GSoC v19 05/13] fetch-pack: move write_fetch_command_and_capabilities() to connect.c","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2026-07-18T18:34:57Z","receivedAt":"2026-07-18T18:35:10Z","isPatch":true,"body":"On Fri, Jul 17, 2026 at 05:05:15PM +0200, Pablo Sabater wrote:\n> In a subsequent commit write_fetch_command_and_capabilities() will be\n> refactored to a more general-purpose function, making it more accessible\n> to additional commands in the future.\n> \n> Move write_fetch_command_and_capabilities() to 'connect.c', where\n> there are similar purpose functions.\n> \n> Because string_list is only used as a pointer, use a forward\n> declaration [1].\n> \n> [1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n> \n> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n>  connect.c    | 34 ++++++++++++++++++++++++++++++++++\n>  connect.h    |  4 ++++\n>  fetch-pack.c | 34 ----------------------------------\n>  3 files changed, 38 insertions(+), 34 deletions(-)\n> \n> diff --git a/connect.c b/connect.c\n> index 47e39d2a73..31e5ab958b 100644\n> --- a/connect.c\n> +++ b/connect.c\n> @@ -700,6 +700,40 @@ int server_supports(const char *feature)\n>  \treturn !!server_feature_value(feature, NULL);\n>  }\n>  \n> +void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> +\t\t\t\t\t  const struct string_list *server_options)\n> +{\n> +\tconst char *hash_name;\n> +\tint advertise_sid = 0;\n> +\n> +\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n> +\n> +\tensure_server_supports_v2(\"fetch\");\n> +\tpacket_buf_write(req_buf, \"command=fetch\");\n> +\tif (server_supports_v2(\"agent\"))\n> +\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n> +\tif (advertise_sid && server_supports_v2(\"session-id\"))\n> +\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n> +\tif (server_options && server_options->nr) {\n> +\t\tensure_server_supports_v2(\"server-option\");\n> +\t\tfor (size_t i = 0; i < server_options->nr; i++)\n> +\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n> +\t\t\t\t\t server_options->items[i].string);\n> +\t}\n> +\n> +\tif (server_feature_v2(\"object-format\", &hash_name)) {\n> +\t\tint hash_algo = hash_algo_by_name(hash_name);\n> +\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n\nhash_algo_by_ptr() returns an unsigned integer, and comparing that to\na signed integer results in:\n\n  connect.c: In function ‘write_fetch_command_and_capabilities’:\n  connect.c:726:53: error: comparison of integer expressions of different signedness: ‘uint32_t’ {aka ‘unsigned int’} and ‘int’ [-Werror=sign-compare]\n    726 |                 if (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n        |                                                     ^~\n  cc1: all warnings being treated as errors\n  make: *** [Makefile:2924: connect.o] Error 1\n\nThis was not an issue while the function lived in \"fetch-pack.c\",\nbecause that file uses #define DISABLE_SIGN_COMPARE_WARNINGS, but\n\"connect.c\" doesn't.\n\nI think as a preparatory step the type of the 'hash_algo' variable\nshould be changed to unsigned int while the function is still in\n\"fetch-pack.c\" to avoid this; you'll convert it in the next\npatch anyway.\n\n> +\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n> +\t\t\t    the_hash_algo->name, hash_name);\n> +\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n> +\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n> +\t\tdie(_(\"the server does not support algorithm '%s'\"),\n> +\t\t    the_hash_algo->name);\n> +\t}\n> +\tpacket_buf_delim(req_buf);\n> +}\n> +\n>  static const char *url_scheme_name(enum url_scheme scheme)\n>  {\n>  \tswitch (scheme) {\n> diff --git a/connect.h b/connect.h\n> index aa482a37fb..c4f6ea4b0a 100644\n> --- a/connect.h\n> +++ b/connect.h\n> @@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n>  \t\t\t       struct packet_reader *reader,\n>  \t\t\t       const char *error);\n>  \n> +struct string_list;\n> +void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> +\t\t\t\t\t  const struct string_list *server_options);\n> +\n>  #endif\n> diff --git a/fetch-pack.c b/fetch-pack.c\n> index 65ebfec09f..f7789e8456 100644\n> --- a/fetch-pack.c\n> +++ b/fetch-pack.c\n> @@ -1375,40 +1375,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n>  \treturn haves_added;\n>  }\n>  \n> -static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n> -\t\t\t\t\t\t const struct string_list *server_options)\n> -{\n> -\tconst char *hash_name;\n> -\tint advertise_sid = 0;\n> -\n> -\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n> -\n> -\tensure_server_supports_v2(\"fetch\");\n> -\tpacket_buf_write(req_buf, \"command=fetch\");\n> -\tif (server_supports_v2(\"agent\"))\n> -\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n> -\tif (advertise_sid && server_supports_v2(\"session-id\"))\n> -\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n> -\tif (server_options && server_options->nr) {\n> -\t\tensure_server_supports_v2(\"server-option\");\n> -\t\tfor (size_t i = 0; i < server_options->nr; i++)\n> -\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n> -\t\t\t\t\t server_options->items[i].string);\n> -\t}\n> -\n> -\tif (server_feature_v2(\"object-format\", &hash_name)) {\n> -\t\tint hash_algo = hash_algo_by_name(hash_name);\n> -\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n> -\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n> -\t\t\t    the_hash_algo->name, hash_name);\n> -\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n> -\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n> -\t\tdie(_(\"the server does not support algorithm '%s'\"),\n> -\t\t    the_hash_algo->name);\n> -\t}\n> -\tpacket_buf_delim(req_buf);\n> -}\n> -\n>  static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n>  \t\t\t      struct fetch_pack_args *args,\n>  \t\t\t      const struct ref *wants, struct oidset *common,\n> \n> -- \n> 2.54.0\n"},{"id":"548592","messageId":"DK1XG2LQP0PS.33L27O783LXE4@gmail.com","threadId":"61695","inReplyTo":"alvHUX68McwlEau2@szeder.dev","subject":"Re: [PATCH GSoC v19 05/13] fetch-pack: move write_fetch_command_and_capabilities() to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T19:00:34Z","receivedAt":"2026-07-18T19:00:40Z","isPatch":true,"body":"On Sat Jul 18, 2026 at 8:34 PM CEST, SZEDER Gábor wrote:\n> On Fri, Jul 17, 2026 at 05:05:15PM +0200, Pablo Sabater wrote:\n>> In a subsequent commit write_fetch_command_and_capabilities() will be\n>> refactored to a more general-purpose function, making it more accessible\n>> to additional commands in the future.\n>>\n>> Move write_fetch_command_and_capabilities() to 'connect.c', where\n>> there are similar purpose functions.\n>>\n>> Because string_list is only used as a pointer, use a forward\n>> declaration [1].\n>>\n>> [1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n>>\n>> Helped-by: Jonathan Tan <jonathantanmy@google.com>\n>> Helped-by: Christian Couder <chriscool@tuxfamily.org>\n>> Signed-off-by: Calvin Wan <calvinwan@google.com>\n>> Signed-off-by: Eric Ju <eric.peijian@gmail.com>\n>> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n>> ---\n>>  connect.c    | 34 ++++++++++++++++++++++++++++++++++\n>>  connect.h    |  4 ++++\n>>  fetch-pack.c | 34 ----------------------------------\n>>  3 files changed, 38 insertions(+), 34 deletions(-)\n>>\n>> diff --git a/connect.c b/connect.c\n>> index 47e39d2a73..31e5ab958b 100644\n>> --- a/connect.c\n>> +++ b/connect.c\n>> @@ -700,6 +700,40 @@ int server_supports(const char *feature)\n>>  \treturn !!server_feature_value(feature, NULL);\n>>  }\n>>\n>> +void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n>> +\t\t\t\t\t  const struct string_list *server_options)\n>> +{\n>> +\tconst char *hash_name;\n>> +\tint advertise_sid = 0;\n>> +\n>> +\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n>> +\n>> +\tensure_server_supports_v2(\"fetch\");\n>> +\tpacket_buf_write(req_buf, \"command=fetch\");\n>> +\tif (server_supports_v2(\"agent\"))\n>> +\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n>> +\tif (advertise_sid && server_supports_v2(\"session-id\"))\n>> +\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n>> +\tif (server_options && server_options->nr) {\n>> +\t\tensure_server_supports_v2(\"server-option\");\n>> +\t\tfor (size_t i = 0; i < server_options->nr; i++)\n>> +\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n>> +\t\t\t\t\t server_options->items[i].string);\n>> +\t}\n>> +\n>> +\tif (server_feature_v2(\"object-format\", &hash_name)) {\n>> +\t\tint hash_algo = hash_algo_by_name(hash_name);\n>> +\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>\n> hash_algo_by_ptr() returns an unsigned integer, and comparing that to\n> a signed integer results in:\n>\n>   connect.c: In function ‘write_fetch_command_and_capabilities’:\n>   connect.c:726:53: error: comparison of integer expressions of different signedness: ‘uint32_t’ {aka ‘unsigned int’} and ‘int’ [-Werror=sign-compare]\n>     726 |                 if (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>         |                                                     ^~\n>   cc1: all warnings being treated as errors\n>   make: *** [Makefile:2924: connect.o] Error 1\n>\n> This was not an issue while the function lived in \"fetch-pack.c\",\n> because that file uses #define DISABLE_SIGN_COMPARE_WARNINGS, but\n> \"connect.c\" doesn't.\n>\n> I think as a preparatory step the type of the 'hash_algo' variable\n> should be changed to unsigned int while the function is still in\n> \"fetch-pack.c\" to avoid this; you'll convert it in the next\n> patch anyway.\n\nThanks for noticing it.\n\nYes, then the prep patch needs to be done in fetch-pack.c before moving\nit to connect.c.\n\nI had it after because 2 hunks of that patch fixes calls for connect.c\nso this way there were all for connect.c instead of mixing.\n\n[snip]\n\nThanks,\nPablo\n"},{"id":"548598","messageId":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260717-ps-eric-work-rebase-v19-0-d4faee35764b@gmail.com","subject":"[PATCH GSoC v20 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:49Z","receivedAt":"2026-07-18T21:50:07Z","isPatch":true,"body":"This patch series is a continuation of Eric Ju's\n(eric.peijian@gmail.com) and Calvin Wan's (calvinwan@google.com) patch\nseries [1] and [2] respectively.\n\nSometimes it is beneficial to retrieve information about an object\nwithout having to download it completely. The server logic for\nretrieving size has already been implemented and merged in a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20) [3].\nThis patch series implements the client option for it.\n\nEric's series adds the remote-object-info command to cat-file\n--batch-command. This command allows the client to make an object-info\ncommand request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info\ncapability, cat-file --batch-command will die.\nIf a user attempts to use remote-object-info with protocol v1, cat-file\n--batch-command will die.\n\nCurrently, only the size (%(objectsize)) is supported end to end in this\nimplementation. The type (%(objecttype)) is known by the client's\nallow-list and request path but is not supported on the server side\nnor the response parsing. A follow up series will add full end-to-end\nsupport for %(objecttype).\n\nThe default format for remote-object-info is set to \"%(objectname)\n%(objectsize)\". Once %(objecttype) is supported, the default format will\nbe unified accordingly.\n\nIf the batch command format includes unsupported fields such as\n%(objecttype), %(objectsize:disk), or %(deltabase), the command will\nreturn empty strings for each unsupported field.\n\nThis series completes Eric's work mainly with the refactor of the\nvalidation of the placeholders with an allow-list that filters what the\nclient asks with what the server is capable of providing, following Jeff\nKing's idea [4].\n\nGitHub CI: https://github.com/pabloosabaterr/git/actions/runs/29660677279\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n[4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n\nChanges in v20:\n- Moved the prep patch that fixes hash_algo's type to be before\n  write_fetch_command_and_capabilities() being moved to 'connect.c'\n- Reverted git-cat-file.adoc documentation comments about CAVEATS\n- Fixed style for EXPAND_DATA_INIT\n- Added more context for comman line die()\n\n---\nCalvin Wan (3):\n      fetch-pack: move fetch initialization\n      serve: advertise object-info feature\n      transport: add client support for object-info\n\nEric Ju (3):\n      cat-file: declare loop counter inside for()\n      t1006: extract helper functions into new 'lib-cat-file.sh'\n      cat-file: add remote-object-info to batch-command\n\nPablo Sabater (7):\n      transport-helper: fix memory leak of helper on disconnect\n      fetch-pack: drop the static advertise_sid variable\n      fetch-pack: use unsigned int for hash_algo variable\n      fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n      connect: make write_fetch_command_and_capabilities() more generic\n      protocol-caps: check object existence regardless of the attributes requested\n      cat-file: make remote-object-info allow-list adapt to the server\n\n Documentation/git-cat-file.adoc        |  24 +-\n Documentation/gitprotocol-v2.adoc      |  21 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 222 +++++++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    | 156 +++++++\n fetch-object-info.h                    |  25 ++\n fetch-pack.c                           |  58 +--\n meson.build                            |   1 +\n object-file.c                          |  10 +\n odb.h                                  |   9 +\n protocol-caps.c                        |  45 +-\n serve.c                                |   5 +-\n t/lib-cat-file.sh                      |  16 +\n t/meson.build                          |   1 +\n t/t1006-cat-file.sh                    |  15 +-\n t/t1017-cat-file-remote-object-info.sh | 747 +++++++++++++++++++++++++++++++++\n t/t5701-git-serve.sh                   |  63 +++\n transport-helper.c                     |  12 +-\n transport-internal.h                   |   8 +\n transport.c                            |  44 ++\n transport.h                            |   9 +\n 23 files changed, 1442 insertions(+), 92 deletions(-)\n\nRange-diff versus v19:\n\n 1:  6df5bcb3bc =  1:  31b02f10b5 transport-helper: fix memory leak of helper on disconnect\n 2:  141d85a76f =  2:  7d62e0586f cat-file: declare loop counter inside for()\n 3:  87457a1fe3 =  3:  082d593fb7 t1006: extract helper functions into new 'lib-cat-file.sh'\n 4:  c26d378931 =  4:  8646b9d7cf fetch-pack: drop the static advertise_sid variable\n -:  ---------- >  5:  5531a3c399 fetch-pack: use unsigned int for hash_algo variable\n 5:  ee16e17228 !  6:  92b4122642 fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n    @@ connect.c: int server_supports(const char *feature)\n     +\t}\n     +\n     +\tif (server_feature_v2(\"object-format\", &hash_name)) {\n    -+\t\tint hash_algo = hash_algo_by_name(hash_name);\n    ++\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n     +\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n     +\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n     +\t\t\t    the_hash_algo->name, hash_name);\n    @@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n     -\t}\n     -\n     -\tif (server_feature_v2(\"object-format\", &hash_name)) {\n    --\t\tint hash_algo = hash_algo_by_name(hash_name);\n    +-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n     -\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n     -\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n     -\t\t\t    the_hash_algo->name, hash_name);\n 6:  bb915f57ad <  -:  ---------- connect: use unsigned int for hash_algo_by_name() calls\n 7:  b3b06b0cae =  7:  b954a5994a connect: make write_fetch_command_and_capabilities() more generic\n 8:  79a87c7011 =  8:  627242a7a1 fetch-pack: move fetch initialization\n 9:  37c3f93975 =  9:  0578594733 protocol-caps: check object existence regardless of the attributes requested\n10:  c5062ecaf2 = 10:  816bfa9162 serve: advertise object-info feature\n11:  22d72168bc = 11:  2323f45cb2 transport: add client support for object-info\n12:  2cf3b24a35 ! 12:  a39975766b cat-file: add remote-object-info to batch-command\n    @@ Documentation/git-cat-file.adoc: one per line, and print information based on th\n      You can specify the information shown for each object by using a custom\n      `<format>`. The `<format>` is copied literally to stdout for each\n     @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n    - \treports).\n    -\n    - `objectsize:disk`::\n    --\tThe size, in bytes, that the object takes up on disk. See the\n    --\tnote about on-disk sizes in the `CAVEATS` section below.\n    -+\tThe size, in bytes, that the object takes up on disk.\n    -\n      `deltabase`::\n      \tIf the object is stored as a delta on-disk, this expands to the\n      \tfull hex representation of the delta base object name.\n     -\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n    --\tbelow.\n    -+\tOtherwise, expands to the null OID (all zeroes).\n    ++\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS` section\n    + \tbelow.\n\n      `rest`::\n    - \tIf this atom is used in the output string, input lines are split\n     @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n      \tafter that first run of whitespace (i.e., the \"rest\" of the\n      \tline) are output in place of the `%(rest)` atom.\n    @@ builtin/cat-file.c: struct expand_data {\n     +\t */\n     +\tunsigned is_remote:1;\n     +};\n    ++\n     +#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n     +\n     +static const char *remote_object_info_atoms[] = {\n    @@ builtin/cat-file.c: static void parse_cmd_mailmap(struct batch_options *opt UNUS\n     +\tline_to_split = xstrdup(line);\n     +\tcount = split_cmdline(line_to_split, &argv);\n     +\tif (count < 0)\n    -+\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n    ++\t\tdie(_(\"remote-object-info: failed to parse command line: %s\"),\n    ++\t\t    split_cmdline_strerror(count));\n     +\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n     +\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n     +\t\t    MAX_ALLOWED_OBJ_LIMIT);\n13:  ebdfc22fb7 ! 13:  70a11d2aea cat-file: make remote-object-info allow-list adapt to the server\n    @@ builtin/cat-file.c: struct expand_data {\n      \t */\n      \tunsigned is_remote:1;\n     -};\n    +-\n     -#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n\n     -static const char *remote_object_info_atoms[] = {\n     -\t\"objectname\",\n     -\t\"objectsize\",\n    ++\t/*\n    ++\t * List of atoms (i.e. \"objectsize\") that the server supports. Built\n    ++\t * from the server's object-info advertised capabilities.\n    ++\t */\n     +\tstruct string_list remote_allowed_atoms;\n      };\n    -+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n    -+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n\n    ++#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, \\\n    ++\t\t\t    .type = OBJ_BAD, \\\n    ++\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n    ++\n      static int is_atom(const char *atom, const char *s, int slen)\n      {\n    + \tint alen = strlen(atom);\n     @@ builtin/cat-file.c: static int expand_atom(struct strbuf *sb, const char *atom, int len,\n      \t\t       struct expand_data *data)\n      {\n    @@ builtin/cat-file.c: static int expand_atom(struct strbuf *sb, const char *atom,\n     -\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n     +\t\tsize_t i;\n     +\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n    -+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n    ++\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string,\n    ++\t\t\t\t    atom, len))\n      \t\t\t\tbreak;\n     -\n     -\t\t/*\n\n---\nbase-commit: 44de1520f08d1dfebc3ab2d9f644208eaa5ac925\n"},{"id":"548599","messageId":"20260718-ps-eric-work-rebase-v20-1-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 01/13] transport-helper: fix memory leak of helper on disconnect","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:50Z","receivedAt":"2026-07-18T21:50:08Z","isPatch":true,"body":"disconnect_helper() only frees data inside of the if(data->helper) block\n[1]. When the transport is disconnected without the helper being fully\nstarted, data->name allocated in transport_helper_init()\nis never freed.\n\nMove FREE_AND_NULL(data->name) outside the conditional block so it's\nalways freed on disconnect.\n\n[1]: https://lore.kernel.org/git/05fbadbae2184479c87c37675dde7bd79b3e32ab.1716465556.git.ps@pks.im/\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n transport-helper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 80f90eb7ba..f195070788 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tres = finish_command(data->helper);\n-\t\tFREE_AND_NULL(data->name);\n \t\tFREE_AND_NULL(data->helper);\n \t}\n+\tFREE_AND_NULL(data->name);\n \treturn res;\n }\n \n\n-- \n2.54.0\n"},{"id":"548600","messageId":"20260718-ps-eric-work-rebase-v20-2-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 02/13] cat-file: declare loop counter inside for()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:51Z","receivedAt":"2026-07-18T21:50:10Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nDeclare loop counters in the for statement when they are only used\nwithin the loop body, limiting their scope and improving readability.\n\nWhile updating the loop counters, use size_t instead of int for counters\nthat iterate over object counts.\n\nUpdate the 'nr' parameter of dispatch_calls() to size_t as all callers\nalready pass a value of that type.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c | 13 ++++---------\n fetch-pack.c       |  3 +--\n 2 files changed, 5 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex b4b99a73da..03afc44c5e 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -721,14 +721,12 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n \t\tstruct queued_cmd *cmd,\n-\t\tint nr)\n+\t\tsize_t nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -736,9 +734,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -765,7 +761,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -775,7 +770,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 29c41132ee..9eb8fc5399 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n\n-- \n2.54.0\n"},{"id":"548601","messageId":"20260718-ps-eric-work-rebase-v20-3-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 03/13] t1006: extract helper functions into new 'lib-cat-file.sh'","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:52Z","receivedAt":"2026-07-18T21:50:11Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nExtract utility functions from the cat-file's test script\n't1006-cat-file.sh' into a new 'lib-cat-file.sh' dedicated library file.\n\nA subsequent commit will need these functions. This improves the code\nreuse and readability, enabling future cat-file tests to share these\nhelpers without duplicating code.\n\nWhile at it update the style of this line to follow coding\nguidelines:\n\n. \"$TEST_DIRECTORY/lib-loose.sh\"\n\nto\n\n. \"$TEST_DIRECTORY\"/lib-loose.sh\n\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 15 ++-------------\n 2 files changed, 18 insertions(+), 13 deletions(-)\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..7c2e877016\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NUL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 8e2c52652c..cf65bfc88f 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -3,7 +3,8 @@\n test_description='git cat-file'\n \n . ./test-lib.sh\n-. \"$TEST_DIRECTORY/lib-loose.sh\"\n+. \"$TEST_DIRECTORY\"/lib-loose.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     object_name=\"$2\"\n\n-- \n2.54.0\n"},{"id":"548602","messageId":"20260718-ps-eric-work-rebase-v20-4-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 04/13] fetch-pack: drop the static advertise_sid variable","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:53Z","receivedAt":"2026-07-18T21:50:13Z","isPatch":true,"body":"write_fetch_command_and_capabilities() is moved to 'connect.c' in a\nsubsequent commit. To prepare for that, drop the static variable usage\nof advertise_sid.\n\nCurrently advertise_sid is set in fetch_pack_config() by reading\n\"transfer.advertisesid\". It is used in three places:\n\n1. In do_fetch_pack(), to clear it when the server lacks support:\n\n        if (!server_supports(\"session-id\"))\n               advertise_sid = 0;\n\n2. In find_common(), to advertise the session id over protocol v0/v1:\n\n        if (advertise_sid)\n                strbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n\n3. In write_fetch_command_and_capabilities(), to advertise it over\n   protocol v2:\n\n        if (advertise_sid && server_supports_v2(\"session-id\"))\n                packet_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n\nAbout 1, the check only guards the v0/v1 path, and the v2 path\nalready checks server support inline in its condition. Follow the\nsame pattern and fold the check into the condition in find_common().\n\nAbout 2 and 3, replace the static variable with a local read via\nrepo_config_get_bool() in each function.\n\nBecause repo_config_get_bool() leaves advertise_sid as is if it is not\nset, initialize it to 0, matching its default.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 13 +++++++------\n 1 file changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 9eb8fc5399..65ebfec09f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -49,7 +49,6 @@ static int fetch_fsck_objects = -1;\n static int transfer_fsck_objects = -1;\n static int agent_supported;\n static int server_supports_filtering;\n-static int advertise_sid;\n static struct shallow_lock shallow_lock;\n static const char *alternate_shallow_file;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n@@ -363,6 +362,9 @@ static int find_common(struct fetch_negotiator *negotiator,\n \tsize_t state_len = 0;\n \tstruct packet_reader reader;\n \tstruct oidset negotiation_include_oids = OIDSET_INIT;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tif (args->stateless_rpc && multi_ack == 1)\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--stateless-rpc\", \"multi_ack_detailed\");\n@@ -414,7 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator,\n \t\t\tif (deepen_not_ok)      strbuf_addstr(&c, \" deepen-not\");\n \t\t\tif (agent_supported)    strbuf_addf(&c, \" agent=%s\",\n \t\t\t\t\t\t\t    git_user_agent_sanitized());\n-\t\t\tif (advertise_sid)\n+\t\t\tif (advertise_sid && server_supports(\"session-id\"))\n \t\t\t\tstrbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n \t\t\tif (args->filter_options.choice)\n \t\t\t\tstrbuf_addstr(&c, \" filter\");\n@@ -1160,9 +1162,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\t\t\t      (int)agent_len, agent_feature);\n \t}\n \n-\tif (!server_supports(\"session-id\"))\n-\t\tadvertise_sid = 0;\n-\n \tif (server_supports(\"shallow\"))\n \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n \telse if (args->depth > 0 || is_repository_shallow(r))\n@@ -1380,6 +1379,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t\t\t\t\t\t const struct string_list *server_options)\n {\n \tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tensure_server_supports_v2(\"fetch\");\n \tpacket_buf_write(req_buf, \"command=fetch\");\n@@ -1998,7 +2000,6 @@ static void fetch_pack_config(void)\n \trepo_config_get_bool(the_repository, \"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n \trepo_config_get_bool(the_repository, \"fetch.fsckobjects\", &fetch_fsck_objects);\n \trepo_config_get_bool(the_repository, \"transfer.fsckobjects\", &transfer_fsck_objects);\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \tif (!uri_protocols.nr) {\n \t\tchar *str;\n \n\n-- \n2.54.0\n"},{"id":"548603","messageId":"20260718-ps-eric-work-rebase-v20-5-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 05/13] fetch-pack: use unsigned int for hash_algo variable","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:54Z","receivedAt":"2026-07-18T21:50:15Z","isPatch":true,"body":"hash_algo_by_name() returns unsigned int, but it is stored in\nhash_algo variable as int. This goes unnoticed because of:\n\n\tDISABLE_SIGN_COMPARE_WARNINGS\n\nOn 'fetch-pack.c'\n\nOn a subsequent commit this function will be moved to 'connect.c' that\nwould notice this.\n\nChange hash_algo variable type to match its return type, also make it\nconst because they are never modified.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 65ebfec09f..f1e64160fc 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1397,7 +1397,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t}\n \n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n \t\t\t    the_hash_algo->name, hash_name);\n\n-- \n2.54.0\n"},{"id":"548604","messageId":"20260718-ps-eric-work-rebase-v20-6-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 06/13] fetch-pack: move write_fetch_command_and_capabilities() to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:55Z","receivedAt":"2026-07-18T21:50:16Z","isPatch":true,"body":"In a subsequent commit write_fetch_command_and_capabilities() will be\nrefactored to a more general-purpose function, making it more accessible\nto additional commands in the future.\n\nMove write_fetch_command_and_capabilities() to 'connect.c', where\nthere are similar purpose functions.\n\nBecause string_list is only used as a pointer, use a forward\ndeclaration [1].\n\n[1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 34 ----------------------------------\n 3 files changed, 38 insertions(+), 34 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 47e39d2a73..c09947cc56 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,6 +700,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(\"fetch\");\n+\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t    the_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t    the_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n static const char *url_scheme_name(enum url_scheme scheme)\n {\n \tswitch (scheme) {\ndiff --git a/connect.h b/connect.h\nindex aa482a37fb..c4f6ea4b0a 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+struct string_list;\n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f1e64160fc..f7789e8456 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1375,40 +1375,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\tint advertise_sid = 0;\n-\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n\n-- \n2.54.0\n"},{"id":"548605","messageId":"20260718-ps-eric-work-rebase-v20-7-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 07/13] connect: make write_fetch_command_and_capabilities() more generic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:56Z","receivedAt":"2026-07-18T21:50:18Z","isPatch":true,"body":"Refactor write_fetch_command_and_capabilities(), enabling it to serve\nboth fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol Documentation/gitprotocol-v2.adoc, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20).\n\nRefactor the function signature to accept a command instead of the\nhardcoded \"fetch\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 8 ++++----\n connect.h    | 8 ++++++--\n fetch-pack.c | 4 ++--\n 3 files changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex c09947cc56..127ed4a2e6 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -700,16 +700,16 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options)\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \tint advertise_sid = 0;\n \n \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\ndiff --git a/connect.h b/connect.h\nindex c4f6ea4b0a..957e5fe2b9 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -35,7 +35,11 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       const char *error);\n \n struct string_list;\n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options);\n+/*\n+ * Write a protocol v2 command request, along with the capability\n+ * advertisements, into req_buf.\n+ */\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f7789e8456..3695059cd5 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1386,7 +1386,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2253,7 +2253,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n\n-- \n2.54.0\n"},{"id":"548606","messageId":"20260718-ps-eric-work-rebase-v20-8-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 08/13] fetch-pack: move fetch initialization","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:57Z","receivedAt":"2026-07-18T21:50:20Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized in\nFETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3695059cd5..922a9b2581 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1735,18 +1735,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n\n-- \n2.54.0\n"},{"id":"548607","messageId":"20260718-ps-eric-work-rebase-v20-9-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 09/13] protocol-caps: check object existence regardless of the attributes requested","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:58Z","receivedAt":"2026-07-18T21:50:23Z","isPatch":true,"body":"Currently, send_info() only checks for existence when the attribute\n'size' is also requested. Requesting a bare OID, without attributes only\nechoes back the OID.\n\nExtract the existence check to be done regardless of the number of\nattributes requested.\n\nWhile at it, introduce a wrapper called get_object_info() similar to\nodb_read_object_info() that returns OBJ_BAD on fail and adds\nOBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK flags.\nOBJECT_INFO_SKIP_FETCH_OBJECT is so a server with a partial clone\ndoesn't trigger fetching objects when it gets an object-info request\nwith an OID that is not available locally. A server should only report\nwhat it has locally.\n\nTighten the condition used to determine whether an object is\nrecognized. get_object_info() returns OBJ_BAD for unknown objects,\nbut OBJ_NONE (0) can also mean \"not found\". Change the check from '< 0'\nto '<= OBJ_NONE' to cover both as unrecognized.\n\nWith this patch, a bare OID has two possible responses:\n\n1. Recognized OID: the server answers with \"<OID>\"\n\n2. Unrecognized OID: the server answers with \"<OID> SP\"\n\nUpdate the object-info section in 'gitprotocol-v2.adoc':\n- Require full obj-oid explicitly.\n- Fix parentheses.\n- Define obj-size explicitly.\n- Make obj-size optional in obj-info and document the behavior\n  for unrecognized object IDs.\n- Describe the attr header as zero or more pkt-lines, one per attribute,\n  matching what the server implements. A request with no attributes gets\n  no header.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/gitprotocol-v2.adoc | 21 ++++++++-----\n protocol-caps.c                   | 45 ++++++++++++++++++++++++----\n t/t5701-git-serve.sh              | 63 +++++++++++++++++++++++++++++++++++++++\n 3 files changed, 115 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 2beb70595f..7bf62014c3 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -568,21 +568,26 @@ An `object-info` request takes the following arguments:\n \n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n-\tinformation for.\n+\tinformation for. They must be full OIDs.\n \n-The response of `object-info` is a list of the requested object ids\n-and associated requested information, each separated by a single space.\n+The response of `object-info` consists of one pkt-line per requested attribute,\n+echoing the attributes the server will report, followed by one pkt-line per\n+requested object id with its information, each field separated by a single\n+space.\n \n \toutput = info flush-pkt\n \n-\tinfo = PKT-LINE(attrs) LF)\n-\t\t*PKT-LINE(obj-info LF)\n-\n-\tattrs = attr | attrs SP attrs\n+\tinfo = *PKT-LINE(attr LF)\n+\t       *PKT-LINE(obj-info LF)\n \n \tattr = \"size\"\n \n-\tobj-info = obj-id SP obj-size\n+\tobj-size = 1*DIGIT\n+\n+\tobj-info = obj-id [SP [obj-size]]\n+\n+If the server does not recognize the OID, the response will be `<oid> SP`\n+regardless of the number of attributes requested.\n \n bundle-uri\n ~~~~~~~~~~\ndiff --git a/protocol-caps.c b/protocol-caps.c\nindex 8858ea4489..02261be14d 100644\n--- a/protocol-caps.c\n+++ b/protocol-caps.c\n@@ -30,6 +30,32 @@ static int parse_oid(const char *line, struct string_list *oid_str_list)\n \treturn 1;\n }\n \n+/*\n+ * odb_read_object_info_extended() wrapper. Similar to odb_read_object_info()\n+ * but uses the flags:\n+ *\n+ * - OBJECT_INFO_SKIP_FETCH_OBJECT so a server won't fetch an object when a\n+ *   object-info request asks for an OID that it doesn't have.\n+ *\n+ * - OBJECT_INFO_QUICK to avoid re-scanning packs when the object is not found.\n+ */\n+static enum object_type get_object_info(struct object_database *odb,\n+\t\t\t   const struct object_id *oid,\n+\t\t\t   size_t *sizep)\n+{\n+\tenum object_type type;\n+\tstruct object_info oi = OBJECT_INFO_INIT;\n+\n+\toi.typep = &type;\n+\toi.sizep = sizep;\n+\tif (odb_read_object_info_extended(odb, oid, &oi,\n+\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE |\n+\t\t\t\t\t  OBJECT_INFO_SKIP_FETCH_OBJECT |\n+\t\t\t\t\t  OBJECT_INFO_QUICK) < 0)\n+\t\treturn OBJ_BAD;\n+\treturn type;\n+}\n+\n /*\n  * Validates and send requested info back to the client. Any errors detected\n  * are returned as they are detected.\n@@ -62,15 +88,22 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \n \t\tstrbuf_addstr(&send_buffer, oid_str);\n \n+\t\t/*\n+\t\t * Check the existence of the object first.\n+\t\t * If an object is not recognized by the server append SP to\n+\t\t * the response.\n+\t\t */\n+\t\tif (get_object_info(r->objects, &oid, &object_size) <= OBJ_NONE) {\n+\t\t\tstrbuf_addstr(&send_buffer, \" \");\n+\t\t\tgoto write;\n+\t\t}\n+\n \t\tif (info->size) {\n-\t\t\tif (odb_read_object_info(r->objects, &oid, &object_size) < 0) {\n-\t\t\t\tstrbuf_addstr(&send_buffer, \" \");\n-\t\t\t} else {\n-\t\t\t\tstrbuf_addf(&send_buffer, \" %\"PRIuMAX,\n-\t\t\t\t\t    (uintmax_t)object_size);\n-\t\t\t}\n+\t\t\tstrbuf_addf(&send_buffer, \" %\"PRIuMAX,\n+\t\t\t\t    (uintmax_t)object_size);\n \t\t}\n \n+write:\n \t\tpacket_writer_write(writer, \"%s\", send_buffer.buf);\n \t\tstrbuf_reset(&send_buffer);\n \t}\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex d4c28bae39..cacff4456c 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -7,6 +7,8 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n \n+unknown_oid=$(printf \"test\" | git hash-object --stdin)\n+\n test_expect_success 'setup to generate files with expected content' '\n \tprintf \"agent=git/%s\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n \n@@ -364,6 +366,67 @@ test_expect_success 'basics of object-info' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'bare OID request' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\toid $(git rev-parse two:two.t)\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\t$(git rev-parse two:two.t)\n+\t0000\n+\tEOF\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'object-info with bare unrecognized OID' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\toid $unknown_oid\n+\t0000\n+\tEOF\n+\n+\tprintf \"%s \\n\" \"$unknown_oid\" >expect &&\n+\tprintf \"0000\\n\" >>expect &&\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'object-info with size for unrecognized OID' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\tsize\n+\toid $unknown_oid\n+\t0000\n+\tEOF\n+\n+\tprintf \"size\\n\" >expect &&\n+\tprintf \"%s \\n\" \"$unknown_oid\" >>expect &&\n+\tprintf \"0000\\n\" >>expect &&\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'test capability advertisement with uploadpack.advertiseBundleURIs' '\n \ttest_config uploadpack.advertiseBundleURIs true &&\n \n\n-- \n2.54.0\n"},{"id":"548608","messageId":"20260718-ps-eric-work-rebase-v20-10-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 10/13] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:49:59Z","receivedAt":"2026-07-18T21:50:26Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This allows a client\nto decide whether to query the server for object-info or fetch as a\nfallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n serve.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex 49a6e39b1d..2b07d922b3 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\t/* Currently only size is supported */\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n\n-- \n2.54.0\n"},{"id":"548609","messageId":"20260718-ps-eric-work-rebase-v20-11-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 11/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:50:00Z","receivedAt":"2026-07-18T21:50:27Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nIntroduce client-side support for the object-info capability.\n\nAdd its own function for object-info separate from existing fetch\ninfrastructure.\n\nCurrently, the client supports requesting a list of OIDs with the size\nattribute from a v2 server. If the server does not advertise this\nfeature (i.e., transfer.advertiseobjectinfo is set to false), the client\nreturns an error and exits.\n\nNote that:\n\n1. The entire request is written into req_buf before being sent to the\n   remote. This approach follows the pattern used in the\n   send_fetch_request() logic within 'fetch-pack.c'. Streaming the\n   request is not addressed in this patch.\n\n2. A new field 'unrecognized' has been added to object_info. This new\n   field is set at fetch_object_info() when the object is unrecognized\n   by the server.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Makefile             |   1 +\n fetch-object-info.c  | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h  |  22 ++++++++\n meson.build          |   1 +\n odb.h                |   6 +++\n transport-helper.c   |  10 ++++\n transport-internal.h |   8 +++\n transport.c          |  45 +++++++++++++++++\n transport.h          |   9 ++++\n 9 files changed, 240 insertions(+)\n\ndiff --git a/Makefile b/Makefile\nindex 1f3f099f5c..d450e0277e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1158,6 +1158,7 @@ LIB_OBJS += ewah/ewah_io.o\n LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fetch-pack.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..30475a1e87\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,138 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"odb.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\telse if (args->object_info_options->nr)\n+\t\tBUG(\"only size should be in object_info_options\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+static int parse_object_size(const char *s, size_t *res)\n+{\n+\tuintmax_t uim;\n+\n+\tif (!s[0] || s[strspn(s, \"0123456789\")])\n+\t\treturn -1;\n+\terrno = 0;\n+\tuim = strtoumax(s, NULL, 10);\n+\tif (errno || uim > SIZE_MAX)\n+\t\treturn -1;\n+\t*res = uim;\n+\treturn 0;\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"object-info requires protocol v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader,\n+\t\t\t\t\t\t  \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\t/*\n+\t\t\t * i is the number of supported options which currently\n+\t\t\t * is only size. No risk of overflow.\n+\t\t\t */\n+\t\t\tsize_index = (int)i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep =\n+\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t} else {\n+\t\t\tBUG(\"only size is supported\");\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0;\n+\t     packet_reader_read(reader) == PACKET_READ_NORMAL &&\n+\t     i < args->oids->nr;\n+\t     i++) {\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n+\n+\t\tif (strcmp(object_info_values.items[0].string,\n+\t\t\t   oid_to_hex(&args->oids->oid[i])))\n+\t\t\tdie(_(\"object-info: expected OID: %s, got %s\"),\n+\t\t\t    oid_to_hex(&args->oids->oid[i]),\n+\t\t\t    object_info_values.items[0].string);\n+\n+\t\t/*\n+\t\t * If the response is two elements but the second one is an\n+\t\t * empty string, that means that the OID is unrecognized by the\n+\t\t * server.\n+\t\t */\n+\t\tif (object_info_values.nr >= 2 &&\n+\t\t    !strcmp(object_info_values.items[1].string, \"\")) {\n+\t\t\tobject_info_data[i].unrecognized = 1;\n+\t\t\tstring_list_clear(&object_info_values, 0);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Because we filter the options to be only the supported by\n+\t\t * the server we expect the server to answer with the same\n+\t\t * number of attributes requested.\n+\t\t */\n+\t\tif (args->object_info_options->nr + 1 != object_info_values.nr)\n+\t\t\tdie(\"object-info: unexpected number of attributes: %s\",\n+\t\t\t    reader->line);\n+\n+\t\tif (size_index >= 0 &&\n+\t\t    parse_object_size(object_info_values.items[size_index + 1].string,\n+\t\t\t\t      object_info_data[i].sizep))\n+\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t    object_info_values.items[0].string,\n+\t\t\t    object_info_values.items[size_index + 1].string);\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..31aad98408\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+struct object_info;\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/meson.build b/meson.build\nindex 9434b56960..dfefcd3475 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -359,6 +359,7 @@ libgit_sources = [\n   'ewah/ewah_rlw.c',\n   'exec-cmd.c',\n   'fetch-negotiator.c',\n+  'fetch-object-info.c',\n   'fetch-pack.c',\n   'fmt-merge-msg.c',\n   'fsck.c',\ndiff --git a/odb.h b/odb.h\nindex 94754643d2..88a37febbf 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -339,6 +339,12 @@ struct object_info {\n \t * or multiple times in the same source.\n \t */\n \tstruct odb_source_info *source_infop;\n+\n+\t/*\n+\t * object-info protocol specific. Set by the protocol when the remote\n+\t * does not recognize the requested object.\n+\t */\n+\tunsigned int unrecognized:1;\n };\n \n /*\ndiff --git a/transport-helper.c b/transport-helper.c\nindex f195070788..623463dcea 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -784,6 +784,15 @@ static int fetch_refs(struct transport *transport,\n \treturn -1;\n }\n \n+static int fetch_object_info_helper(struct transport *transport)\n+{\n+\tget_helper(transport);\n+\tif (process_connect(transport, 0))\n+\t\treturn transport->vtable->fetch_object_info(transport);\n+\n+\tdie(_(\"object-info requires protocol v2\"));\n+}\n+\n struct push_update_ref_state {\n \tstruct ref *hint;\n \tstruct ref_push_report *report;\n@@ -1330,6 +1339,7 @@ static struct transport_vtable vtable = {\n \t.get_refs_list\t= get_refs_list,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs,\n+\t.fetch_object_info = fetch_object_info_helper,\n \t.push_refs\t= push_refs,\n \t.connect\t= connect_helper,\n \t.disconnect\t= release_helper\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 051f3ab0dc..60db0bedcd 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -45,6 +45,14 @@ struct transport_vtable {\n \t **/\n \tint (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);\n \n+\t/*\n+\t * Fetch object info (only size currently) from remote without\n+\t * downloading the objects.\n+\t *\n+\t * Uses object-info capability of v2 protocol.\n+\t */\n+\tint (*fetch_object_info)(struct transport *transport);\n+\n \t/**\n \t * Push the objects and refs. Send the necessary objects, and\n \t * then, for any refs where peer_ref is set and\ndiff --git a/transport.c b/transport.c\nindex fc144f0aed..9342680531 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -432,6 +433,48 @@ static int get_bundle_uri(struct transport *transport)\n \t\t\t\t     transport->bundles, stateless_rpc);\n }\n \n+static int fetch_object_info_via_pack(struct transport *transport)\n+{\n+\tint ret = 0;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct packet_reader reader;\n+\tstruct object_info_args args = { 0 };\n+\n+\targs.server_options = transport->server_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\tstring_list_sort(args.object_info_options);\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tdata->version = discover_version(&reader);\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tret = fetch_object_info(data->version, &args, &reader,\n+\t\t\t\tdata->options.object_info_data,\n+\t\t\t\ttransport->stateless_rpc, data->fd[1]);\n+\n+\tclose(data->fd[0]);\n+\tif (data->fd[1] >= 0)\n+\t\tclose(data->fd[1]);\n+\tif (finish_connect(data->conn))\n+\t\tret = -1;\n+\tdata->conn = NULL;\n+\n+\treturn ret;\n+}\n+\n+int transport_fetch_object_info(struct transport *transport)\n+{\n+\tif (!transport->vtable->fetch_object_info)\n+\t\tdie(_(\"remote does not support object-info\"));\n+\treturn transport->vtable->fetch_object_info(transport);\n+}\n+\n static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t       int nr_heads, struct ref **to_fetch)\n {\n@@ -1004,6 +1047,7 @@ static struct transport_vtable taken_over_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.disconnect\t= disconnect_git\n };\n@@ -1169,6 +1213,7 @@ static struct transport_vtable builtin_smart_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.connect\t= connect_git,\n \t.disconnect\t= disconnect_git\ndiff --git a/transport.h b/transport.h\nindex 7e5867cffa..a7869d18e0 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -55,6 +55,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n@@ -309,6 +313,11 @@ int transport_get_remote_bundle_uri(struct transport *transport);\n const struct git_hash_algo *transport_get_hash_algo(struct transport *transport);\n int transport_fetch_refs(struct transport *transport, struct ref *refs);\n \n+/*\n+ * Fetch the object info from remote\n+ */\n+int transport_fetch_object_info(struct transport *transport);\n+\n /*\n  * If this flag is set, unlocking will avoid to call non-async-signal-safe\n  * functions. This will necessarily leave behind some data structures which\n\n-- \n2.54.0\n"},{"id":"548610","messageId":"20260718-ps-eric-work-rebase-v20-12-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 12/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:50:01Z","receivedAt":"2026-07-18T21:50:29Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSince the info command in cat-file --batch-command prints object\ninfo for a given object, it is natural to add another command in\ncat-file --batch-command to print object info for a given object\nfrom a remote.\n\nAdd remote-object-info command to cat-file --batch-command.\n\nWhile info takes object ids one at a time, this creates overhead when\nmaking requests to a server. So remote-object-info instead can take\nmultiple object ids at once.\n\nThe cat-file --batch-command command is generally implemented in the\nfollowing manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, remote-object-info is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is remote-object-info:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode remote-object-info:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is remote-object-info:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, remote-object-info gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nPlaceholders in the format are validated against an allow-list of the\natoms the remote path supports: \"objectname\" and \"objectsize\".\nUnsupported atoms expand to an empty string, honoring how for-each-ref\nhandles known but inapplicable atoms.\nWithout this, atoms like %(objecttype) would mark data->info.typep and\nbecause the server only sends size, type_name() would later crash.\nAs extra safety, even outside of the remote path, initialize\nexpand_data's type to OBJ_BAD and handle type_name() returning NULL.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n[pablo: added the atom allow-list validation]\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  24 +-\n builtin/cat-file.c                     | 181 ++++++++-\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n t/meson.build                          |   1 +\n t/t1017-cat-file-remote-object-info.sh | 719 +++++++++++++++++++++++++++++++++\n 6 files changed, 930 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex 86b9181599..b9cd958e11 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -169,6 +169,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -301,7 +308,8 @@ one per line, and print information based on the command given. With\n `--batch-command`, the `info` command followed by an object will print\n information about the object the same way `--batch-check` would, and the\n `contents` command followed by an object prints contents in the same way\n-`--batch` would.\n+`--batch` would. The `remote-object-info` command followed by a remote and\n+objects IDs prints object info from the remote without downloading the objects.\n \n You can specify the information shown for each object by using a custom\n `<format>`. The `<format>` is copied literally to stdout for each\n@@ -330,7 +338,7 @@ newline. The available atoms are:\n `deltabase`::\n \tIf the object is stored as a delta on-disk, this expands to the\n \tfull hex representation of the delta base object name.\n-\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n+\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS` section\n \tbelow.\n \n `rest`::\n@@ -340,8 +348,14 @@ newline. The available atoms are:\n \tafter that first run of whitespace (i.e., the \"rest\" of the\n \tline) are output in place of the `%(rest)` atom.\n \n+The command `remote-object-info` only supports the `%(objectname)` and\n+`%(objectsize)` placeholders. See `CAVEATS` below for more information.\n+\n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which\n+use `%(objectname) %(objectsize)` because \"%(objecttype)\" is not supported yet.\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified,\n+so DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -438,6 +452,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that only `%(objectname)` and `%(objectsize)` are currently\n+supported by the `remote-object-info` command. Using any other placeholder in\n+the format string will return an empty string in its position.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 03afc44c5e..8994b04d15 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -29,6 +29,22 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/*\n+ * Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8 * 1024)\n+\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE \\\n+\t(MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -317,8 +333,19 @@ struct expand_data {\n \t * optimized out.\n \t */\n \tunsigned skip_object_info : 1;\n+\n+\t/*\n+\t * Flags about when an object info is being fetched from remote.\n+\t */\n+\tunsigned is_remote:1;\n+};\n+\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n+\n+static const char *remote_object_info_atoms[] = {\n+\t\"objectname\",\n+\t\"objectsize\",\n };\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -329,14 +356,31 @@ static int is_atom(const char *atom, const char *s, int slen)\n static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n+\tif (data->is_remote) {\n+\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n+\t\tfor (i = 0; i < allowed_nr; i++)\n+\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * On remote, skip unsupported atoms returning an empty sb,\n+\t\t * honoring how for-each-ref handles known but inapplicable\n+\t\t * atoms (e.g. %(tagger)).\n+\t\t */\n+\t\tif (i == allowed_nr)\n+\t\t\treturn 1;\n+\t}\n+\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n \t\t\tstrbuf_add_oid_hex(sb, &data->oid);\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n-\t\tif (data->mark_query)\n+\t\tif (data->mark_query) {\n \t\t\tdata->info.typep = &data->type;\n-\t\telse\n-\t\t\tstrbuf_addstr(sb, type_name(data->type));\n+\t\t} else {\n+\t\t\tconst char *t = type_name(data->type);\n+\t\t\tstrbuf_addstr(sb, t ? t : \"\");\n+\t\t}\n \t} else if (is_atom(\"objectsize\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.sizep = &data->size;\n@@ -636,6 +680,65 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(int argc,\n+\t\t\t   const char **argv,\n+\t\t\t   struct object_info **remote_object_info,\n+\t\t\t   struct oid_array *object_info_oids)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstruct transport *gtransport;\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid)) {\n+\t\t\tsize_t len = strlen(argv[i]);\n+\n+\t\t\tif (len < the_hash_algo->hexsz && len >= 4) {\n+\t\t\t\tsize_t j;\n+\t\t\t\tfor (j = 0; j < len; j++)\n+\t\t\t\t\tif (!isxdigit(argv[i][j]))\n+\t\t\t\t\t\tbreak;\n+\t\t\t\tif (j == len)\n+\t\t\t\t\tdie(_(\"remote-object-info does not support \"\n+\t\t\t\t\t      \"short oids, %d characters required\"),\n+\t\t\t\t\t    (int)the_hash_algo->hexsz);\n+\t\t\t}\n+\t\t\tdie(_(\"not a valid object name '%s'\"), argv[i]);\n+\t\t}\n+\t\toid_array_append(object_info_oids, &oid);\n+\t}\n+\n+\tif (!object_info_oids->nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\n+\tif (!gtransport->smart_options) {\n+\t\tretval = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n+\tgtransport->smart_options->object_info_oids = object_info_oids;\n+\n+\tstring_list_append(&object_info_options, \"size\");\n+\n+\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\tretval = transport_fetch_object_info(gtransport);\n+cleanup:\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -717,6 +820,73 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\tstruct object_info *remote_object_info = NULL;\n+\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tconst char *saved_format = opt->format;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command too long\"));\n+\t/*\n+\t * TODO: Use the default format once %(objecttype) is supported.\n+\t */\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"remote-object-info: failed to parse command line: %s\"),\n+\t\t    split_cmdline_strerror(count));\n+\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n+\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n+\t\t    MAX_ALLOWED_OBJ_LIMIT);\n+\n+\tif (get_remote_info(count, argv, &remote_object_info,\n+\t\t\t    &object_info_oids))\n+\t\tdie(_(\"failed to get object info from the remote: %s\"), argv[0]);\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\n+\t\tif (remote_object_info[i].unrecognized) {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid),\n+\t\t\t\t\t     &data->oid, \"missing\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tdata->is_remote = 1;\n+\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t\tdata->is_remote = 0;\n+\t\t} else {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\topt->format = saved_format;\n+\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+\toid_array_clear(&object_info_oids);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -747,9 +917,10 @@ static const struct parse_cmd {\n \tunsigned takes_args;\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1 },\n-\t{ \"info\", parse_cmd_info, 1 },\n \t{ \"flush\", NULL, 0 },\n+\t{ \"info\", parse_cmd_info, 1 },\n \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex 6453b1d6fa..07f019a0f6 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1694,3 +1694,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)\n \n \treturn &transaction->base;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+}\ndiff --git a/odb.h b/odb.h\nindex 88a37febbf..92fa414e2c 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -623,4 +623,7 @@ void parse_alternates(const char *string,\n \t\t      const char *relative_base,\n \t\t      struct strvec *out);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* ODB_H */\ndiff --git a/t/meson.build b/t/meson.build\nindex 8ae6ab6c5f..10241e3dcc 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -171,6 +171,7 @@ integration_tests = [\n   't1014-read-tree-confusing.sh',\n   't1015-read-index-unmerged.sh',\n   't1016-compatObjectFormat.sh',\n+  't1017-cat-file-remote-object-info.sh',\n   't1020-subdirectory.sh',\n   't1022-read-tree-partial-clone.sh',\n   't1050-large.sh',\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..edc20394d8\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,719 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n+\n+unstored_content=\"Hello Git\"\n+unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not change the default format of info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tlocal_content=\"local object\" &&\n+\t\tlocal_oid=$(echo_without_newline \"$local_content\" | git hash-object -w --stdin) &&\n+\t\tlocal_size=$(strlen \"$local_content\") &&\n+\n+\t\techo \"$local_oid blob $local_size\" >expect &&\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$local_oid blob $local_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tinfo $local_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tinfo $local_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not support short oids' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_short_oid\n+\t\tEOF\n+\t\ttest_grep \"does not support short oids\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not die on missing oid like info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tgit cat-file --batch-command >local <<-EOF &&\n+\t\tinfo $unstored_oid\n+\t\tEOF\n+\t\tgit cat-file --batch-command >remote <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $unstored_oid\n+\t\tEOF\n+\t\ttest_cmp local remote\n+\t)\n+'\n+\n+# This tests depends on %(objecttype) not being supported yet, once supported\n+# it needs to be updated.\n+test_expect_success 'unsupported placeholder on remote returns empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"\" >expect &&\n+\t\tgit cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\techo \"$hello_oid \" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize:disk)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(deltabase)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n\n-- \n2.54.0\n"},{"id":"548611","messageId":"20260718-ps-eric-work-rebase-v20-13-0c13962ac532@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v20 13/13] cat-file: make remote-object-info allow-list adapt to the server","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-18T21:50:02Z","receivedAt":"2026-07-18T21:50:31Z","isPatch":true,"body":"The static allow-list in expand_atom() is hardcoded to allow only\n\"objectname\" and \"objectsize\" for remote queries. This works because,\nup to this point, servers will either support object-info with name\nand size or they do not support them at all.\n\nAs object-info gains new capabilities, we cannot expect different\nservers with different Git versions to have the same object-info\ncapabilities. Therefore, the client needs to adapt its allow-list to\nwhat the server advertises.\n\nThe client now:\n\n1. Requests the protocol option that the placeholder refers to (i.e.\n   \"size\" for \"%(objectsize)\").\n\n2. Drops any requested option that the server does not advertise in\n   fetch_object_info().\n\n3. Maps the remaining advertised options back to their placeholders and\n   populates remote_allowed_atoms.\n\n4. Uses remote_allowed_atoms in expand_atom(), preserving the previous\n   behavior for supported placeholders.\n\nFor example, if the client requests \"%(objectsize) %(objecttype)\" and\nthe server only supports 'size', then the client only requests 'size'.\nThe server returns the size (i.e \"42\") \"%(objectsize)\" is expanded\nnormally while \"%(objecttype)\" expands to an empty string:\n\n\t\"42 \"\n\nNote that the empty string expansion is only for known but unsupported\nplaceholders. \"%(objectcolor)\" which doesn't exist would die().\n\nThis honors what for-each-ref does for known but inapplicable atoms\n(placeholders).\n\nMove object_info_options out of get_remote_info() so the caller which\nhas data can select what options will be requested instead of requesting\nalways size.\n\nMove batch_object_write() out so output is always produced.\nIf there are no supported attributes, the output is a blank line.\n\nInclude \"type\" in the object_info_options even though the client does\nnot yet know how to parse the server's \"type\" capability.\n\nAs a result, \"type\" is always filtered out, allowing the tests to verify\nthat known but unsupported placeholders expand to an empty string.\n\nSince the filter removes options by swapping with the last element,\nthe list is no longer kept sorted. Drop the pre-sort in\nfetch_object_info_via_pack() and use the unsorted string_list lookup\nfor the response header. This has no effect in performance as the list\ncan only be two entries long ('size' and 'type').\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c                     | 94 ++++++++++++++++++++++------------\n fetch-object-info.c                    | 20 +++++++-\n fetch-object-info.h                    |  3 ++\n t/t1017-cat-file-remote-object-info.sh | 28 ++++++++++\n transport.c                            |  1 -\n 5 files changed, 111 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 8994b04d15..2007b0d4bb 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -338,15 +338,18 @@ struct expand_data {\n \t * Flags about when an object info is being fetched from remote.\n \t */\n \tunsigned is_remote:1;\n-};\n-\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n \n-static const char *remote_object_info_atoms[] = {\n-\t\"objectname\",\n-\t\"objectsize\",\n+\t/*\n+\t * List of atoms (i.e. \"objectsize\") that the server supports. Built\n+\t * from the server's object-info advertised capabilities.\n+\t */\n+\tstruct string_list remote_allowed_atoms;\n };\n \n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, \\\n+\t\t\t    .type = OBJ_BAD, \\\n+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n+\n static int is_atom(const char *atom, const char *s, int slen)\n {\n \tint alen = strlen(atom);\n@@ -357,17 +360,12 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n \tif (data->is_remote) {\n-\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n-\t\tfor (i = 0; i < allowed_nr; i++)\n-\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\tsize_t i;\n+\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string,\n+\t\t\t\t    atom, len))\n \t\t\t\tbreak;\n-\n-\t\t/*\n-\t\t * On remote, skip unsupported atoms returning an empty sb,\n-\t\t * honoring how for-each-ref handles known but inapplicable\n-\t\t * atoms (e.g. %(tagger)).\n-\t\t */\n-\t\tif (i == allowed_nr)\n+\t\tif (i == data->remote_allowed_atoms.nr)\n \t\t\treturn 1;\n \t}\n \n@@ -683,12 +681,12 @@ static void batch_one_object(const char *obj_name,\n static int get_remote_info(int argc,\n \t\t\t   const char **argv,\n \t\t\t   struct object_info **remote_object_info,\n-\t\t\t   struct oid_array *object_info_oids)\n+\t\t\t   struct oid_array *object_info_oids,\n+\t\t\t   struct string_list *object_info_options)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n \tstruct object_id oid;\n-\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tstruct transport *gtransport;\n \n \tremote = remote_get(argv[0]);\n@@ -728,13 +726,10 @@ static int get_remote_info(int argc,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\tstring_list_append(&object_info_options, \"size\");\n-\n-\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tgtransport->smart_options->object_info_options = object_info_options;\n \tgtransport->smart_options->object_info_data = *remote_object_info;\n \tretval = transport_fetch_object_info(gtransport);\n cleanup:\n-\tstring_list_clear(&object_info_options, 0);\n \ttransport_disconnect(gtransport);\n \treturn retval;\n }\n@@ -820,6 +815,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+struct protocol_placeholder_entry {\n+\tconst char *option;\n+\tconst char *atom;\n+};\n+\n+static const struct protocol_placeholder_entry remote_atom_map[] = {\n+\t{\"size\", \"objectsize\"},\n+\t{\"type\", \"objecttype\"},\n+\t/*\n+\t * Add new protocol options here. Even if the server doesn't support\n+\t * them the allow_list will drop them if the server doesn't advertise\n+\t * them.\n+\t */\n+};\n+\n static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n@@ -829,6 +839,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct object_info *remote_object_info = NULL;\n \tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tconst char *saved_format = opt->format;\n \n \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n@@ -848,10 +859,22 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n+\tif (data->info.sizep)\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\tif (data->info.typep)\n+\t\tstring_list_append(&object_info_options, \"type\");\n+\n \tif (get_remote_info(count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids))\n+\t\t\t    &object_info_oids, &object_info_options))\n \t\tdie(_(\"failed to get object info from the remote: %s\"), argv[0]);\n \n+\tstring_list_clear(&data->remote_allowed_atoms, 0);\n+\tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n+\tfor (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n+\t\tif (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n+\t\t\tstring_list_append(&data->remote_allowed_atoms,\n+\t\t\t\t\t   remote_atom_map[i].atom);\n+\n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n \t\tdata->oid = object_info_oids.oid[i];\n@@ -862,25 +885,29 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\tcontinue;\n \t\t}\n \n+\t\t/*\n+\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t * information from server without downloading them.\n+\t\t */\n \t\tif (remote_object_info[i].sizep) {\n-\t\t\t/*\n-\t\t\t * When reaching here, it means remote-object-info can retrieve\n-\t\t\t * information from server without downloading them.\n-\t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n-\t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tdata->is_remote = 1;\n-\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n-\t\t\tdata->is_remote = 0;\n-\t\t} else {\n-\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n \t\t}\n+\n+\t\tif (remote_object_info[i].typep) {\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tdata->is_remote = 1;\n+\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n \topt->format = saved_format;\n \n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n+\tstring_list_clear(&object_info_options, 0);\n \tfree(line_to_split);\n \tfree(argv);\n \tfree(remote_object_info);\n@@ -1200,6 +1227,7 @@ static int batch_objects(struct batch_options *opt)\n  cleanup:\n \tstrbuf_release(&input);\n \tstrbuf_release(&output);\n+\tstring_list_clear(&data.remote_allowed_atoms, 0);\n \tcfg->warn_on_object_refname_ambiguity = save_warning;\n \treturn retval;\n }\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 30475a1e87..ba7e179c44 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -55,6 +55,24 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \tcase protocol_v2:\n \t\tif (!server_supports_v2(\"object-info\"))\n \t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\t/*\n+\t\t * When removing an element from the list it gets swapped by the\n+\t\t * last element, iterate backwards to prevent elements skipping\n+\t\t * evaluation.\n+\t\t *\n+\t\t * object_info_options->nr can be safely casted without overflow\n+\t\t * because the number of options is a small known number (the\n+\t\t * supported placeholders which currently are size and type).\n+\t\t */\n+\t\tfor (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)\n+\t\t\tif (!server_supports_feature(\"object-info\",\n+\t\t\t\t\t\t     args->object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(args->object_info_options, i, 0);\n+\n+\t\t/*\n+\t\t * Even if no options are left, we still send the oid so we get\n+\t\t * at least an existence check.\n+\t\t */\n \t\tsend_object_info_request(fd_out, args);\n \t\tbreak;\n \tcase protocol_v1:\n@@ -71,7 +89,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\t\treturn -1;\n \t\t}\n \n-\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\tif (!unsorted_string_list_has_string(args->object_info_options, reader->line))\n \t\t\treturn -1;\n \n \t\tif (!strcmp(reader->line, \"size\")) {\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 31aad98408..269cebb3f7 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -14,6 +14,9 @@ struct object_info;\n /*\n  * Sends git-cat-file object-info command into the request buf and read the\n  * results from packets.\n+ *\n+ * Modifies args->object_info_options, on return it contains only the supported\n+ * options by the server.\n  */\n int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n \t\t      struct packet_reader *reader, struct object_info *object_info_data,\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex edc20394d8..116862f9d0 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -271,6 +271,34 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '\n \t)\n '\n \n+test_expect_success 'requesting only objectname echoes back' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo $hello_oid >expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'objectname goes through existence check' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$unstored_oid missing\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $unstored_oid\n+\t\tEOF\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n # Test --batch-command remote-object-info with 'git://' and\n # transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\ndiff --git a/transport.c b/transport.c\nindex 9342680531..f0a6a45547 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -443,7 +443,6 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \targs.server_options = transport->server_options;\n \targs.oids = transport->smart_options->object_info_oids;\n \targs.object_info_options = transport->smart_options->object_info_options;\n-\tstring_list_sort(args.object_info_options);\n \n \tconnect_setup(transport, 0);\n \tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n\n-- \n2.54.0\n"},{"id":"548738","messageId":"CAOLa=ZT77L39-jSZxgrvVFwdY6_rapBmKv8gCxr7QpdkCsEzXg@mail.gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"Re: [PATCH GSoC v20 00/13] cat-file: add remote-object-info to batch-command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-07-21T20:12:12Z","receivedAt":"2026-07-21T20:12:15Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n[snip]\n\n> Changes in v20:\n> - Moved the prep patch that fixes hash_algo's type to be before\n>   write_fetch_command_and_capabilities() being moved to 'connect.c'\n> - Reverted git-cat-file.adoc documentation comments about CAVEATS\n> - Fixed style for EXPAND_DATA_INIT\n> - Added more context for comman line die()\n>\n\n[snip]\n\n> Range-diff versus v19:\n>\n>  1:  6df5bcb3bc =  1:  31b02f10b5 transport-helper: fix memory leak of helper on disconnect\n>  2:  141d85a76f =  2:  7d62e0586f cat-file: declare loop counter inside for()\n>  3:  87457a1fe3 =  3:  082d593fb7 t1006: extract helper functions into new 'lib-cat-file.sh'\n>  4:  c26d378931 =  4:  8646b9d7cf fetch-pack: drop the static advertise_sid variable\n>  -:  ---------- >  5:  5531a3c399 fetch-pack: use unsigned int for hash_algo variable\n>  5:  ee16e17228 !  6:  92b4122642 fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n>     @@ connect.c: int server_supports(const char *feature)\n>      +\t}\n>      +\n>      +\tif (server_feature_v2(\"object-format\", &hash_name)) {\n>     -+\t\tint hash_algo = hash_algo_by_name(hash_name);\n>     ++\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n>      +\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>      +\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n>      +\t\t\t    the_hash_algo->name, hash_name);\n>     @@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n>      -\t}\n>      -\n>      -\tif (server_feature_v2(\"object-format\", &hash_name)) {\n>     --\t\tint hash_algo = hash_algo_by_name(hash_name);\n>     +-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n>      -\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>      -\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n>      -\t\t\t    the_hash_algo->name, hash_name);\n>  6:  bb915f57ad <  -:  ---------- connect: use unsigned int for hash_algo_by_name() calls\n>  7:  b3b06b0cae =  7:  b954a5994a connect: make write_fetch_command_and_capabilities() more generic\n>  8:  79a87c7011 =  8:  627242a7a1 fetch-pack: move fetch initialization\n>  9:  37c3f93975 =  9:  0578594733 protocol-caps: check object existence regardless of the attributes requested\n> 10:  c5062ecaf2 = 10:  816bfa9162 serve: advertise object-info feature\n> 11:  22d72168bc = 11:  2323f45cb2 transport: add client support for object-info\n> 12:  2cf3b24a35 ! 12:  a39975766b cat-file: add remote-object-info to batch-command\n>     @@ Documentation/git-cat-file.adoc: one per line, and print information based on th\n>       You can specify the information shown for each object by using a custom\n>       `<format>`. The `<format>` is copied literally to stdout for each\n>      @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n>     - \treports).\n>     -\n>     - `objectsize:disk`::\n>     --\tThe size, in bytes, that the object takes up on disk. See the\n>     --\tnote about on-disk sizes in the `CAVEATS` section below.\n>     -+\tThe size, in bytes, that the object takes up on disk.\n>     -\n>       `deltabase`::\n>       \tIf the object is stored as a delta on-disk, this expands to the\n>       \tfull hex representation of the delta base object name.\n>      -\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n>     --\tbelow.\n>     -+\tOtherwise, expands to the null OID (all zeroes).\n>     ++\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS` section\n>     + \tbelow.\n>\n\nThis chagne is still unnecessary, no?\n\n>       `rest`::\n>     - \tIf this atom is used in the output string, input lines are split\n>      @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n>       \tafter that first run of whitespace (i.e., the \"rest\" of the\n>       \tline) are output in place of the `%(rest)` atom.\n>     @@ builtin/cat-file.c: struct expand_data {\n>      +\t */\n>      +\tunsigned is_remote:1;\n>      +};\n>     ++\n>      +#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n>      +\n>      +static const char *remote_object_info_atoms[] = {\n>     @@ builtin/cat-file.c: static void parse_cmd_mailmap(struct batch_options *opt UNUS\n>      +\tline_to_split = xstrdup(line);\n>      +\tcount = split_cmdline(line_to_split, &argv);\n>      +\tif (count < 0)\n>     -+\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n>     ++\t\tdie(_(\"remote-object-info: failed to parse command line: %s\"),\n>     ++\t\t    split_cmdline_strerror(count));\n>      +\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n>      +\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n>      +\t\t    MAX_ALLOWED_OBJ_LIMIT);\n> 13:  ebdfc22fb7 ! 13:  70a11d2aea cat-file: make remote-object-info allow-list adapt to the server\n>     @@ builtin/cat-file.c: struct expand_data {\n>       \t */\n>       \tunsigned is_remote:1;\n>      -};\n>     +-\n>      -#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n>\n>      -static const char *remote_object_info_atoms[] = {\n>      -\t\"objectname\",\n>      -\t\"objectsize\",\n>     ++\t/*\n>     ++\t * List of atoms (i.e. \"objectsize\") that the server supports. Built\n>     ++\t * from the server's object-info advertised capabilities.\n>     ++\t */\n>      +\tstruct string_list remote_allowed_atoms;\n>       };\n>     -+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n>     -+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n>\n>     ++#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, \\\n>     ++\t\t\t    .type = OBJ_BAD, \\\n>     ++\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n>     ++\n\nI guess this is output of running the style check script (which I did\nrecommend, but I should've mentioned that you should take it with a\npinch of salt, it doesn't always provide the best solutions.)\n\n>       static int is_atom(const char *atom, const char *s, int slen)\n>       {\n>     + \tint alen = strlen(atom);\n>      @@ builtin/cat-file.c: static int expand_atom(struct strbuf *sb, const char *atom, int len,\n>       \t\t       struct expand_data *data)\n>       {\n>     @@ builtin/cat-file.c: static int expand_atom(struct strbuf *sb, const char *atom,\n>      -\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n>      +\t\tsize_t i;\n>      +\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n>     -+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n>     ++\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string,\n>     ++\t\t\t\t    atom, len))\n>       \t\t\t\tbreak;\n>      -\n>      -\t\t/*\n>\n> ---\n> base-commit: 44de1520f08d1dfebc3ab2d9f644208eaa5ac925\n\nI reviewed v18 last, but the diff here looks good to me. Thanks!\n"},{"id":"548776","messageId":"DK549LIZS8BS.3PL9CQQIR2LAA@gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZT77L39-jSZxgrvVFwdY6_rapBmKv8gCxr7QpdkCsEzXg@mail.gmail.com","subject":"Re: [PATCH GSoC v20 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-22T12:59:15Z","receivedAt":"2026-07-22T12:59:18Z","isPatch":true,"body":"On Tue Jul 21, 2026 at 10:12 PM CEST, Karthik Nayak wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> [snip]\n>\n>> Changes in v20:\n>> - Moved the prep patch that fixes hash_algo's type to be before\n>>   write_fetch_command_and_capabilities() being moved to 'connect.c'\n>> - Reverted git-cat-file.adoc documentation comments about CAVEATS\n>> - Fixed style for EXPAND_DATA_INIT\n>> - Added more context for comman line die()\n>>\n>\n> [snip]\n>\n>> Range-diff versus v19:\n>>\n>>  1:  6df5bcb3bc =  1:  31b02f10b5 transport-helper: fix memory leak of helper on disconnect\n>>  2:  141d85a76f =  2:  7d62e0586f cat-file: declare loop counter inside for()\n>>  3:  87457a1fe3 =  3:  082d593fb7 t1006: extract helper functions into new 'lib-cat-file.sh'\n>>  4:  c26d378931 =  4:  8646b9d7cf fetch-pack: drop the static advertise_sid variable\n>>  -:  ---------- >  5:  5531a3c399 fetch-pack: use unsigned int for hash_algo variable\n>>  5:  ee16e17228 !  6:  92b4122642 fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n>>     @@ connect.c: int server_supports(const char *feature)\n>>      +\t}\n>>      +\n>>      +\tif (server_feature_v2(\"object-format\", &hash_name)) {\n>>     -+\t\tint hash_algo = hash_algo_by_name(hash_name);\n>>     ++\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n>>      +\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>>      +\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n>>      +\t\t\t    the_hash_algo->name, hash_name);\n>>     @@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n>>      -\t}\n>>      -\n>>      -\tif (server_feature_v2(\"object-format\", &hash_name)) {\n>>     --\t\tint hash_algo = hash_algo_by_name(hash_name);\n>>     +-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n>>      -\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>>      -\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n>>      -\t\t\t    the_hash_algo->name, hash_name);\n>>  6:  bb915f57ad <  -:  ---------- connect: use unsigned int for hash_algo_by_name() calls\n>>  7:  b3b06b0cae =  7:  b954a5994a connect: make write_fetch_command_and_capabilities() more generic\n>>  8:  79a87c7011 =  8:  627242a7a1 fetch-pack: move fetch initialization\n>>  9:  37c3f93975 =  9:  0578594733 protocol-caps: check object existence regardless of the attributes requested\n>> 10:  c5062ecaf2 = 10:  816bfa9162 serve: advertise object-info feature\n>> 11:  22d72168bc = 11:  2323f45cb2 transport: add client support for object-info\n>> 12:  2cf3b24a35 ! 12:  a39975766b cat-file: add remote-object-info to batch-command\n>>     @@ Documentation/git-cat-file.adoc: one per line, and print information based on th\n>>       You can specify the information shown for each object by using a custom\n>>       `<format>`. The `<format>` is copied literally to stdout for each\n>>      @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n>>     - \treports).\n>>     -\n>>     - `objectsize:disk`::\n>>     --\tThe size, in bytes, that the object takes up on disk. See the\n>>     --\tnote about on-disk sizes in the `CAVEATS` section below.\n>>     -+\tThe size, in bytes, that the object takes up on disk.\n>>     -\n>>       `deltabase`::\n>>       \tIf the object is stored as a delta on-disk, this expands to the\n>>       \tfull hex representation of the delta base object name.\n>>      -\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n>>     --\tbelow.\n>>     -+\tOtherwise, expands to the null OID (all zeroes).\n>>     ++\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS` section\n>>     + \tbelow.\n>>\n>\n> This chagne is still unnecessary, no?\n\nI guess it is unnecessary. I added it because on top, at objectsize:disk\nit says \"`CAVEATS` section\". So I tried to have it consistently.\n\nIs it worth a reroll?\n\n>\n>>       `rest`::\n>>     - \tIf this atom is used in the output string, input lines are split\n>>      @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n>>       \tafter that first run of whitespace (i.e., the \"rest\" of the\n>>       \tline) are output in place of the `%(rest)` atom.\n>>     @@ builtin/cat-file.c: struct expand_data {\n>>      +\t */\n>>      +\tunsigned is_remote:1;\n>>      +};\n>>     ++\n>>      +#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n>>      +\n>>      +static const char *remote_object_info_atoms[] = {\n>>     @@ builtin/cat-file.c: static void parse_cmd_mailmap(struct batch_options *opt UNUS\n>>      +\tline_to_split = xstrdup(line);\n>>      +\tcount = split_cmdline(line_to_split, &argv);\n>>      +\tif (count < 0)\n>>     -+\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n>>     ++\t\tdie(_(\"remote-object-info: failed to parse command line: %s\"),\n>>     ++\t\t    split_cmdline_strerror(count));\n>>      +\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n>>      +\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n>>      +\t\t    MAX_ALLOWED_OBJ_LIMIT);\n>> 13:  ebdfc22fb7 ! 13:  70a11d2aea cat-file: make remote-object-info allow-list adapt to the server\n>>     @@ builtin/cat-file.c: struct expand_data {\n>>       \t */\n>>       \tunsigned is_remote:1;\n>>      -};\n>>     +-\n>>      -#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n>>\n>>      -static const char *remote_object_info_atoms[] = {\n>>      -\t\"objectname\",\n>>      -\t\"objectsize\",\n>>     ++\t/*\n>>     ++\t * List of atoms (i.e. \"objectsize\") that the server supports. Built\n>>     ++\t * from the server's object-info advertised capabilities.\n>>     ++\t */\n>>      +\tstruct string_list remote_allowed_atoms;\n>>       };\n>>     -+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n>>     -+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n>>\n>>     ++#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, \\\n>>     ++\t\t\t    .type = OBJ_BAD, \\\n>>     ++\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n>>     ++\n>\n> I guess this is output of running the style check script (which I did\n> recommend, but I should've mentioned that you should take it with a\n> pinch of salt, it doesn't always provide the best solutions.)\n\nIt comes in part from Junio's suggestion [1]. Although I don't think that\nit will receive a lot of new fields, I thought that it would leave the\nmacro clean for that.\n\n>\n>>       static int is_atom(const char *atom, const char *s, int slen)\n>>       {\n>>     + \tint alen = strlen(atom);\n>>      @@ builtin/cat-file.c: static int expand_atom(struct strbuf *sb, const char *atom, int len,\n>>       \t\t       struct expand_data *data)\n>>       {\n>>     @@ builtin/cat-file.c: static int expand_atom(struct strbuf *sb, const char *atom,\n>>      -\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n>>      +\t\tsize_t i;\n>>      +\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n>>     -+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n>>     ++\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string,\n>>     ++\t\t\t\t    atom, len))\n>>       \t\t\t\tbreak;\n>>      -\n>>      -\t\t/*\n>>\n>> ---\n>> base-commit: 44de1520f08d1dfebc3ab2d9f644208eaa5ac925\n>\n> I reviewed v18 last, but the diff here looks good to me. Thanks!\n\n[1]: https://lore.kernel.org/git/xmqqcxwonnkx.fsf@gitster.g/\n\nThanks,\nPablo\n"},{"id":"548787","messageId":"CAOLa=ZS8J4t12ab1=3-LRYNuZOwqSHG861iYm97JjF3mGprvJA@mail.gmail.com","threadId":"61695","inReplyTo":"DK549LIZS8BS.3PL9CQQIR2LAA@gmail.com","subject":"Re: [PATCH GSoC v20 00/13] cat-file: add remote-object-info to batch-command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-07-22T16:13:04Z","receivedAt":"2026-07-22T16:13:06Z","isPatch":true,"body":"\"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n\n> On Tue Jul 21, 2026 at 10:12 PM CEST, Karthik Nayak wrote:\n>> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>>\n>> [snip]\n>>\n>>> Changes in v20:\n>>> - Moved the prep patch that fixes hash_algo's type to be before\n>>>   write_fetch_command_and_capabilities() being moved to 'connect.c'\n>>> - Reverted git-cat-file.adoc documentation comments about CAVEATS\n>>> - Fixed style for EXPAND_DATA_INIT\n>>> - Added more context for comman line die()\n>>>\n>>\n>> [snip]\n>>\n>>> Range-diff versus v19:\n>>>\n>>>  1:  6df5bcb3bc =  1:  31b02f10b5 transport-helper: fix memory leak of helper on disconnect\n>>>  2:  141d85a76f =  2:  7d62e0586f cat-file: declare loop counter inside for()\n>>>  3:  87457a1fe3 =  3:  082d593fb7 t1006: extract helper functions into new 'lib-cat-file.sh'\n>>>  4:  c26d378931 =  4:  8646b9d7cf fetch-pack: drop the static advertise_sid variable\n>>>  -:  ---------- >  5:  5531a3c399 fetch-pack: use unsigned int for hash_algo variable\n>>>  5:  ee16e17228 !  6:  92b4122642 fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n>>>     @@ connect.c: int server_supports(const char *feature)\n>>>      +\t}\n>>>      +\n>>>      +\tif (server_feature_v2(\"object-format\", &hash_name)) {\n>>>     -+\t\tint hash_algo = hash_algo_by_name(hash_name);\n>>>     ++\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n>>>      +\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>>>      +\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n>>>      +\t\t\t    the_hash_algo->name, hash_name);\n>>>     @@ fetch-pack.c: static int add_haves(struct fetch_negotiator *negotiator,\n>>>      -\t}\n>>>      -\n>>>      -\tif (server_feature_v2(\"object-format\", &hash_name)) {\n>>>     --\t\tint hash_algo = hash_algo_by_name(hash_name);\n>>>     +-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n>>>      -\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n>>>      -\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n>>>      -\t\t\t    the_hash_algo->name, hash_name);\n>>>  6:  bb915f57ad <  -:  ---------- connect: use unsigned int for hash_algo_by_name() calls\n>>>  7:  b3b06b0cae =  7:  b954a5994a connect: make write_fetch_command_and_capabilities() more generic\n>>>  8:  79a87c7011 =  8:  627242a7a1 fetch-pack: move fetch initialization\n>>>  9:  37c3f93975 =  9:  0578594733 protocol-caps: check object existence regardless of the attributes requested\n>>> 10:  c5062ecaf2 = 10:  816bfa9162 serve: advertise object-info feature\n>>> 11:  22d72168bc = 11:  2323f45cb2 transport: add client support for object-info\n>>> 12:  2cf3b24a35 ! 12:  a39975766b cat-file: add remote-object-info to batch-command\n>>>     @@ Documentation/git-cat-file.adoc: one per line, and print information based on th\n>>>       You can specify the information shown for each object by using a custom\n>>>       `<format>`. The `<format>` is copied literally to stdout for each\n>>>      @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n>>>     - \treports).\n>>>     -\n>>>     - `objectsize:disk`::\n>>>     --\tThe size, in bytes, that the object takes up on disk. See the\n>>>     --\tnote about on-disk sizes in the `CAVEATS` section below.\n>>>     -+\tThe size, in bytes, that the object takes up on disk.\n>>>     -\n>>>       `deltabase`::\n>>>       \tIf the object is stored as a delta on-disk, this expands to the\n>>>       \tfull hex representation of the delta base object name.\n>>>      -\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n>>>     --\tbelow.\n>>>     -+\tOtherwise, expands to the null OID (all zeroes).\n>>>     ++\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS` section\n>>>     + \tbelow.\n>>>\n>>\n>> This chagne is still unnecessary, no?\n>\n> I guess it is unnecessary. I added it because on top, at objectsize:disk\n> it says \"`CAVEATS` section\". So I tried to have it consistently.\n>\n> Is it worth a reroll?\n>\n\nNo I don't think a re-roll is necessary. But something to avoid next\ntime :)\n\n>>\n>>>       `rest`::\n>>>     - \tIf this atom is used in the output string, input lines are split\n>>>      @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n>>>       \tafter that first run of whitespace (i.e., the \"rest\" of the\n>>>       \tline) are output in place of the `%(rest)` atom.\n>>>     @@ builtin/cat-file.c: struct expand_data {\n>>>      +\t */\n>>>      +\tunsigned is_remote:1;\n>>>      +};\n>>>     ++\n>>>      +#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n>>>      +\n>>>      +static const char *remote_object_info_atoms[] = {\n>>>     @@ builtin/cat-file.c: static void parse_cmd_mailmap(struct batch_options *opt UNUS\n>>>      +\tline_to_split = xstrdup(line);\n>>>      +\tcount = split_cmdline(line_to_split, &argv);\n>>>      +\tif (count < 0)\n>>>     -+\t\tdie(_(\"remote-object-info: %s\"), split_cmdline_strerror(count));\n>>>     ++\t\tdie(_(\"remote-object-info: failed to parse command line: %s\"),\n>>>     ++\t\t    split_cmdline_strerror(count));\n>>>      +\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n>>>      +\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n>>>      +\t\t    MAX_ALLOWED_OBJ_LIMIT);\n>>> 13:  ebdfc22fb7 ! 13:  70a11d2aea cat-file: make remote-object-info allow-list adapt to the server\n>>>     @@ builtin/cat-file.c: struct expand_data {\n>>>       \t */\n>>>       \tunsigned is_remote:1;\n>>>      -};\n>>>     +-\n>>>      -#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n>>>\n>>>      -static const char *remote_object_info_atoms[] = {\n>>>      -\t\"objectname\",\n>>>      -\t\"objectsize\",\n>>>     ++\t/*\n>>>     ++\t * List of atoms (i.e. \"objectsize\") that the server supports. Built\n>>>     ++\t * from the server's object-info advertised capabilities.\n>>>     ++\t */\n>>>      +\tstruct string_list remote_allowed_atoms;\n>>>       };\n>>>     -+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n>>>     -+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n>>>\n>>>     ++#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, \\\n>>>     ++\t\t\t    .type = OBJ_BAD, \\\n>>>     ++\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n>>>     ++\n>>\n>> I guess this is output of running the style check script (which I did\n>> recommend, but I should've mentioned that you should take it with a\n>> pinch of salt, it doesn't always provide the best solutions.)\n>\n> It comes in part from Junio's suggestion [1]. Although I don't think that\n> it will receive a lot of new fields, I thought that it would leave the\n> macro clean for that.\n>\n\nThen wouldn't something like this be better?\n\n     #define EXPAND_DATA_INIT  {  \\\n             .mode = S_IFINVALID, \\\n             .type = OBJ_BAD,     \\\n             .remote_allowed_atoms = STRING_LIST_INIT_NODUP, \\\n    }\n\nNote the difference:\n1. The first line no longer has the '.mode' field, this would ensure\nthat removal of the field is a diff which only contains the field and\nnot movement of other fields to the top line.\n2. Similar for the last time.\n3. We also add a comma for the last field, this allows addition of new\nfields without modifying previous lines by adding a missing comma.\n\nI don't think this too warrants a re-roll, but this is something to\ndefinitely fix incase you do re-roll for other reasons :)\n\n>>\n>>>       static int is_atom(const char *atom, const char *s, int slen)\n>>>       {\n>>>     + \tint alen = strlen(atom);\n>>>      @@ builtin/cat-file.c: static int expand_atom(struct strbuf *sb, const char *atom, int len,\n>>>       \t\t       struct expand_data *data)\n>>>       {\n>>>     @@ builtin/cat-file.c: static int expand_atom(struct strbuf *sb, const char *atom,\n>>>      -\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n>>>      +\t\tsize_t i;\n>>>      +\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n>>>     -+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))\n>>>     ++\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string,\n>>>     ++\t\t\t\t    atom, len))\n>>>       \t\t\t\tbreak;\n>>>      -\n>>>      -\t\t/*\n>>>\n>>> ---\n>>> base-commit: 44de1520f08d1dfebc3ab2d9f644208eaa5ac925\n>>\n>> I reviewed v18 last, but the diff here looks good to me. Thanks!\n>\n> [1]: https://lore.kernel.org/git/xmqqcxwonnkx.fsf@gitster.g/\n>\n> Thanks,\n> Pablo\n"},{"id":"548796","messageId":"DK5E7W7M0G8G.306LUAYVXFJ2N@gmail.com","threadId":"61695","inReplyTo":"CAOLa=ZS8J4t12ab1=3-LRYNuZOwqSHG861iYm97JjF3mGprvJA@mail.gmail.com","subject":"Re: [PATCH GSoC v20 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-22T20:47:12Z","receivedAt":"2026-07-22T20:47:17Z","isPatch":true,"body":"On Wed Jul 22, 2026 at 6:13 PM CEST, Karthik Nayak wrote:\n> \"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n>\n>> On Tue Jul 21, 2026 at 10:12 PM CEST, Karthik Nayak wrote:\n>>> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>>>\n>>> [snip]\n>>>\n>>>> Changes in v20:\n>>>> - Moved the prep patch that fixes hash_algo's type to be before\n>>>>   write_fetch_command_and_capabilities() being moved to 'connect.c'\n>>>> - Reverted git-cat-file.adoc documentation comments about CAVEATS\n>>>> - Fixed style for EXPAND_DATA_INIT\n>>>> - Added more context for comman line die()\n>>>>\n>>>\n>>> [snip]\n>>>\n>>>> Range-diff versus v19:\n\n[snip]\n\n>>>>\n>>>>     @@ Documentation/git-cat-file.adoc: one per line, and print information based on th\n>>>>       You can specify the information shown for each object by using a custom\n>>>>       `<format>`. The `<format>` is copied literally to stdout for each\n>>>>      @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n>>>>     - \treports).\n>>>>     -\n>>>>     - `objectsize:disk`::\n>>>>     --\tThe size, in bytes, that the object takes up on disk. See the\n>>>>     --\tnote about on-disk sizes in the `CAVEATS` section below.\n>>>>     -+\tThe size, in bytes, that the object takes up on disk.\n>>>>     -\n>>>>       `deltabase`::\n>>>>       \tIf the object is stored as a delta on-disk, this expands to the\n>>>>       \tfull hex representation of the delta base object name.\n>>>>      -\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n>>>>     --\tbelow.\n>>>>     -+\tOtherwise, expands to the null OID (all zeroes).\n>>>>     ++\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS` section\n>>>>     + \tbelow.\n>>>>\n>>>\n>>> This chagne is still unnecessary, no?\n>>\n>> I guess it is unnecessary. I added it because on top, at objectsize:disk\n>> it says \"`CAVEATS` section\". So I tried to have it consistently.\n>>\n>> Is it worth a reroll?\n>>\n>\n> No I don't think a re-roll is necessary. But something to avoid next\n> time :)\n>\n\n[snip]\n\n>>>\n>>>> 13:  ebdfc22fb7 ! 13:  70a11d2aea cat-file: make remote-object-info allow-list adapt to the server\n>>>>     @@ builtin/cat-file.c: struct expand_data {\n>>>>       \t */\n>>>>       \tunsigned is_remote:1;\n>>>>      -};\n>>>>     +-\n>>>>      -#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n>>>>\n>>>>      -static const char *remote_object_info_atoms[] = {\n>>>>      -\t\"objectname\",\n>>>>      -\t\"objectsize\",\n>>>>     ++\t/*\n>>>>     ++\t * List of atoms (i.e. \"objectsize\") that the server supports. Built\n>>>>     ++\t * from the server's object-info advertised capabilities.\n>>>>     ++\t */\n>>>>      +\tstruct string_list remote_allowed_atoms;\n>>>>       };\n>>>>     -+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD, \\\n>>>>     -+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n>>>>\n>>>>     ++#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, \\\n>>>>     ++\t\t\t    .type = OBJ_BAD, \\\n>>>>     ++\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n>>>>     ++\n>>>\n>>> I guess this is output of running the style check script (which I did\n>>> recommend, but I should've mentioned that you should take it with a\n>>> pinch of salt, it doesn't always provide the best solutions.)\n>>\n>> It comes in part from Junio's suggestion [1]. Although I don't think that\n>> it will receive a lot of new fields, I thought that it would leave the\n>> macro clean for that.\n>>\n>\n> Then wouldn't something like this be better?\n>\n>      #define EXPAND_DATA_INIT  {  \\\n>              .mode = S_IFINVALID, \\\n>              .type = OBJ_BAD,     \\\n>              .remote_allowed_atoms = STRING_LIST_INIT_NODUP, \\\n>     }\n>\n> Note the difference:\n> 1. The first line no longer has the '.mode' field, this would ensure\n> that removal of the field is a diff which only contains the field and\n> not movement of other fields to the top line.\n> 2. Similar for the last time.\n> 3. We also add a comma for the last field, this allows addition of new\n> fields without modifying previous lines by adding a missing comma.\n>\n> I don't think this too warrants a re-roll, but this is something to\n> definitely fix incase you do re-roll for other reasons :)\n\nYeah, this one is better formatted than what I did. Thanks for\nthe explanation. I'll definetely fix this two hunks if I have to reroll.\n\n[snip]\n\nThanks,\nPablo\n"},{"id":"548876","messageId":"20260724-ps-eric-work-rebase-v21-1-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 01/13] transport-helper: fix memory leak of helper on disconnect","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:12Z","receivedAt":"2026-07-24T10:54:30Z","isPatch":true,"body":"disconnect_helper() only frees data inside of the if(data->helper) block\n[1]. When the transport is disconnected without the helper being fully\nstarted, data->name allocated in transport_helper_init()\nis never freed.\n\nMove FREE_AND_NULL(data->name) outside the conditional block so it's\nalways freed on disconnect.\n\n[1]: https://lore.kernel.org/git/05fbadbae2184479c87c37675dde7bd79b3e32ab.1716465556.git.ps@pks.im/\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n transport-helper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 80f90eb7ba..f195070788 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -266,9 +266,9 @@ static int disconnect_helper(struct transport *transport)\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tres = finish_command(data->helper);\n-\t\tFREE_AND_NULL(data->name);\n \t\tFREE_AND_NULL(data->helper);\n \t}\n+\tFREE_AND_NULL(data->name);\n \treturn res;\n }\n \n\n-- \n2.54.0\n"},{"id":"548879","messageId":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com","subject":"[PATCH GSoC v21 00/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:11Z","receivedAt":"2026-07-24T10:54:30Z","isPatch":true,"body":"This patch series is a continuation of Eric Ju's\n(eric.peijian@gmail.com) and Calvin Wan's (calvinwan@google.com) patch\nseries [1] and [2] respectively.\n\nSometimes it is beneficial to retrieve information about an object\nwithout having to download it completely. The server logic for\nretrieving size has already been implemented and merged in a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20) [3].\nThis patch series implements the client option for it.\n\nEric's series adds the remote-object-info command to cat-file\n--batch-command. This command allows the client to make an object-info\ncommand request to a server that supports protocol v2.\n\nIf the server uses protocol v2 but does not support the object-info\ncapability, cat-file --batch-command will die.\nIf a user attempts to use remote-object-info with protocol v1, cat-file\n--batch-command will die.\n\nCurrently, only the size (%(objectsize)) is supported end to end in this\nimplementation. The type (%(objecttype)) is known by the client's\nallow-list and request path but is not supported on the server side\nnor the response parsing. A follow up series will add full end-to-end\nsupport for %(objecttype).\n\nThe default format for remote-object-info is set to \"%(objectname)\n%(objectsize)\". Once %(objecttype) is supported, the default format will\nbe unified accordingly.\n\nIf the batch command format includes unsupported fields such as\n%(objecttype), %(objectsize:disk), or %(deltabase), the command will\nreturn empty strings for each unsupported field.\n\nThis series completes Eric's work mainly with the refactor of the\nvalidation of the placeholders with an allow-list that filters what the\nclient asks with what the server is capable of providing, following Jeff\nKing's idea [4].\n\nGitHub CI: https://github.com/pabloosabaterr/git/actions/runs/30086202054\n\n[1]: https://lore.kernel.org/git/20250221190451.12536-1-eric.peijian@gmail.com/\n[2]: https://lore.kernel.org/git/20220728230210.2952731-1-calvinwan@google.com/#t\n[3]: https://git.kernel.org/pub/scm/git/git.git/commit/?id=a2ba162cda2acc171c3e36acbbc854792b093cb7\n[4]: https://lore.kernel.org/git/20250313060250.GH94015@coredump.intra.peff.net/\n\nChanges in v21:\n- Dropped unnecessary change in git-cat-file.adoc\n- Fixed EXPAND_DATA_INIT macro formating\n- typo and blank line before WARNING:\n\n---\nCalvin Wan (3):\n      fetch-pack: move fetch initialization\n      serve: advertise object-info feature\n      transport: add client support for object-info\n\nEric Ju (3):\n      cat-file: declare loop counter inside for()\n      t1006: extract helper functions into new 'lib-cat-file.sh'\n      cat-file: add remote-object-info to batch-command\n\nPablo Sabater (7):\n      transport-helper: fix memory leak of helper on disconnect\n      fetch-pack: drop the static advertise_sid variable\n      fetch-pack: use unsigned int for hash_algo variable\n      fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n      connect: make write_fetch_command_and_capabilities() more generic\n      protocol-caps: check object existence regardless of the attributes requested\n      cat-file: make remote-object-info allow-list adapt to the server\n\n Documentation/git-cat-file.adoc        |  23 +-\n Documentation/gitprotocol-v2.adoc      |  21 +-\n Makefile                               |   1 +\n builtin/cat-file.c                     | 224 +++++++++-\n connect.c                              |  34 ++\n connect.h                              |   8 +\n fetch-object-info.c                    | 156 +++++++\n fetch-object-info.h                    |  25 ++\n fetch-pack.c                           |  58 +--\n meson.build                            |   1 +\n object-file.c                          |  10 +\n odb.h                                  |   9 +\n protocol-caps.c                        |  45 +-\n serve.c                                |   5 +-\n t/lib-cat-file.sh                      |  16 +\n t/meson.build                          |   1 +\n t/t1006-cat-file.sh                    |  15 +-\n t/t1017-cat-file-remote-object-info.sh | 747 +++++++++++++++++++++++++++++++++\n t/t5701-git-serve.sh                   |  63 +++\n transport-helper.c                     |  12 +-\n transport-internal.h                   |   8 +\n transport.c                            |  44 ++\n transport.h                            |   9 +\n 23 files changed, 1444 insertions(+), 91 deletions(-)\n\nRange-diff versus v20:\n\n 1:  ddc6386af3 =  1:  6c868e0e29 transport-helper: fix memory leak of helper on disconnect\n 2:  24e0c07dde =  2:  40579c0095 cat-file: declare loop counter inside for()\n 3:  08882a9ddd =  3:  b70d1f91c4 t1006: extract helper functions into new 'lib-cat-file.sh'\n 4:  0925c208c1 =  4:  5c5050d541 fetch-pack: drop the static advertise_sid variable\n 5:  f130092dd9 =  5:  29c321ad3e fetch-pack: use unsigned int for hash_algo variable\n 6:  37326255e9 =  6:  bdcd407a22 fetch-pack: move write_fetch_command_and_capabilities() to connect.c\n 7:  069b9d4def =  7:  970d59e2cf connect: make write_fetch_command_and_capabilities() more generic\n 8:  4225d3087c =  8:  86c64d10d7 fetch-pack: move fetch initialization\n 9:  af3b2bb293 =  9:  7007808819 protocol-caps: check object existence regardless of the attributes requested\n10:  99df8374df = 10:  2f5acaa540 serve: advertise object-info feature\n11:  380bca2a43 = 11:  abb9306254 transport: add client support for object-info\n12:  eb48e94f6f ! 12:  36fe34a596 cat-file: add remote-object-info to batch-command\n    @@ Documentation/git-cat-file.adoc: one per line, and print information based on th\n      `contents` command followed by an object prints contents in the same way\n     -`--batch` would.\n     +`--batch` would. The `remote-object-info` command followed by a remote and\n    -+objects IDs prints object info from the remote without downloading the objects.\n    ++object IDs prints object info from the remote without downloading the objects.\n\n      You can specify the information shown for each object by using a custom\n      `<format>`. The `<format>` is copied literally to stdout for each\n    -@@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n    - `deltabase`::\n    - \tIf the object is stored as a delta on-disk, this expands to the\n    - \tfull hex representation of the delta base object name.\n    --\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS`\n    -+\tOtherwise, expands to the null OID (all zeroes). See `CAVEATS` section\n    - \tbelow.\n    -\n    - `rest`::\n     @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n      \tafter that first run of whitespace (i.e., the \"rest\" of the\n      \tline) are output in place of the `%(rest)` atom.\n    @@ Documentation/git-cat-file.adoc: newline. The available atoms are:\n      If no format is specified, the default format is `%(objectname)\n     -%(objecttype) %(objectsize)`.\n     +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which\n    -+use `%(objectname) %(objectsize)` because \"%(objecttype)\" is not supported yet.\n    ++use `%(objectname) %(objectsize)` because `%(objecttype)` is not supported yet.\n    ++\n     +WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified,\n     +so DO NOT RELY on the current default format to stay the same!!!\n\n    @@ builtin/cat-file.c: static const struct parse_cmd {\n      static void batch_objects_command(struct batch_options *opt,\n\n      ## object-file.c ##\n    -@@ object-file.c: struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)\n    +@@ object-file.c: int odb_transaction_files_begin(struct odb_source *source,\n\n    - \treturn &transaction->base;\n    + \treturn 0;\n      }\n     +\n     +void free_object_info_contents(struct object_info *object_info)\n13:  75b1c61c67 ! 13:  71e19e8d27 cat-file: make remote-object-info allow-list adapt to the server\n    @@ builtin/cat-file.c: struct expand_data {\n     +\tstruct string_list remote_allowed_atoms;\n      };\n\n    -+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, \\\n    -+\t\t\t    .type = OBJ_BAD, \\\n    -+\t\t\t    .remote_allowed_atoms = STRING_LIST_INIT_NODUP }\n    ++#define EXPAND_DATA_INIT  {  \\\n    ++\t.mode = S_IFINVALID, \\\n    ++\t.type = OBJ_BAD,     \\\n    ++\t.remote_allowed_atoms = STRING_LIST_INIT_NODUP, \\\n    ++}\n     +\n      static int is_atom(const char *atom, const char *s, int slen)\n      {\n\n---\nbase-commit: 9a0c4701dcd5725c4184599322b52933ff5005ca\nchange-id: 20260608-ps-eric-work-rebase-b73ae84ba671\n"},{"id":"548878","messageId":"20260724-ps-eric-work-rebase-v21-2-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 02/13] cat-file: declare loop counter inside for()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:13Z","receivedAt":"2026-07-24T10:54:31Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nDeclare loop counters in the for statement when they are only used\nwithin the loop body, limiting their scope and improving readability.\n\nWhile updating the loop counters, use size_t instead of int for counters\nthat iterate over object counts.\n\nUpdate the 'nr' parameter of dispatch_calls() to size_t as all callers\nalready pass a value of that type.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c | 13 ++++---------\n fetch-pack.c       |  3 +--\n 2 files changed, 5 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 1458dd76d6..fc18a92736 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -720,14 +720,12 @@ static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n \t\tstruct queued_cmd *cmd,\n-\t\tint nr)\n+\t\tsize_t nr)\n {\n-\tint i;\n-\n \tif (!opt->buffer_output)\n \t\tdie(_(\"flush is only for --buffer mode\"));\n \n-\tfor (i = 0; i < nr; i++)\n+\tfor (size_t i = 0; i < nr; i++)\n \t\tcmd[i].fn(opt, cmd[i].line, output, data);\n \n \tfflush(stdout);\n@@ -735,9 +733,7 @@ static void dispatch_calls(struct batch_options *opt,\n \n static void free_cmds(struct queued_cmd *cmd, size_t *nr)\n {\n-\tsize_t i;\n-\n-\tfor (i = 0; i < *nr; i++)\n+\tfor (size_t i = 0; i < *nr; i++)\n \t\tFREE_AND_NULL(cmd[i].line);\n \n \t*nr = 0;\n@@ -764,7 +760,6 @@ static void batch_objects_command(struct batch_options *opt,\n \tsize_t alloc = 0, nr = 0;\n \n \twhile (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) {\n-\t\tint i;\n \t\tconst struct parse_cmd *cmd = NULL;\n \t\tconst char *p = NULL, *cmd_end;\n \t\tstruct queued_cmd call = {0};\n@@ -774,7 +769,7 @@ static void batch_objects_command(struct batch_options *opt,\n \t\tif (isspace(*input.buf))\n \t\t\tdie(_(\"whitespace before command: '%s'\"), input.buf);\n \n-\t\tfor (i = 0; i < ARRAY_SIZE(commands); i++) {\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(commands); i++) {\n \t\t\tif (!skip_prefix(input.buf, commands[i].name, &cmd_end))\n \t\t\t\tcontinue;\n \ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 29c41132ee..9eb8fc5399 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1388,9 +1388,8 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\n \t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n \tif (server_options && server_options->nr) {\n-\t\tint i;\n \t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (i = 0; i < server_options->nr; i++)\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n \t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n \t\t\t\t\t server_options->items[i].string);\n \t}\n\n-- \n2.54.0\n"},{"id":"548877","messageId":"20260724-ps-eric-work-rebase-v21-3-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 03/13] t1006: extract helper functions into new 'lib-cat-file.sh'","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:14Z","receivedAt":"2026-07-24T10:54:32Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nExtract utility functions from the cat-file's test script\n't1006-cat-file.sh' into a new 'lib-cat-file.sh' dedicated library file.\n\nA subsequent commit will need these functions. This improves the code\nreuse and readability, enabling future cat-file tests to share these\nhelpers without duplicating code.\n\nWhile at it update the style of this line to follow coding\nguidelines:\n\n. \"$TEST_DIRECTORY/lib-loose.sh\"\n\nto\n\n. \"$TEST_DIRECTORY\"/lib-loose.sh\n\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n t/lib-cat-file.sh   | 16 ++++++++++++++++\n t/t1006-cat-file.sh | 15 ++-------------\n 2 files changed, 18 insertions(+), 13 deletions(-)\n\ndiff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh\nnew file mode 100644\nindex 0000000000..7c2e877016\n--- /dev/null\n+++ b/t/lib-cat-file.sh\n@@ -0,0 +1,16 @@\n+# Library of git-cat-file related test functions.\n+\n+# Print a string without a trailing newline.\n+echo_without_newline () {\n+\tprintf '%s' \"$*\"\n+}\n+\n+# Print a string without newlines and replace them with a NUL character (\\0).\n+echo_without_newline_nul () {\n+\techo_without_newline \"$@\" | tr '\\n' '\\0'\n+}\n+\n+# Calculate the length of a string.\n+strlen () {\n+\techo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 762c77c351..0c1dc0fb40 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -3,7 +3,8 @@\n test_description='git cat-file'\n \n . ./test-lib.sh\n-. \"$TEST_DIRECTORY/lib-loose.sh\"\n+. \"$TEST_DIRECTORY\"/lib-loose.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n \n test_cmdmode_usage () {\n \ttest_expect_code 129 \"$@\" 2>err &&\n@@ -99,18 +100,6 @@ do\n \t'\n done\n \n-echo_without_newline () {\n-    printf '%s' \"$*\"\n-}\n-\n-echo_without_newline_nul () {\n-\techo_without_newline \"$@\" | tr '\\n' '\\0'\n-}\n-\n-strlen () {\n-    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n-}\n-\n run_tests () {\n     type=$1\n     object_name=\"$2\"\n\n-- \n2.54.0\n"},{"id":"548880","messageId":"20260724-ps-eric-work-rebase-v21-4-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 04/13] fetch-pack: drop the static advertise_sid variable","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:15Z","receivedAt":"2026-07-24T10:54:33Z","isPatch":true,"body":"write_fetch_command_and_capabilities() is moved to 'connect.c' in a\nsubsequent commit. To prepare for that, drop the static variable usage\nof advertise_sid.\n\nCurrently advertise_sid is set in fetch_pack_config() by reading\n\"transfer.advertisesid\". It is used in three places:\n\n1. In do_fetch_pack(), to clear it when the server lacks support:\n\n        if (!server_supports(\"session-id\"))\n               advertise_sid = 0;\n\n2. In find_common(), to advertise the session id over protocol v0/v1:\n\n        if (advertise_sid)\n                strbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n\n3. In write_fetch_command_and_capabilities(), to advertise it over\n   protocol v2:\n\n        if (advertise_sid && server_supports_v2(\"session-id\"))\n                packet_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n\nAbout 1, the check only guards the v0/v1 path, and the v2 path\nalready checks server support inline in its condition. Follow the\nsame pattern and fold the check into the condition in find_common().\n\nAbout 2 and 3, replace the static variable with a local read via\nrepo_config_get_bool() in each function.\n\nBecause repo_config_get_bool() leaves advertise_sid as is if it is not\nset, initialize it to 0, matching its default.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 13 +++++++------\n 1 file changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 9eb8fc5399..65ebfec09f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -49,7 +49,6 @@ static int fetch_fsck_objects = -1;\n static int transfer_fsck_objects = -1;\n static int agent_supported;\n static int server_supports_filtering;\n-static int advertise_sid;\n static struct shallow_lock shallow_lock;\n static const char *alternate_shallow_file;\n static struct strbuf fsck_msg_types = STRBUF_INIT;\n@@ -363,6 +362,9 @@ static int find_common(struct fetch_negotiator *negotiator,\n \tsize_t state_len = 0;\n \tstruct packet_reader reader;\n \tstruct oidset negotiation_include_oids = OIDSET_INIT;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tif (args->stateless_rpc && multi_ack == 1)\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--stateless-rpc\", \"multi_ack_detailed\");\n@@ -414,7 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator,\n \t\t\tif (deepen_not_ok)      strbuf_addstr(&c, \" deepen-not\");\n \t\t\tif (agent_supported)    strbuf_addf(&c, \" agent=%s\",\n \t\t\t\t\t\t\t    git_user_agent_sanitized());\n-\t\t\tif (advertise_sid)\n+\t\t\tif (advertise_sid && server_supports(\"session-id\"))\n \t\t\t\tstrbuf_addf(&c, \" session-id=%s\", trace2_session_id());\n \t\t\tif (args->filter_options.choice)\n \t\t\t\tstrbuf_addstr(&c, \" filter\");\n@@ -1160,9 +1162,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,\n \t\t\t\t      (int)agent_len, agent_feature);\n \t}\n \n-\tif (!server_supports(\"session-id\"))\n-\t\tadvertise_sid = 0;\n-\n \tif (server_supports(\"shallow\"))\n \t\tprint_verbose(args, _(\"Server supports %s\"), \"shallow\");\n \telse if (args->depth > 0 || is_repository_shallow(r))\n@@ -1380,6 +1379,9 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t\t\t\t\t\t const struct string_list *server_options)\n {\n \tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n \tensure_server_supports_v2(\"fetch\");\n \tpacket_buf_write(req_buf, \"command=fetch\");\n@@ -1998,7 +2000,6 @@ static void fetch_pack_config(void)\n \trepo_config_get_bool(the_repository, \"repack.usedeltabaseoffset\", &prefer_ofs_delta);\n \trepo_config_get_bool(the_repository, \"fetch.fsckobjects\", &fetch_fsck_objects);\n \trepo_config_get_bool(the_repository, \"transfer.fsckobjects\", &transfer_fsck_objects);\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \tif (!uri_protocols.nr) {\n \t\tchar *str;\n \n\n-- \n2.54.0\n"},{"id":"548882","messageId":"20260724-ps-eric-work-rebase-v21-5-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 05/13] fetch-pack: use unsigned int for hash_algo variable","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:16Z","receivedAt":"2026-07-24T10:54:34Z","isPatch":true,"body":"hash_algo_by_name() returns unsigned int, but it is stored in\nhash_algo variable as int. This goes unnoticed because of:\n\n\tDISABLE_SIGN_COMPARE_WARNINGS\n\nOn 'fetch-pack.c'\n\nOn a subsequent commit this function will be moved to 'connect.c' that\nwould notice this.\n\nChange hash_algo variable type to match its return type, also make it\nconst because they are never modified.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 65ebfec09f..f1e64160fc 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1397,7 +1397,7 @@ static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n \t}\n \n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tint hash_algo = hash_algo_by_name(hash_name);\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n \t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n \t\t\t    the_hash_algo->name, hash_name);\n\n-- \n2.54.0\n"},{"id":"548881","messageId":"20260724-ps-eric-work-rebase-v21-6-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 06/13] fetch-pack: move write_fetch_command_and_capabilities() to connect.c","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:17Z","receivedAt":"2026-07-24T10:54:36Z","isPatch":true,"body":"In a subsequent commit write_fetch_command_and_capabilities() will be\nrefactored to a more general-purpose function, making it more accessible\nto additional commands in the future.\n\nMove write_fetch_command_and_capabilities() to 'connect.c', where\nthere are similar purpose functions.\n\nBecause string_list is only used as a pointer, use a forward\ndeclaration [1].\n\n[1]: https://lore.kernel.org/git/Z0RIqUAoEob8lGfM@pks.im/\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 34 ++++++++++++++++++++++++++++++++++\n connect.h    |  4 ++++\n fetch-pack.c | 34 ----------------------------------\n 3 files changed, 38 insertions(+), 34 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 1d74c1eda2..0d197eca84 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -709,6 +709,40 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options)\n+{\n+\tconst char *hash_name;\n+\tint advertise_sid = 0;\n+\n+\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n+\n+\tensure_server_supports_v2(\"fetch\");\n+\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tif (server_supports_v2(\"agent\"))\n+\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n+\tif (advertise_sid && server_supports_v2(\"session-id\"))\n+\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n+\tif (server_options && server_options->nr) {\n+\t\tensure_server_supports_v2(\"server-option\");\n+\t\tfor (size_t i = 0; i < server_options->nr; i++)\n+\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n+\t\t\t\t\t server_options->items[i].string);\n+\t}\n+\n+\tif (server_feature_v2(\"object-format\", &hash_name)) {\n+\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n+\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n+\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n+\t\t\t    the_hash_algo->name, hash_name);\n+\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n+\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n+\t\tdie(_(\"the server does not support algorithm '%s'\"),\n+\t\t    the_hash_algo->name);\n+\t}\n+\tpacket_buf_delim(req_buf);\n+}\n+\n static const char *url_scheme_name(enum url_scheme scheme)\n {\n \tswitch (scheme) {\ndiff --git a/connect.h b/connect.h\nindex aa482a37fb..c4f6ea4b0a 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -34,4 +34,8 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       struct packet_reader *reader,\n \t\t\t       const char *error);\n \n+struct string_list;\n+void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n+\t\t\t\t\t  const struct string_list *server_options);\n+\n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f1e64160fc..f7789e8456 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1375,40 +1375,6 @@ static int add_haves(struct fetch_negotiator *negotiator,\n \treturn haves_added;\n }\n \n-static void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t\t const struct string_list *server_options)\n-{\n-\tconst char *hash_name;\n-\tint advertise_sid = 0;\n-\n-\trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n-\n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n-\tif (server_supports_v2(\"agent\"))\n-\t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n-\tif (advertise_sid && server_supports_v2(\"session-id\"))\n-\t\tpacket_buf_write(req_buf, \"session-id=%s\", trace2_session_id());\n-\tif (server_options && server_options->nr) {\n-\t\tensure_server_supports_v2(\"server-option\");\n-\t\tfor (size_t i = 0; i < server_options->nr; i++)\n-\t\t\tpacket_buf_write(req_buf, \"server-option=%s\",\n-\t\t\t\t\t server_options->items[i].string);\n-\t}\n-\n-\tif (server_feature_v2(\"object-format\", &hash_name)) {\n-\t\tconst unsigned int hash_algo = hash_algo_by_name(hash_name);\n-\t\tif (hash_algo_by_ptr(the_hash_algo) != hash_algo)\n-\t\t\tdie(_(\"mismatched algorithms: client %s; server %s\"),\n-\t\t\t    the_hash_algo->name, hash_name);\n-\t\tpacket_buf_write(req_buf, \"object-format=%s\", the_hash_algo->name);\n-\t} else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1_LEGACY) {\n-\t\tdie(_(\"the server does not support algorithm '%s'\"),\n-\t\t    the_hash_algo->name);\n-\t}\n-\tpacket_buf_delim(req_buf);\n-}\n-\n static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \t\t\t      struct fetch_pack_args *args,\n \t\t\t      const struct ref *wants, struct oidset *common,\n\n-- \n2.54.0\n"},{"id":"548883","messageId":"20260724-ps-eric-work-rebase-v21-7-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 07/13] connect: make write_fetch_command_and_capabilities() more generic","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:18Z","receivedAt":"2026-07-24T10:54:37Z","isPatch":true,"body":"Refactor write_fetch_command_and_capabilities(), enabling it to serve\nboth fetch and additional commands.\n\nIn this context, \"command\" refers to the \"operations\" supported by\nGit's wire protocol Documentation/gitprotocol-v2.adoc, such as a Git\nsubcommand (e.g., git-fetch(1)) or a server-side operation like\n\"object-info\" as implemented in commit a2ba162cda\n(object-info: support for retrieving object info, 2021-04-20).\n\nRefactor the function signature to accept a command instead of the\nhardcoded \"fetch\".\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n connect.c    | 8 ++++----\n connect.h    | 8 ++++++--\n fetch-pack.c | 4 ++--\n 3 files changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex 0d197eca84..feb0420b0f 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -709,16 +709,16 @@ int server_supports(const char *feature)\n \treturn !!server_feature_value(feature, NULL);\n }\n \n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options)\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options)\n {\n \tconst char *hash_name;\n \tint advertise_sid = 0;\n \n \trepo_config_get_bool(the_repository, \"transfer.advertisesid\", &advertise_sid);\n \n-\tensure_server_supports_v2(\"fetch\");\n-\tpacket_buf_write(req_buf, \"command=fetch\");\n+\tensure_server_supports_v2(command);\n+\tpacket_buf_write(req_buf, \"command=%s\", command);\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_buf_write(req_buf, \"agent=%s\", git_user_agent_sanitized());\n \tif (advertise_sid && server_supports_v2(\"session-id\"))\ndiff --git a/connect.h b/connect.h\nindex c4f6ea4b0a..957e5fe2b9 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -35,7 +35,11 @@ void check_stateless_delimiter(int stateless_rpc,\n \t\t\t       const char *error);\n \n struct string_list;\n-void write_fetch_command_and_capabilities(struct strbuf *req_buf,\n-\t\t\t\t\t  const struct string_list *server_options);\n+/*\n+ * Write a protocol v2 command request, along with the capability\n+ * advertisements, into req_buf.\n+ */\n+void write_command_and_capabilities(struct strbuf *req_buf, const char *command,\n+\t\t\t\t    const struct string_list *server_options);\n \n #endif\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex f7789e8456..3695059cd5 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1386,7 +1386,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,\n \tint done_sent = 0;\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_fetch_command_and_capabilities(&req_buf, args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"fetch\", args->server_options);\n \n \tif (args->use_thin_pack)\n \t\tpacket_buf_write(&req_buf, \"thin-pack\");\n@@ -2253,7 +2253,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,\n \t\t\t\t\t   the_repository, \"%d\",\n \t\t\t\t\t   negotiation_round);\n \t\tstrbuf_reset(&req_buf);\n-\t\twrite_fetch_command_and_capabilities(&req_buf, server_options);\n+\t\twrite_command_and_capabilities(&req_buf, \"fetch\", server_options);\n \n \t\tpacket_buf_write(&req_buf, \"wait-for-done\");\n \n\n-- \n2.54.0\n"},{"id":"548884","messageId":"20260724-ps-eric-work-rebase-v21-8-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 08/13] fetch-pack: move fetch initialization","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:19Z","receivedAt":"2026-07-24T10:54:40Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nThere are some variables initialized at the start of the\ndo_fetch_pack_v2() state machine. Currently, they are initialized in\nFETCH_CHECK_LOCAL, which is the initial state set at the beginning\nof the function.\n\nHowever, a subsequent patch will allow for another initial state,\nwhile still requiring these initialized variables.\nMove the initialization to be before the state machine,\nso that they are set regardless of the initial state.\n\nNote that there is no change in behavior, because we're moving code\nfrom the beginning of the first state to just before the execution of\nthe state machine.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n fetch-pack.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 3695059cd5..922a9b2581 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1735,18 +1735,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \t\treader.me = \"fetch-pack\";\n \t}\n \n+\t/* v2 supports these by default */\n+\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n+\tuse_sideband = 2;\n+\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n+\t\targs->deepen = 1;\n+\n \twhile (state != FETCH_DONE) {\n \t\tswitch (state) {\n \t\tcase FETCH_CHECK_LOCAL:\n \t\t\tsort_ref_list(&ref, ref_compare_name);\n \t\t\tQSORT(sought, nr_sought, cmp_ref_by_name);\n \n-\t\t\t/* v2 supports these by default */\n-\t\t\tallow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;\n-\t\t\tuse_sideband = 2;\n-\t\t\tif (args->depth > 0 || args->deepen_since || args->deepen_not)\n-\t\t\t\targs->deepen = 1;\n-\n \t\t\t/* Filter 'ref' by 'sought' and those that aren't local */\n \t\t\tmark_complete_and_common_ref(negotiator, args, &ref);\n \t\t\tfilter_refs(args, &ref, sought, nr_sought);\n\n-- \n2.54.0\n"},{"id":"548885","messageId":"20260724-ps-eric-work-rebase-v21-9-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 09/13] protocol-caps: check object existence regardless of the attributes requested","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:20Z","receivedAt":"2026-07-24T10:54:41Z","isPatch":true,"body":"Currently, send_info() only checks for existence when the attribute\n'size' is also requested. Requesting a bare OID, without attributes only\nechoes back the OID.\n\nExtract the existence check to be done regardless of the number of\nattributes requested.\n\nWhile at it, introduce a wrapper called get_object_info() similar to\nodb_read_object_info() that returns OBJ_BAD on fail and adds\nOBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK flags.\nOBJECT_INFO_SKIP_FETCH_OBJECT is so a server with a partial clone\ndoesn't trigger fetching objects when it gets an object-info request\nwith an OID that is not available locally. A server should only report\nwhat it has locally.\n\nTighten the condition used to determine whether an object is\nrecognized. get_object_info() returns OBJ_BAD for unknown objects,\nbut OBJ_NONE (0) can also mean \"not found\". Change the check from '< 0'\nto '<= OBJ_NONE' to cover both as unrecognized.\n\nWith this patch, a bare OID has two possible responses:\n\n1. Recognized OID: the server answers with \"<OID>\"\n\n2. Unrecognized OID: the server answers with \"<OID> SP\"\n\nUpdate the object-info section in 'gitprotocol-v2.adoc':\n- Require full obj-oid explicitly.\n- Fix parentheses.\n- Define obj-size explicitly.\n- Make obj-size optional in obj-info and document the behavior\n  for unrecognized object IDs.\n- Describe the attr header as zero or more pkt-lines, one per attribute,\n  matching what the server implements. A request with no attributes gets\n  no header.\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/gitprotocol-v2.adoc | 21 ++++++++-----\n protocol-caps.c                   | 45 ++++++++++++++++++++++++----\n t/t5701-git-serve.sh              | 63 +++++++++++++++++++++++++++++++++++++++\n 3 files changed, 115 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 2beb70595f..7bf62014c3 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -568,21 +568,26 @@ An `object-info` request takes the following arguments:\n \n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n-\tinformation for.\n+\tinformation for. They must be full OIDs.\n \n-The response of `object-info` is a list of the requested object ids\n-and associated requested information, each separated by a single space.\n+The response of `object-info` consists of one pkt-line per requested attribute,\n+echoing the attributes the server will report, followed by one pkt-line per\n+requested object id with its information, each field separated by a single\n+space.\n \n \toutput = info flush-pkt\n \n-\tinfo = PKT-LINE(attrs) LF)\n-\t\t*PKT-LINE(obj-info LF)\n-\n-\tattrs = attr | attrs SP attrs\n+\tinfo = *PKT-LINE(attr LF)\n+\t       *PKT-LINE(obj-info LF)\n \n \tattr = \"size\"\n \n-\tobj-info = obj-id SP obj-size\n+\tobj-size = 1*DIGIT\n+\n+\tobj-info = obj-id [SP [obj-size]]\n+\n+If the server does not recognize the OID, the response will be `<oid> SP`\n+regardless of the number of attributes requested.\n \n bundle-uri\n ~~~~~~~~~~\ndiff --git a/protocol-caps.c b/protocol-caps.c\nindex 8858ea4489..02261be14d 100644\n--- a/protocol-caps.c\n+++ b/protocol-caps.c\n@@ -30,6 +30,32 @@ static int parse_oid(const char *line, struct string_list *oid_str_list)\n \treturn 1;\n }\n \n+/*\n+ * odb_read_object_info_extended() wrapper. Similar to odb_read_object_info()\n+ * but uses the flags:\n+ *\n+ * - OBJECT_INFO_SKIP_FETCH_OBJECT so a server won't fetch an object when a\n+ *   object-info request asks for an OID that it doesn't have.\n+ *\n+ * - OBJECT_INFO_QUICK to avoid re-scanning packs when the object is not found.\n+ */\n+static enum object_type get_object_info(struct object_database *odb,\n+\t\t\t   const struct object_id *oid,\n+\t\t\t   size_t *sizep)\n+{\n+\tenum object_type type;\n+\tstruct object_info oi = OBJECT_INFO_INIT;\n+\n+\toi.typep = &type;\n+\toi.sizep = sizep;\n+\tif (odb_read_object_info_extended(odb, oid, &oi,\n+\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE |\n+\t\t\t\t\t  OBJECT_INFO_SKIP_FETCH_OBJECT |\n+\t\t\t\t\t  OBJECT_INFO_QUICK) < 0)\n+\t\treturn OBJ_BAD;\n+\treturn type;\n+}\n+\n /*\n  * Validates and send requested info back to the client. Any errors detected\n  * are returned as they are detected.\n@@ -62,15 +88,22 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \n \t\tstrbuf_addstr(&send_buffer, oid_str);\n \n+\t\t/*\n+\t\t * Check the existence of the object first.\n+\t\t * If an object is not recognized by the server append SP to\n+\t\t * the response.\n+\t\t */\n+\t\tif (get_object_info(r->objects, &oid, &object_size) <= OBJ_NONE) {\n+\t\t\tstrbuf_addstr(&send_buffer, \" \");\n+\t\t\tgoto write;\n+\t\t}\n+\n \t\tif (info->size) {\n-\t\t\tif (odb_read_object_info(r->objects, &oid, &object_size) < 0) {\n-\t\t\t\tstrbuf_addstr(&send_buffer, \" \");\n-\t\t\t} else {\n-\t\t\t\tstrbuf_addf(&send_buffer, \" %\"PRIuMAX,\n-\t\t\t\t\t    (uintmax_t)object_size);\n-\t\t\t}\n+\t\t\tstrbuf_addf(&send_buffer, \" %\"PRIuMAX,\n+\t\t\t\t    (uintmax_t)object_size);\n \t\t}\n \n+write:\n \t\tpacket_writer_write(writer, \"%s\", send_buffer.buf);\n \t\tstrbuf_reset(&send_buffer);\n \t}\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 108eb30945..9a575aa098 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -7,6 +7,8 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n \n+unknown_oid=$(printf \"test\" | git hash-object --stdin)\n+\n test_expect_success 'setup to generate files with expected content' '\n \tprintf \"agent=git/%s\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n \n@@ -364,6 +366,67 @@ test_expect_success 'basics of object-info' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'bare OID request' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\toid $(git rev-parse two:two.t)\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\t$(git rev-parse two:two.t)\n+\t0000\n+\tEOF\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'object-info with bare unrecognized OID' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\toid $unknown_oid\n+\t0000\n+\tEOF\n+\n+\tprintf \"%s \\n\" \"$unknown_oid\" >expect &&\n+\tprintf \"0000\\n\" >>expect &&\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'object-info with size for unrecognized OID' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\tsize\n+\toid $unknown_oid\n+\t0000\n+\tEOF\n+\n+\tprintf \"size\\n\" >expect &&\n+\tprintf \"%s \\n\" \"$unknown_oid\" >>expect &&\n+\tprintf \"0000\\n\" >>expect &&\n+\n+\ttest-tool serve-v2 --stateless-rpc <in >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'test capability advertisement with uploadpack.advertiseBundleURIs' '\n \ttest_config uploadpack.advertiseBundleURIs true &&\n \n\n-- \n2.54.0\n"},{"id":"548886","messageId":"20260724-ps-eric-work-rebase-v21-10-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 10/13] serve: advertise object-info feature","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:21Z","receivedAt":"2026-07-24T10:54:42Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nIn order for a client to know what object-info components a server can\nprovide, advertise supported object-info features. This allows a client\nto decide whether to query the server for object-info or fetch as a\nfallback.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n serve.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/serve.c b/serve.c\nindex 49a6e39b1d..2b07d922b3 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,\n \ttrace2_data_string(\"transfer\", NULL, \"client-sid\", client_sid);\n }\n \n-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)\n+static int object_info_advertise(struct repository *r, struct strbuf *value)\n {\n \tif (advertise_object_info == -1 &&\n \t    repo_config_get_bool(r, \"transfer.advertiseobjectinfo\",\n@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n+\t/* Currently only size is supported */\n+\tif (value && advertise_object_info)\n+\t\tstrbuf_addstr(value, \"size\");\n \treturn advertise_object_info;\n }\n \n\n-- \n2.54.0\n"},{"id":"548887","messageId":"20260724-ps-eric-work-rebase-v21-11-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 11/13] transport: add client support for object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:22Z","receivedAt":"2026-07-24T10:54:45Z","isPatch":true,"body":"From: Calvin Wan <calvinwan@google.com>\n\nSometimes, it is beneficial to retrieve information about an object\nwithout downloading it entirely. The server-side logic for this\nfunctionality was implemented in commit \"a2ba162cda (object-info:\nsupport for retrieving object info, 2021-04-20).\" And the wire\nformat is documented at\nhttps://git-scm.com/docs/protocol-v2#_object_info.\n\nIntroduce client-side support for the object-info capability.\n\nAdd its own function for object-info separate from existing fetch\ninfrastructure.\n\nCurrently, the client supports requesting a list of OIDs with the size\nattribute from a v2 server. If the server does not advertise this\nfeature (i.e., transfer.advertiseobjectinfo is set to false), the client\nreturns an error and exits.\n\nNote that:\n\n1. The entire request is written into req_buf before being sent to the\n   remote. This approach follows the pattern used in the\n   send_fetch_request() logic within 'fetch-pack.c'. Streaming the\n   request is not addressed in this patch.\n\n2. A new field 'unrecognized' has been added to object_info. This new\n   field is set at fetch_object_info() when the object is unrecognized\n   by the server.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Makefile             |   1 +\n fetch-object-info.c  | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++\n fetch-object-info.h  |  22 ++++++++\n meson.build          |   1 +\n odb.h                |   6 +++\n transport-helper.c   |  10 ++++\n transport-internal.h |   8 +++\n transport.c          |  45 +++++++++++++++++\n transport.h          |   9 ++++\n 9 files changed, 240 insertions(+)\n\ndiff --git a/Makefile b/Makefile\nindex 98e995e4be..fac3e8879c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1169,6 +1169,7 @@ LIB_OBJS += ewah/ewah_io.o\n LIB_OBJS += ewah/ewah_rlw.o\n LIB_OBJS += exec-cmd.o\n LIB_OBJS += fetch-negotiator.o\n+LIB_OBJS += fetch-object-info.o\n LIB_OBJS += fetch-pack.o\n LIB_OBJS += fmt-merge-msg.o\n LIB_OBJS += fsck.o\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nnew file mode 100644\nindex 0000000000..30475a1e87\n--- /dev/null\n+++ b/fetch-object-info.c\n@@ -0,0 +1,138 @@\n+#include \"git-compat-util.h\"\n+#include \"gettext.h\"\n+#include \"hex.h\"\n+#include \"pkt-line.h\"\n+#include \"connect.h\"\n+#include \"oid-array.h\"\n+#include \"odb.h\"\n+#include \"fetch-object-info.h\"\n+#include \"string-list.h\"\n+\n+/* Sends object-info command and its arguments into the request buffer. */\n+static void send_object_info_request(const int fd_out, struct object_info_args *args)\n+{\n+\tstruct strbuf req_buf = STRBUF_INIT;\n+\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\n+\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\t\tpacket_buf_write(&req_buf, \"size\");\n+\telse if (args->object_info_options->nr)\n+\t\tBUG(\"only size should be in object_info_options\");\n+\n+\tif (args->oids)\n+\t\tfor (size_t i = 0; i < args->oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\", oid_to_hex(&args->oids->oid[i]));\n+\n+\tpacket_buf_flush(&req_buf);\n+\tif (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)\n+\t\tdie_errno(_(\"unable to write request to remote\"));\n+\n+\tstrbuf_release(&req_buf);\n+}\n+\n+static int parse_object_size(const char *s, size_t *res)\n+{\n+\tuintmax_t uim;\n+\n+\tif (!s[0] || s[strspn(s, \"0123456789\")])\n+\t\treturn -1;\n+\terrno = 0;\n+\tuim = strtoumax(s, NULL, 10);\n+\tif (errno || uim > SIZE_MAX)\n+\t\treturn -1;\n+\t*res = uim;\n+\treturn 0;\n+}\n+\n+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      const int stateless_rpc, const int fd_out)\n+{\n+\tint size_index = -1;\n+\n+\tswitch (version) {\n+\tcase protocol_v2:\n+\t\tif (!server_supports_v2(\"object-info\"))\n+\t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\tsend_object_info_request(fd_out, args);\n+\t\tbreak;\n+\tcase protocol_v1:\n+\tcase protocol_v0:\n+\t\tdie(_(\"object-info requires protocol v2\"));\n+\tcase protocol_unknown_version:\n+\t\tBUG(\"unknown protocol version\");\n+\t}\n+\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tcheck_stateless_delimiter(stateless_rpc, reader,\n+\t\t\t\t\t\t  \"stateless delimiter expected\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\t\treturn -1;\n+\n+\t\tif (!strcmp(reader->line, \"size\")) {\n+\t\t\t/*\n+\t\t\t * i is the number of supported options which currently\n+\t\t\t * is only size. No risk of overflow.\n+\t\t\t */\n+\t\t\tsize_index = (int)i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].sizep =\n+\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].sizep));\n+\t\t} else {\n+\t\t\tBUG(\"only size is supported\");\n+\t\t}\n+\t}\n+\n+\tfor (size_t i = 0;\n+\t     packet_reader_read(reader) == PACKET_READ_NORMAL &&\n+\t     i < args->oids->nr;\n+\t     i++) {\n+\t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n+\n+\t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n+\n+\t\tif (strcmp(object_info_values.items[0].string,\n+\t\t\t   oid_to_hex(&args->oids->oid[i])))\n+\t\t\tdie(_(\"object-info: expected OID: %s, got %s\"),\n+\t\t\t    oid_to_hex(&args->oids->oid[i]),\n+\t\t\t    object_info_values.items[0].string);\n+\n+\t\t/*\n+\t\t * If the response is two elements but the second one is an\n+\t\t * empty string, that means that the OID is unrecognized by the\n+\t\t * server.\n+\t\t */\n+\t\tif (object_info_values.nr >= 2 &&\n+\t\t    !strcmp(object_info_values.items[1].string, \"\")) {\n+\t\t\tobject_info_data[i].unrecognized = 1;\n+\t\t\tstring_list_clear(&object_info_values, 0);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Because we filter the options to be only the supported by\n+\t\t * the server we expect the server to answer with the same\n+\t\t * number of attributes requested.\n+\t\t */\n+\t\tif (args->object_info_options->nr + 1 != object_info_values.nr)\n+\t\t\tdie(\"object-info: unexpected number of attributes: %s\",\n+\t\t\t    reader->line);\n+\n+\t\tif (size_index >= 0 &&\n+\t\t    parse_object_size(object_info_values.items[size_index + 1].string,\n+\t\t\t\t      object_info_data[i].sizep))\n+\t\t\tdie(\"object-info: ref %s has invalid size %s\",\n+\t\t\t    object_info_values.items[0].string,\n+\t\t\t    object_info_values.items[size_index + 1].string);\n+\n+\t\tstring_list_clear(&object_info_values, 0);\n+\t}\n+\tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n+\n+\treturn 0;\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nnew file mode 100644\nindex 0000000000..31aad98408\n--- /dev/null\n+++ b/fetch-object-info.h\n@@ -0,0 +1,22 @@\n+#ifndef FETCH_OBJECT_INFO_H\n+#define FETCH_OBJECT_INFO_H\n+\n+#include \"pkt-line.h\"\n+#include \"protocol.h\"\n+\n+struct object_info_args {\n+\tstruct string_list *object_info_options;\n+\tconst struct string_list *server_options;\n+\tstruct oid_array *oids;\n+};\n+\n+struct object_info;\n+/*\n+ * Sends git-cat-file object-info command into the request buf and read the\n+ * results from packets.\n+ */\n+int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n+\t\t      struct packet_reader *reader, struct object_info *object_info_data,\n+\t\t      int stateless_rpc, int fd_out);\n+\n+#endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/meson.build b/meson.build\nindex f7c40ea079..7073d5844d 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -374,6 +374,7 @@ libgit_sources = [\n   'ewah/ewah_rlw.c',\n   'exec-cmd.c',\n   'fetch-negotiator.c',\n+  'fetch-object-info.c',\n   'fetch-pack.c',\n   'fmt-merge-msg.c',\n   'fsck.c',\ndiff --git a/odb.h b/odb.h\nindex 7995bed97b..b7bc0ee844 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -340,6 +340,12 @@ struct object_info {\n \t * or multiple times in the same source.\n \t */\n \tstruct odb_source_info *source_infop;\n+\n+\t/*\n+\t * object-info protocol specific. Set by the protocol when the remote\n+\t * does not recognize the requested object.\n+\t */\n+\tunsigned int unrecognized:1;\n };\n \n /*\ndiff --git a/transport-helper.c b/transport-helper.c\nindex f195070788..623463dcea 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -784,6 +784,15 @@ static int fetch_refs(struct transport *transport,\n \treturn -1;\n }\n \n+static int fetch_object_info_helper(struct transport *transport)\n+{\n+\tget_helper(transport);\n+\tif (process_connect(transport, 0))\n+\t\treturn transport->vtable->fetch_object_info(transport);\n+\n+\tdie(_(\"object-info requires protocol v2\"));\n+}\n+\n struct push_update_ref_state {\n \tstruct ref *hint;\n \tstruct ref_push_report *report;\n@@ -1330,6 +1339,7 @@ static struct transport_vtable vtable = {\n \t.get_refs_list\t= get_refs_list,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs,\n+\t.fetch_object_info = fetch_object_info_helper,\n \t.push_refs\t= push_refs,\n \t.connect\t= connect_helper,\n \t.disconnect\t= release_helper\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 051f3ab0dc..60db0bedcd 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -45,6 +45,14 @@ struct transport_vtable {\n \t **/\n \tint (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);\n \n+\t/*\n+\t * Fetch object info (only size currently) from remote without\n+\t * downloading the objects.\n+\t *\n+\t * Uses object-info capability of v2 protocol.\n+\t */\n+\tint (*fetch_object_info)(struct transport *transport);\n+\n \t/**\n \t * Push the objects and refs. Send the necessary objects, and\n \t * then, for any refs where peer_ref is set and\ndiff --git a/transport.c b/transport.c\nindex fc144f0aed..9342680531 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -9,6 +9,7 @@\n #include \"hook.h\"\n #include \"pkt-line.h\"\n #include \"fetch-pack.h\"\n+#include \"fetch-object-info.h\"\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"send-pack.h\"\n@@ -432,6 +433,48 @@ static int get_bundle_uri(struct transport *transport)\n \t\t\t\t     transport->bundles, stateless_rpc);\n }\n \n+static int fetch_object_info_via_pack(struct transport *transport)\n+{\n+\tint ret = 0;\n+\tstruct git_transport_data *data = transport->data;\n+\tstruct packet_reader reader;\n+\tstruct object_info_args args = { 0 };\n+\n+\targs.server_options = transport->server_options;\n+\targs.oids = transport->smart_options->object_info_oids;\n+\targs.object_info_options = transport->smart_options->object_info_options;\n+\tstring_list_sort(args.object_info_options);\n+\n+\tconnect_setup(transport, 0);\n+\tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tdata->version = discover_version(&reader);\n+\ttransport->hash_algo = reader.hash_algo;\n+\n+\tret = fetch_object_info(data->version, &args, &reader,\n+\t\t\t\tdata->options.object_info_data,\n+\t\t\t\ttransport->stateless_rpc, data->fd[1]);\n+\n+\tclose(data->fd[0]);\n+\tif (data->fd[1] >= 0)\n+\t\tclose(data->fd[1]);\n+\tif (finish_connect(data->conn))\n+\t\tret = -1;\n+\tdata->conn = NULL;\n+\n+\treturn ret;\n+}\n+\n+int transport_fetch_object_info(struct transport *transport)\n+{\n+\tif (!transport->vtable->fetch_object_info)\n+\t\tdie(_(\"remote does not support object-info\"));\n+\treturn transport->vtable->fetch_object_info(transport);\n+}\n+\n static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t       int nr_heads, struct ref **to_fetch)\n {\n@@ -1004,6 +1047,7 @@ static struct transport_vtable taken_over_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.disconnect\t= disconnect_git\n };\n@@ -1169,6 +1213,7 @@ static struct transport_vtable builtin_smart_vtable = {\n \t.get_refs_list\t= get_refs_via_connect,\n \t.get_bundle_uri = get_bundle_uri,\n \t.fetch_refs\t= fetch_refs_via_pack,\n+\t.fetch_object_info = fetch_object_info_via_pack,\n \t.push_refs\t= git_transport_push,\n \t.connect\t= connect_git,\n \t.disconnect\t= disconnect_git\ndiff --git a/transport.h b/transport.h\nindex 7e5867cffa..a7869d18e0 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -55,6 +55,10 @@ struct git_transport_options {\n \t * common commits to this oidset instead of fetching any packfiles.\n \t */\n \tstruct oidset *acked_commits;\n+\n+\tstruct oid_array *object_info_oids;\n+\tstruct object_info *object_info_data;\n+\tstruct string_list *object_info_options;\n };\n \n enum transport_family {\n@@ -309,6 +313,11 @@ int transport_get_remote_bundle_uri(struct transport *transport);\n const struct git_hash_algo *transport_get_hash_algo(struct transport *transport);\n int transport_fetch_refs(struct transport *transport, struct ref *refs);\n \n+/*\n+ * Fetch the object info from remote\n+ */\n+int transport_fetch_object_info(struct transport *transport);\n+\n /*\n  * If this flag is set, unlocking will avoid to call non-async-signal-safe\n  * functions. This will necessarily leave behind some data structures which\n\n-- \n2.54.0\n"},{"id":"548889","messageId":"20260724-ps-eric-work-rebase-v21-12-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 12/13] cat-file: add remote-object-info to batch-command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:23Z","receivedAt":"2026-07-24T10:54:45Z","isPatch":true,"body":"From: Eric Ju <eric.peijian@gmail.com>\n\nSince the info command in cat-file --batch-command prints object\ninfo for a given object, it is natural to add another command in\ncat-file --batch-command to print object info for a given object\nfrom a remote.\n\nAdd remote-object-info command to cat-file --batch-command.\n\nWhile info takes object ids one at a time, this creates overhead when\nmaking requests to a server. So remote-object-info instead can take\nmultiple object ids at once.\n\nThe cat-file --batch-command command is generally implemented in the\nfollowing manner:\n\n - Receive and parse input from user\n - Call respective function attached to command\n - Get object info, print object info\n\nIn --buffer mode, this changes to:\n\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue\n    - Call respective function attached to command\n    - Get object info, print object info\n\nNotice how the getting and printing of object info is accomplished one\nat a time. As described above, this creates a problem for making\nrequests to a server. Therefore, remote-object-info is implemented in\nthe following manner:\n\n - Receive and parse input from user\n If command is remote-object-info:\n    - Get object info from remote\n    - Loop through and print each object info\n Else:\n    - Call respective function attached to command\n    - Parse input, get object info, print object info\n\nAnd finally for --buffer mode remote-object-info:\n - Receive and parse input from user\n - Store respective function attached to command in a queue\n - After flush, loop through commands in queue:\n    If command is remote-object-info:\n        - Get object info from remote\n        - Loop through and print each object info\n    Else:\n        - Call respective function attached to command\n        - Get object info, print object info\n\nTo summarize, remote-object-info gets object info from the remote and\nthen loops through the object info passed in, printing the info.\n\nIn order for remote-object-info to avoid remote communication\noverhead in the non-buffer mode, the objects are passed in as such:\n\nremote-object-info <remote> <oid> <oid> ... <oid>\n\nrather than\n\nremote-object-info <remote> <oid>\nremote-object-info <remote> <oid>\n...\nremote-object-info <remote> <oid>\n\nPlaceholders in the format are validated against an allow-list of the\natoms the remote path supports: \"objectname\" and \"objectsize\".\nUnsupported atoms expand to an empty string, honoring how for-each-ref\nhandles known but inapplicable atoms.\nWithout this, atoms like %(objecttype) would mark data->info.typep and\nbecause the server only sends size, type_name() would later crash.\nAs extra safety, even outside of the remote path, initialize\nexpand_data's type to OBJ_BAD and handle type_name() returning NULL.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Calvin Wan <calvinwan@google.com>\nSigned-off-by: Eric Ju <eric.peijian@gmail.com>\n[pablo: added the atom allow-list validation]\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n Documentation/git-cat-file.adoc        |  23 +-\n builtin/cat-file.c                     | 181 ++++++++-\n object-file.c                          |  10 +\n odb.h                                  |   3 +\n t/meson.build                          |   1 +\n t/t1017-cat-file-remote-object-info.sh | 719 +++++++++++++++++++++++++++++++++\n 6 files changed, 930 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex 86b9181599..ac3b528c6f 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -169,6 +169,13 @@ info <object>::\n \tPrint object info for object reference `<object>`. This corresponds to the\n \toutput of `--batch-check`.\n \n+remote-object-info <remote> <object>...::\n+\tPrint object info for object references `<object>` at specified\n+\t`<remote>` without downloading objects from the remote.\n+\tRaise an error when the `object-info` capability is not supported by the remote.\n+\tRaise an error when no object references are provided.\n+\tThis command may be combined with `--buffer`.\n+\n flush::\n \tUsed with `--buffer` to execute all preceding commands that were issued\n \tsince the beginning or since the last flush was issued. When `--buffer`\n@@ -301,7 +308,8 @@ one per line, and print information based on the command given. With\n `--batch-command`, the `info` command followed by an object will print\n information about the object the same way `--batch-check` would, and the\n `contents` command followed by an object prints contents in the same way\n-`--batch` would.\n+`--batch` would. The `remote-object-info` command followed by a remote and\n+object IDs prints object info from the remote without downloading the objects.\n \n You can specify the information shown for each object by using a custom\n `<format>`. The `<format>` is copied literally to stdout for each\n@@ -340,8 +348,15 @@ newline. The available atoms are:\n \tafter that first run of whitespace (i.e., the \"rest\" of the\n \tline) are output in place of the `%(rest)` atom.\n \n+The command `remote-object-info` only supports the `%(objectname)` and\n+`%(objectsize)` placeholders. See `CAVEATS` below for more information.\n+\n If no format is specified, the default format is `%(objectname)\n-%(objecttype) %(objectsize)`.\n+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which\n+use `%(objectname) %(objectsize)` because `%(objecttype)` is not supported yet.\n+\n+WARNING: When \"%(objecttype)\" is supported, the default format WILL be unified,\n+so DO NOT RELY on the current default format to stay the same!!!\n \n If `--batch` is specified, or if `--batch-command` is used with the `contents`\n command, the object information is followed by the object contents (consisting\n@@ -438,6 +453,10 @@ scripting purposes.\n CAVEATS\n -------\n \n+Note that only `%(objectname)` and `%(objectsize)` are currently\n+supported by the `remote-object-info` command. Using any other placeholder in\n+the format string will return an empty string in its position.\n+\n Note that the sizes of objects on disk are reported accurately, but care\n should be taken in drawing conclusions about which refs or objects are\n responsible for disk usage. The size of a packed non-delta object may be\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex fc18a92736..b2a1ad6a0f 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -28,6 +28,22 @@\n #include \"promisor-remote.h\"\n #include \"mailmap.h\"\n #include \"write-or-die.h\"\n+#include \"alias.h\"\n+#include \"remote.h\"\n+#include \"transport.h\"\n+\n+/*\n+ * Maximum length for a remote URL. While no universal standard exists,\n+ * 8K is assumed to be a reasonable limit.\n+ */\n+#define MAX_REMOTE_URL_LEN (8 * 1024)\n+\n+/* Maximum number of objects allowed in a single remote-object-info request. */\n+#define MAX_ALLOWED_OBJ_LIMIT 10000\n+\n+/* Maximum input size permitted for the remote-object-info command. */\n+#define MAX_REMOTE_OBJ_INFO_LINE \\\n+\t(MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))\n \n enum batch_mode {\n \tBATCH_MODE_CONTENTS,\n@@ -316,8 +332,19 @@ struct expand_data {\n \t * optimized out.\n \t */\n \tunsigned skip_object_info : 1;\n+\n+\t/*\n+\t * Flags about when an object info is being fetched from remote.\n+\t */\n+\tunsigned is_remote:1;\n+};\n+\n+#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n+\n+static const char *remote_object_info_atoms[] = {\n+\t\"objectname\",\n+\t\"objectsize\",\n };\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID }\n \n static int is_atom(const char *atom, const char *s, int slen)\n {\n@@ -328,14 +355,31 @@ static int is_atom(const char *atom, const char *s, int slen)\n static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n+\tif (data->is_remote) {\n+\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n+\t\tfor (i = 0; i < allowed_nr; i++)\n+\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * On remote, skip unsupported atoms returning an empty sb,\n+\t\t * honoring how for-each-ref handles known but inapplicable\n+\t\t * atoms (e.g. %(tagger)).\n+\t\t */\n+\t\tif (i == allowed_nr)\n+\t\t\treturn 1;\n+\t}\n+\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n \t\t\tstrbuf_add_oid_hex(sb, &data->oid);\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n-\t\tif (data->mark_query)\n+\t\tif (data->mark_query) {\n \t\t\tdata->info.typep = &data->type;\n-\t\telse\n-\t\t\tstrbuf_addstr(sb, type_name(data->type));\n+\t\t} else {\n+\t\t\tconst char *t = type_name(data->type);\n+\t\t\tstrbuf_addstr(sb, t ? t : \"\");\n+\t\t}\n \t} else if (is_atom(\"objectsize\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.sizep = &data->size;\n@@ -635,6 +679,65 @@ static void batch_one_object(const char *obj_name,\n \tobject_context_release(&ctx);\n }\n \n+static int get_remote_info(int argc,\n+\t\t\t   const char **argv,\n+\t\t\t   struct object_info **remote_object_info,\n+\t\t\t   struct oid_array *object_info_oids)\n+{\n+\tint retval = 0;\n+\tstruct remote *remote = NULL;\n+\tstruct object_id oid;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n+\tstruct transport *gtransport;\n+\n+\tremote = remote_get(argv[0]);\n+\tif (!remote)\n+\t\tdie(_(\"must supply valid remote when using remote-object-info\"));\n+\n+\toid_array_clear(object_info_oids);\n+\tfor (size_t i = 1; i < argc; i++) {\n+\t\tif (get_oid_hex(argv[i], &oid)) {\n+\t\t\tsize_t len = strlen(argv[i]);\n+\n+\t\t\tif (len < the_hash_algo->hexsz && len >= 4) {\n+\t\t\t\tsize_t j;\n+\t\t\t\tfor (j = 0; j < len; j++)\n+\t\t\t\t\tif (!isxdigit(argv[i][j]))\n+\t\t\t\t\t\tbreak;\n+\t\t\t\tif (j == len)\n+\t\t\t\t\tdie(_(\"remote-object-info does not support \"\n+\t\t\t\t\t      \"short oids, %d characters required\"),\n+\t\t\t\t\t    (int)the_hash_algo->hexsz);\n+\t\t\t}\n+\t\t\tdie(_(\"not a valid object name '%s'\"), argv[i]);\n+\t\t}\n+\t\toid_array_append(object_info_oids, &oid);\n+\t}\n+\n+\tif (!object_info_oids->nr)\n+\t\tdie(_(\"remote-object-info requires objects\"));\n+\n+\tgtransport = transport_get(remote, NULL);\n+\n+\tif (!gtransport->smart_options) {\n+\t\tretval = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n+\tgtransport->smart_options->object_info_oids = object_info_oids;\n+\n+\tstring_list_append(&object_info_options, \"size\");\n+\n+\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\tretval = transport_fetch_object_info(gtransport);\n+cleanup:\n+\tstring_list_clear(&object_info_options, 0);\n+\ttransport_disconnect(gtransport);\n+\treturn retval;\n+}\n+\n struct object_cb_data {\n \tstruct batch_options *opt;\n \tstruct expand_data *expand;\n@@ -716,6 +819,73 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+static void parse_cmd_remote_object_info(struct batch_options *opt,\n+\t\t\t\t\t const char *line, struct strbuf *output,\n+\t\t\t\t\t struct expand_data *data)\n+{\n+\tint count;\n+\tconst char **argv;\n+\tchar *line_to_split;\n+\tstruct object_info *remote_object_info = NULL;\n+\tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tconst char *saved_format = opt->format;\n+\n+\tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n+\t\tdie(_(\"remote-object-info command too long\"));\n+\t/*\n+\t * TODO: Use the default format once %(objecttype) is supported.\n+\t */\n+\tif (!opt->format)\n+\t\topt->format = \"%(objectname) %(objectsize)\";\n+\n+\tline_to_split = xstrdup(line);\n+\tcount = split_cmdline(line_to_split, &argv);\n+\tif (count < 0)\n+\t\tdie(_(\"remote-object-info: failed to parse command line: %s\"),\n+\t\t    split_cmdline_strerror(count));\n+\tif (count - 1 > MAX_ALLOWED_OBJ_LIMIT)\n+\t\tdie(_(\"remote-object-info supports at most %d objects\"),\n+\t\t    MAX_ALLOWED_OBJ_LIMIT);\n+\n+\tif (get_remote_info(count, argv, &remote_object_info,\n+\t\t\t    &object_info_oids))\n+\t\tdie(_(\"failed to get object info from the remote: %s\"), argv[0]);\n+\n+\tdata->skip_object_info = 1;\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\t\tdata->oid = object_info_oids.oid[i];\n+\n+\t\tif (remote_object_info[i].unrecognized) {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid),\n+\t\t\t\t\t     &data->oid, \"missing\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (remote_object_info[i].sizep) {\n+\t\t\t/*\n+\t\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t\t * information from server without downloading them.\n+\t\t\t */\n+\t\t\tdata->size = *remote_object_info[i].sizep;\n+\t\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\t\tdata->is_remote = 1;\n+\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\t\tdata->is_remote = 0;\n+\t\t} else {\n+\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n+\t\t}\n+\t}\n+\tdata->skip_object_info = 0;\n+\topt->format = saved_format;\n+\n+\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n+\t\tfree_object_info_contents(&remote_object_info[i]);\n+\tfree(line_to_split);\n+\tfree(argv);\n+\tfree(remote_object_info);\n+\toid_array_clear(&object_info_oids);\n+}\n+\n static void dispatch_calls(struct batch_options *opt,\n \t\tstruct strbuf *output,\n \t\tstruct expand_data *data,\n@@ -746,9 +916,10 @@ static const struct parse_cmd {\n \tunsigned takes_args;\n } commands[] = {\n \t{ \"contents\", parse_cmd_contents, 1 },\n-\t{ \"info\", parse_cmd_info, 1 },\n \t{ \"flush\", NULL, 0 },\n+\t{ \"info\", parse_cmd_info, 1 },\n \t{ \"mailmap\", parse_cmd_mailmap, 1 },\n+\t{ \"remote-object-info\", parse_cmd_remote_object_info, 1 },\n };\n \n static void batch_objects_command(struct batch_options *opt,\ndiff --git a/object-file.c b/object-file.c\nindex 7ff2b730ac..c5809db598 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1740,3 +1740,13 @@ int odb_transaction_files_begin(struct odb_source *source,\n \n \treturn 0;\n }\n+\n+void free_object_info_contents(struct object_info *object_info)\n+{\n+\tif (!object_info)\n+\t\treturn;\n+\tfree(object_info->typep);\n+\tfree(object_info->sizep);\n+\tfree(object_info->disk_sizep);\n+\tfree(object_info->delta_base_oid);\n+}\ndiff --git a/odb.h b/odb.h\nindex b7bc0ee844..3f7c483656 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -635,4 +635,7 @@ void parse_alternates(const char *string,\n \t\t      const char *relative_base,\n \t\t      struct strvec *out);\n \n+/* Free pointers inside of object_info, but not object_info itself */\n+void free_object_info_contents(struct object_info *object_info);\n+\n #endif /* ODB_H */\ndiff --git a/t/meson.build b/t/meson.build\nindex 8ae6ab6c5f..10241e3dcc 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -171,6 +171,7 @@ integration_tests = [\n   't1014-read-tree-confusing.sh',\n   't1015-read-index-unmerged.sh',\n   't1016-compatObjectFormat.sh',\n+  't1017-cat-file-remote-object-info.sh',\n   't1020-subdirectory.sh',\n   't1022-read-tree-partial-clone.sh',\n   't1050-large.sh',\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nnew file mode 100755\nindex 0000000000..edc20394d8\n--- /dev/null\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -0,0 +1,719 @@\n+#!/bin/sh\n+\n+test_description='git cat-file --batch-command with remote-object-info command'\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-cat-file.sh\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n+\n+unstored_content=\"Hello Git\"\n+unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdin)\n+\n+# This is how we get 13:\n+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where\n+# file mode is 100644, which is 6 characters;\n+# file name is hello, which is 5 characters\n+# a space is 1 character and a null is 1 character\n+tree_size=$(($(test_oid rawsz) + 13))\n+\n+commit_message=\"Initial commit\"\n+\n+# This is how we get 137:\n+# 137 = <tree header> + <a_space> + <a newline> +\n+# <Author line> + <a newline> +\n+# <Committer line> + <a newline> +\n+# <a newline> +\n+# <commit message length>\n+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,\n+# to get 177, 2. then deduct 40 hex characters to get 137\n+commit_size=$(($(test_oid hexsz) + 137))\n+\n+tag_header_without_oid=\"type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_header_without_timestamp=\"object $hello_oid\n+$tag_header_without_oid\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0 +0000\n+\n+$tag_description\"\n+\n+tag_oid=$(echo_without_newline \"$tag_content\" | git hash-object -t tag --stdin -w)\n+tag_size=$(strlen \"$tag_content\")\n+\n+set_transport_variables () {\n+\thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\ttree_oid=$(git -C \"$1\" write-tree)\n+\tcommit_oid=$(echo_without_newline \"$commit_message\" | git -C \"$1\" commit-tree $tree_oid)\n+\ttag_oid=$(echo_without_newline \"$tag_content\" | git -C \"$1\" hash-object -t tag --stdin -w)\n+\ttag_size=$(strlen \"$tag_content\")\n+}\n+\n+# This section tests --batch-command with remote-object-info command\n+# Since \"%(objecttype)\" is currently not supported by the command remote-object-info ,\n+# the filters are set to \"%(objectname) %(objectsize)\" in some test cases.\n+\n+# Test --batch-command remote-object-info with 'git://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+. \"$TEST_DIRECTORY\"/lib-git-daemon.sh\n+start_git_daemon --export-all\n+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent\n+\n+test_expect_success 'create repo to be served by git-daemon' '\n+\tgit init \"$daemon_parent\" &&\n+\techo_without_newline \"$hello_content\" > $daemon_parent/hello &&\n+\tgit -C \"$daemon_parent\" update-index --add hello &&\n+\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true &&\n+\tgit clone \"$GIT_DAEMON_URL/parent\" -n \"$daemon_parent/daemon_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not change the default format of info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tlocal_content=\"local object\" &&\n+\t\tlocal_oid=$(echo_without_newline \"$local_content\" | git hash-object -w --stdin) &&\n+\t\tlocal_size=$(strlen \"$local_content\") &&\n+\n+\t\techo \"$local_oid blob $local_size\" >expect &&\n+\t\techo \"$hello_oid $hello_size\" >>expect &&\n+\t\techo \"$local_oid blob $local_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tinfo $local_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tinfo $local_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info git://' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid\n+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not support short oids' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_short_oid\n+\t\tEOF\n+\t\ttest_grep \"does not support short oids\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info does not die on missing oid like info' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tgit cat-file --batch-command >local <<-EOF &&\n+\t\tinfo $unstored_oid\n+\t\tEOF\n+\t\tgit cat-file --batch-command >remote <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $unstored_oid\n+\t\tEOF\n+\t\ttest_cmp local remote\n+\t)\n+'\n+\n+# This tests depends on %(objecttype) not being supported yet, once supported\n+# it needs to be updated.\n+test_expect_success 'unsupported placeholder on remote returns empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"\" >expect &&\n+\t\tgit cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'git://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo false &&\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$daemon_parent\" config transfer.advertiseobjectinfo true\n+\n+\t)\n+'\n+\n+stop_git_daemon\n+\n+# Test --batch-command remote-object-info with 'file://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+# shellcheck disable=SC2016\n+test_expect_success 'create repo to be served by file:// transport' '\n+\tgit init server &&\n+\tgit -C server config protocol.version 2 &&\n+\tgit -C server config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > server/hello &&\n+\tgit -C server update-index --add hello &&\n+\tgit clone -n \"file://$(pwd)/server\" file_client_empty\n+'\n+\n+test_expect_success 'batch-command remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid\n+\t\tremote-object-info \"file://${server_path}\" $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid\n+\t\tremote-object-info \"file://${server_path}\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info file://' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid\n+\t\tremote-object-info \"file://${server_path}\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tcd file_client_empty &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid missing\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid missing\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info \\\"file://${server_path}\\\" $hello_oid $tree_oid\n+remote-object-info \\\"file://${server_path}\\\" $commit_oid $tag_oid\n+info $hello_oid\n+info $tree_oid\n+info $commit_oid\n+info $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'file://' and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '\n+\t(\n+\t\tset_transport_variables \"server\" &&\n+\t\tserver_path=\"$(pwd)/server\" &&\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"file://${server_path}\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"${server_path}\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# Test --batch-command remote-object-info with 'http://' transport with\n+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability\n+\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+test_expect_success 'create repo to be served by http:// transport' '\n+\tgit init \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config http.receivepack true &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true &&\n+\techo_without_newline \"$hello_content\" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&\n+\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" update-index --add hello &&\n+\tgit clone \"$HTTPD_URL/smart/http_parent\" -n \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\"\n+'\n+\n+test_expect_success 'batch-command remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// one line' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command --buffer remote-object-info http://' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\t# These results prove remote-object-info can get object info from the remote\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\t# These results prove remote-object-info did not download objects from the remote\n+\t\techo \"$hello_oid missing\" >>expect &&\n+\t\techo \"$tree_oid missing\" >>expect &&\n+\t\techo \"$commit_oid missing\" >>expect &&\n+\t\techo \"$tag_oid missing\" >>expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize)\" --buffer >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tinfo $hello_oid\n+\t\tinfo $tree_oid\n+\t\tinfo $commit_oid\n+\t\tinfo $tag_oid\n+\t\tflush\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\techo \"$hello_oid $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tgit cat-file --batch-command >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty\" &&\n+\n+\t\tprintf \"%s\\0\" \"$hello_oid $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_size\" >>expect &&\n+\n+\t\tbatch_input=\"remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid\n+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid\n+\" &&\n+\t\techo_without_newline_nul \"$batch_input\" >commands_null_delimited &&\n+\n+\t\tgit cat-file --batch-command -Z < commands_null_delimited >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\techo \"$hello_oid \" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname) %(objectsize:disk)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\techo \"\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(deltabase)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info requires protocol v2\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on malformed OID with default filter' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tmalformed_object_id=\"this_id_is_not_valid\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $malformed_object_id\n+\t\tEOF\n+\t\ttest_grep \"not a valid object name '$malformed_object_id'\" err\n+\t)\n+'\n+\n+test_expect_success 'remote-object-info fails on not providing OID' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\"\n+\t\tEOF\n+\t\ttest_grep \"remote-object-info requires objects\" err\n+\t)\n+'\n+\n+\n+# Test --batch-command remote-object-info with 'http://' transport and\n+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '\n+\t(\n+\t\tset_transport_variables \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" &&\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo false &&\n+\n+\t\ttest_must_fail git cat-file --batch-command=\"%(objectname) %(objectsize)\" 2>err <<-EOF &&\n+\t\tremote-object-info \"$HTTPD_URL/smart/http_parent\" $hello_oid $tree_oid $commit_oid $tag_oid\n+\t\tEOF\n+\t\ttest_grep \"object-info capability is not enabled on the server\" err &&\n+\n+\t\t# revert server state back\n+\t\tgit -C \"$HTTPD_DOCUMENT_ROOT_PATH/http_parent\" config transfer.advertiseobjectinfo true\n+\t)\n+'\n+\n+# DO NOT add non-httpd-specific tests here, because the last part of this\n+# test script is only executed when httpd is available and enabled.\n+\n+test_done\n\n-- \n2.54.0\n"},{"id":"548888","messageId":"20260724-ps-eric-work-rebase-v21-13-ba67f024fdff@gmail.com","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"[PATCH GSoC v21 13/13] cat-file: make remote-object-info allow-list adapt to the server","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-24T10:54:24Z","receivedAt":"2026-07-24T10:54:47Z","isPatch":true,"body":"The static allow-list in expand_atom() is hardcoded to allow only\n\"objectname\" and \"objectsize\" for remote queries. This works because,\nup to this point, servers will either support object-info with name\nand size or they do not support them at all.\n\nAs object-info gains new capabilities, we cannot expect different\nservers with different Git versions to have the same object-info\ncapabilities. Therefore, the client needs to adapt its allow-list to\nwhat the server advertises.\n\nThe client now:\n\n1. Requests the protocol option that the placeholder refers to (i.e.\n   \"size\" for \"%(objectsize)\").\n\n2. Drops any requested option that the server does not advertise in\n   fetch_object_info().\n\n3. Maps the remaining advertised options back to their placeholders and\n   populates remote_allowed_atoms.\n\n4. Uses remote_allowed_atoms in expand_atom(), preserving the previous\n   behavior for supported placeholders.\n\nFor example, if the client requests \"%(objectsize) %(objecttype)\" and\nthe server only supports 'size', then the client only requests 'size'.\nThe server returns the size (i.e \"42\") \"%(objectsize)\" is expanded\nnormally while \"%(objecttype)\" expands to an empty string:\n\n\t\"42 \"\n\nNote that the empty string expansion is only for known but unsupported\nplaceholders. \"%(objectcolor)\" which doesn't exist would die().\n\nThis honors what for-each-ref does for known but inapplicable atoms\n(placeholders).\n\nMove object_info_options out of get_remote_info() so the caller which\nhas data can select what options will be requested instead of requesting\nalways size.\n\nMove batch_object_write() out so output is always produced.\nIf there are no supported attributes, the output is a blank line.\n\nInclude \"type\" in the object_info_options even though the client does\nnot yet know how to parse the server's \"type\" capability.\n\nAs a result, \"type\" is always filtered out, allowing the tests to verify\nthat known but unsupported placeholders expand to an empty string.\n\nSince the filter removes options by swapping with the last element,\nthe list is no longer kept sorted. Drop the pre-sort in\nfetch_object_info_via_pack() and use the unsorted string_list lookup\nfor the response header. This has no effect in performance as the list\ncan only be two entries long ('size' and 'type').\n\nMentored-by: Karthik Nayak <karthik.188@gmail.com>\nMentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c                     | 96 ++++++++++++++++++++++------------\n fetch-object-info.c                    | 20 ++++++-\n fetch-object-info.h                    |  3 ++\n t/t1017-cat-file-remote-object-info.sh | 28 ++++++++++\n transport.c                            |  1 -\n 5 files changed, 113 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex b2a1ad6a0f..884b6d5ad3 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -337,15 +337,20 @@ struct expand_data {\n \t * Flags about when an object info is being fetched from remote.\n \t */\n \tunsigned is_remote:1;\n-};\n-\n-#define EXPAND_DATA_INIT  { .mode = S_IFINVALID, .type = OBJ_BAD }\n \n-static const char *remote_object_info_atoms[] = {\n-\t\"objectname\",\n-\t\"objectsize\",\n+\t/*\n+\t * List of atoms (i.e. \"objectsize\") that the server supports. Built\n+\t * from the server's object-info advertised capabilities.\n+\t */\n+\tstruct string_list remote_allowed_atoms;\n };\n \n+#define EXPAND_DATA_INIT  {  \\\n+\t.mode = S_IFINVALID, \\\n+\t.type = OBJ_BAD,     \\\n+\t.remote_allowed_atoms = STRING_LIST_INIT_NODUP, \\\n+}\n+\n static int is_atom(const char *atom, const char *s, int slen)\n {\n \tint alen = strlen(atom);\n@@ -356,17 +361,12 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\t       struct expand_data *data)\n {\n \tif (data->is_remote) {\n-\t\tsize_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);\n-\t\tfor (i = 0; i < allowed_nr; i++)\n-\t\t\tif (is_atom(remote_object_info_atoms[i], atom, len))\n+\t\tsize_t i;\n+\t\tfor (i = 0; i < data->remote_allowed_atoms.nr; i++)\n+\t\t\tif (is_atom(data->remote_allowed_atoms.items[i].string,\n+\t\t\t\t    atom, len))\n \t\t\t\tbreak;\n-\n-\t\t/*\n-\t\t * On remote, skip unsupported atoms returning an empty sb,\n-\t\t * honoring how for-each-ref handles known but inapplicable\n-\t\t * atoms (e.g. %(tagger)).\n-\t\t */\n-\t\tif (i == allowed_nr)\n+\t\tif (i == data->remote_allowed_atoms.nr)\n \t\t\treturn 1;\n \t}\n \n@@ -682,12 +682,12 @@ static void batch_one_object(const char *obj_name,\n static int get_remote_info(int argc,\n \t\t\t   const char **argv,\n \t\t\t   struct object_info **remote_object_info,\n-\t\t\t   struct oid_array *object_info_oids)\n+\t\t\t   struct oid_array *object_info_oids,\n+\t\t\t   struct string_list *object_info_options)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n \tstruct object_id oid;\n-\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tstruct transport *gtransport;\n \n \tremote = remote_get(argv[0]);\n@@ -727,13 +727,10 @@ static int get_remote_info(int argc,\n \tCALLOC_ARRAY(*remote_object_info, object_info_oids->nr);\n \tgtransport->smart_options->object_info_oids = object_info_oids;\n \n-\tstring_list_append(&object_info_options, \"size\");\n-\n-\tgtransport->smart_options->object_info_options = &object_info_options;\n+\tgtransport->smart_options->object_info_options = object_info_options;\n \tgtransport->smart_options->object_info_data = *remote_object_info;\n \tretval = transport_fetch_object_info(gtransport);\n cleanup:\n-\tstring_list_clear(&object_info_options, 0);\n \ttransport_disconnect(gtransport);\n \treturn retval;\n }\n@@ -819,6 +816,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,\n \t\tload_mailmap();\n }\n \n+struct protocol_placeholder_entry {\n+\tconst char *option;\n+\tconst char *atom;\n+};\n+\n+static const struct protocol_placeholder_entry remote_atom_map[] = {\n+\t{\"size\", \"objectsize\"},\n+\t{\"type\", \"objecttype\"},\n+\t/*\n+\t * Add new protocol options here. Even if the server doesn't support\n+\t * them the allow_list will drop them if the server doesn't advertise\n+\t * them.\n+\t */\n+};\n+\n static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\t\t\t const char *line, struct strbuf *output,\n \t\t\t\t\t struct expand_data *data)\n@@ -828,6 +840,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct object_info *remote_object_info = NULL;\n \tstruct oid_array object_info_oids = OID_ARRAY_INIT;\n+\tstruct string_list object_info_options = STRING_LIST_INIT_NODUP;\n \tconst char *saved_format = opt->format;\n \n \tif (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n@@ -847,10 +860,22 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdie(_(\"remote-object-info supports at most %d objects\"),\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n+\tif (data->info.sizep)\n+\t\tstring_list_append(&object_info_options, \"size\");\n+\tif (data->info.typep)\n+\t\tstring_list_append(&object_info_options, \"type\");\n+\n \tif (get_remote_info(count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids))\n+\t\t\t    &object_info_oids, &object_info_options))\n \t\tdie(_(\"failed to get object info from the remote: %s\"), argv[0]);\n \n+\tstring_list_clear(&data->remote_allowed_atoms, 0);\n+\tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n+\tfor (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)\n+\t\tif (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))\n+\t\t\tstring_list_append(&data->remote_allowed_atoms,\n+\t\t\t\t\t   remote_atom_map[i].atom);\n+\n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n \t\tdata->oid = object_info_oids.oid[i];\n@@ -861,25 +886,29 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t\tcontinue;\n \t\t}\n \n+\t\t/*\n+\t\t * When reaching here, it means remote-object-info can retrieve\n+\t\t * information from server without downloading them.\n+\t\t */\n \t\tif (remote_object_info[i].sizep) {\n-\t\t\t/*\n-\t\t\t * When reaching here, it means remote-object-info can retrieve\n-\t\t\t * information from server without downloading them.\n-\t\t\t */\n \t\t\tdata->size = *remote_object_info[i].sizep;\n-\t\t\topt->batch_mode = BATCH_MODE_INFO;\n-\t\t\tdata->is_remote = 1;\n-\t\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n-\t\t\tdata->is_remote = 0;\n-\t\t} else {\n-\t\t\treport_object_status(opt, oid_to_hex(&data->oid), &data->oid, \"missing\");\n \t\t}\n+\n+\t\tif (remote_object_info[i].typep) {\n+\t\t\tdata->type = *remote_object_info[i].typep;\n+\t\t}\n+\n+\t\topt->batch_mode = BATCH_MODE_INFO;\n+\t\tdata->is_remote = 1;\n+\t\tbatch_object_write(argv[i + 1], output, opt, data, NULL, 0);\n+\t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n \topt->format = saved_format;\n \n \tfor (size_t i = 0; i < object_info_oids.nr; i++)\n \t\tfree_object_info_contents(&remote_object_info[i]);\n+\tstring_list_clear(&object_info_options, 0);\n \tfree(line_to_split);\n \tfree(argv);\n \tfree(remote_object_info);\n@@ -1138,6 +1167,7 @@ static int batch_objects(struct batch_options *opt)\n  cleanup:\n \tstrbuf_release(&input);\n \tstrbuf_release(&output);\n+\tstring_list_clear(&data.remote_allowed_atoms, 0);\n \tcfg->warn_on_object_refname_ambiguity = save_warning;\n \treturn retval;\n }\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 30475a1e87..ba7e179c44 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -55,6 +55,24 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \tcase protocol_v2:\n \t\tif (!server_supports_v2(\"object-info\"))\n \t\t\tdie(_(\"object-info capability is not enabled on the server\"));\n+\t\t/*\n+\t\t * When removing an element from the list it gets swapped by the\n+\t\t * last element, iterate backwards to prevent elements skipping\n+\t\t * evaluation.\n+\t\t *\n+\t\t * object_info_options->nr can be safely casted without overflow\n+\t\t * because the number of options is a small known number (the\n+\t\t * supported placeholders which currently are size and type).\n+\t\t */\n+\t\tfor (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)\n+\t\t\tif (!server_supports_feature(\"object-info\",\n+\t\t\t\t\t\t     args->object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(args->object_info_options, i, 0);\n+\n+\t\t/*\n+\t\t * Even if no options are left, we still send the oid so we get\n+\t\t * at least an existence check.\n+\t\t */\n \t\tsend_object_info_request(fd_out, args);\n \t\tbreak;\n \tcase protocol_v1:\n@@ -71,7 +89,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\t\treturn -1;\n \t\t}\n \n-\t\tif (!string_list_has_string(args->object_info_options, reader->line))\n+\t\tif (!unsorted_string_list_has_string(args->object_info_options, reader->line))\n \t\t\treturn -1;\n \n \t\tif (!strcmp(reader->line, \"size\")) {\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 31aad98408..269cebb3f7 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -14,6 +14,9 @@ struct object_info;\n /*\n  * Sends git-cat-file object-info command into the request buf and read the\n  * results from packets.\n+ *\n+ * Modifies args->object_info_options, on return it contains only the supported\n+ * options by the server.\n  */\n int fetch_object_info(enum protocol_version version, struct object_info_args *args,\n \t\t      struct packet_reader *reader, struct object_info *object_info_data,\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex edc20394d8..116862f9d0 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -271,6 +271,34 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '\n \t)\n '\n \n+test_expect_success 'requesting only objectname echoes back' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo $hello_oid >expect &&\n+\t\tgit cat-file --batch-command=\"%(objectname)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'objectname goes through existence check' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\techo \"$unstored_oid missing\" >expect &&\n+\n+\t\tgit cat-file --batch-command=\"%(objectname)\" >actual <<-EOF &&\n+\t\tremote-object-info \"$GIT_DAEMON_URL/parent\" $unstored_oid\n+\t\tEOF\n+\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n # Test --batch-command remote-object-info with 'git://' and\n # transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability\n test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '\ndiff --git a/transport.c b/transport.c\nindex 9342680531..f0a6a45547 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -443,7 +443,6 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \targs.server_options = transport->server_options;\n \targs.oids = transport->smart_options->object_info_oids;\n \targs.object_info_options = transport->smart_options->object_info_options;\n-\tstring_list_sort(args.object_info_options);\n \n \tconnect_setup(transport, 0);\n \tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n\n-- \n2.54.0\n"},{"id":"548897","messageId":"xmqqtspo8jwl.fsf@gitster.g","threadId":"61695","inReplyTo":"20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com","subject":"Re: [PATCH GSoC v21 00/13] cat-file: add remote-object-info to batch-command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-24T15:52:26Z","receivedAt":"2026-07-24T15:52:30Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> Changes in v21:\n> - Dropped unnecessary change in git-cat-file.adoc\n> - Fixed EXPAND_DATA_INIT macro formating\n> - typo and blank line before WARNING:\n> ...\n> Range-diff versus v20:\n\nAll looks good.  Let's mark it for 'next' now.\n"}]}