{"thread":{"id":"66063","subject":"[PATCH GSoC 0/5] cat-file: extend remote-object-info to support %(objecttype)","startedAt":"2026-07-25T11:55:33Z","lastAt":"2026-08-11T07:43:14Z","messageCount":112,"participants":["Pablo Sabater","Chandra Pratap","Junio C Hamano","Karthik Nayak","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"548958","messageId":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","threadId":"66063","inReplyTo":null,"subject":"[PATCH GSoC 0/5] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-25T11:55:01Z","receivedAt":"2026-07-25T11:55:33Z","isPatch":true,"body":"\"%(objecttype)\" is already known by the client's allow-list, but neither\nthe client nor the server knows how to handle type. This series continues\nthe work for git cat-file --batch-command extending remote-object-info\nto support \"%(objecttype)\" end to end. It is based on its predecessor\nseries \"cat-file: add remote-object-info to batch-command\" [1].\n\nType is the last property that is identical on the server and on the\nclient once fetched.\n\nWhether to support more metadata such as:\n\n- objectsize:disk\n- deltabase\n- objectmode (needs context)\n\nshould be discussed, unlike size and type, the rest depend on how things\nare packed on the server and on what the client already has, so they\ncannot reliably match local once fetched. IMO they are not worth\nsupporting, I can't find a use case for them.\n\nAdding new placeholders has become trivial.\nTo add a new placeholder, follow the steps in this series and add it to\nthe client's allow-list at 'builtin/cat-file.c'.\n\nBased-on: <20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com>\n\nGithub CI: https://github.com/pabloosabaterr/git/actions/runs/30155586279\n\n[1]: https://lore.kernel.org/git/20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com/\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nPablo Sabater (5):\n      protocol-caps: add type support to object-info\n      fetch-object-info: parse type from server response\n      fetch-object-info: request all supported options dynamically\n      serve: advertise type capability\n      cat-file: unify default format\n\n Documentation/git-cat-file.adoc        | 17 +++-----\n Documentation/gitprotocol-v2.adoc      | 15 +++++--\n builtin/cat-file.c                     |  7 ---\n fetch-object-info.c                    | 23 +++++++---\n protocol-caps.c                        | 21 +++++++--\n serve.c                                |  4 +-\n t/t1017-cat-file-remote-object-info.sh | 80 +++++++++++++---------------------\n t/t5701-git-serve.sh                   | 27 ++++++++++++\n 8 files changed, 113 insertions(+), 81 deletions(-)\n\n\n---\nbase-commit: 71e19e8d2713f385c3fcef59cf6f29bcbd93d91f\nchange-id: 20260724-objecttype-support-ea1ef6941d07\n\n"},{"id":"548959","messageId":"20260725-objecttype-support-v1-1-2d4ca3bbabf1@gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"[PATCH GSoC 1/5] protocol-caps: add type support to object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-25T11:55:02Z","receivedAt":"2026-07-25T11:55:34Z","isPatch":true,"body":"Teach the server-side object-info handler to accept type as a requested\nfield. When the client includes type in its object-info request, the\nserver returns the requested object type.\n\nWhile at it, fix requested_info->size bit field style.\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 protocol-caps.c      | 21 ++++++++++++++++++---\n t/t5701-git-serve.sh | 27 +++++++++++++++++++++++++++\n 2 files changed, 45 insertions(+), 3 deletions(-)\n\ndiff --git a/protocol-caps.c b/protocol-caps.c\nindex 02261be14d..5531d388f0 100644\n--- a/protocol-caps.c\n+++ b/protocol-caps.c\n@@ -11,7 +11,8 @@\n #include \"strbuf.h\"\n \n struct requested_info {\n-\tunsigned size : 1;\n+\tunsigned size:1;\n+\tunsigned type:1;\n };\n \n /*\n@@ -73,15 +74,20 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \tif (info->size)\n \t\tpacket_writer_write(writer, \"size\");\n \n+\tif (info->type)\n+\t\tpacket_writer_write(writer, \"type\");\n+\n \tfor_each_string_list_item (item, oid_str_list) {\n \t\tconst char *oid_str = item->string;\n \t\tstruct object_id oid;\n \t\tsize_t object_size;\n+\t\tenum object_type object_type;\n \n \t\tif (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n \t\t\tpacket_writer_error(\n \t\t\t\twriter,\n-\t\t\t\t\"object-info: protocol error, expected to get oid, not '%s'\",\n+\t\t\t\t\"object-info: protocol error, expected to get \"\n+\t\t\t\t\"oid, not '%s'\",\n \t\t\t\toid_str);\n \t\t\tcontinue;\n \t\t}\n@@ -93,7 +99,8 @@ static void send_info(struct repository *r, struct packet_writer *writer,\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\tobject_type = get_object_info(r->objects, &oid, &object_size);\n+\t\tif (object_type <= OBJ_NONE) {\n \t\t\tstrbuf_addstr(&send_buffer, \" \");\n \t\t\tgoto write;\n \t\t}\n@@ -103,6 +110,9 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \t\t\t\t    (uintmax_t)object_size);\n \t\t}\n \n+\t\tif (info->type)\n+\t\t\tstrbuf_addf(&send_buffer, \" %s\", type_name(object_type));\n+\n write:\n \t\tpacket_writer_write(writer, \"%s\", send_buffer.buf);\n \t\tstrbuf_reset(&send_buffer);\n@@ -124,6 +134,11 @@ int cap_object_info(struct repository *r, struct packet_reader *request)\n \t\t\tcontinue;\n \t\t}\n \n+\t\tif (!strcmp(\"type\", request->line)) {\n+\t\t\tinfo.type = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif (parse_oid(request->line, &oid_str_list))\n \t\t\tcontinue;\n \ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 9a575aa098..d7c93b5b55 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -366,6 +366,33 @@ test_expect_success 'basics of object-info' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'type' '\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+\ttype\n+\toid $(git rev-parse two:two.t)\n+\toid $(git rev-parse two:two.t)\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tsize\n+\ttype\n+\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs) blob\n+\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs) blob\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 'bare OID request' '\n \ttest_config transfer.advertiseObjectInfo true &&\n \n\n-- \n2.54.0\n\n"},{"id":"548960","messageId":"20260725-objecttype-support-v1-2-2d4ca3bbabf1@gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"[PATCH GSoC 2/5] fetch-object-info: parse type from server response","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-25T11:55:03Z","receivedAt":"2026-07-25T11:55:35Z","isPatch":true,"body":"The server can handle type requests but does not advertise the\ncapability yet. Prepare the client to know how to parse the server\nresponse once the server advertises the capability.\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 fetch-object-info.c | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex ba7e179c44..cf6b94afb8 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -50,6 +50,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\t      const int stateless_rpc, const int fd_out)\n {\n \tint size_index = -1;\n+\tint type_index = -1;\n \n \tswitch (version) {\n \tcase protocol_v2:\n@@ -101,8 +102,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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 if (!strcmp(reader->line, \"type\")) {\n+\t\t\ttype_index = (int)i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].typep =\n+\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].typep));\n \t\t} else {\n-\t\t\tBUG(\"only size is supported\");\n+\t\t\tBUG(\"unexpected object-info option: %s\", reader->line);\n \t\t}\n \t}\n \n@@ -148,6 +154,10 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tif (type_index >= 0)\n+\t\t\t*object_info_data[i].typep =\n+\t\t\t\ttype_from_string(object_info_values.items[type_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-- \n2.54.0\n\n"},{"id":"548961","messageId":"20260725-objecttype-support-v1-3-2d4ca3bbabf1@gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"[PATCH GSoC 3/5] fetch-object-info: request all supported options dynamically","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-25T11:55:04Z","receivedAt":"2026-07-25T11:55:36Z","isPatch":true,"body":"In send_object_info_request(), size is hardcoded to be the only option\nsent. In order to support type and future capabilities, replace the\nhardcoded size with a loop that requests everything on\nobject_info_options list.\n\nThis is safe because the list has already been trimmed previously in\nfetch_object_info() to only contain options that the server supports.\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 fetch-object-info.c | 11 +++++++----\n 1 file changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex cf6b94afb8..e5cfdafe68 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -15,10 +15,13 @@ static void send_object_info_request(const int fd_out, struct object_info_args *\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+\t/*\n+\t * The list is already checked to only request valid and supported fields\n+\t * no need to check, just request everything left on the list\n+\t */\n+\tfor (size_t i = 0; i < args->object_info_options->nr; i++)\n+\t\tpacket_buf_write(&req_buf, \"%s\",\n+\t\t\t\t args->object_info_options->items[i].string);\n \n \tif (args->oids)\n \t\tfor (size_t i = 0; i < args->oids->nr; i++)\n\n-- \n2.54.0\n\n"},{"id":"548962","messageId":"20260725-objecttype-support-v1-4-2d4ca3bbabf1@gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"[PATCH GSoC 4/5] serve: advertise type capability","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-25T11:55:05Z","receivedAt":"2026-07-25T11:55:37Z","isPatch":true,"body":"The server and the client can handle type requests but the client won't\nask for it until the server advertises it.\n\nAdd type to the advertised capabilities so the client knows that it can\nrequest 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 serve.c                                |  4 ++--\n t/t1017-cat-file-remote-object-info.sh | 10 ++++++----\n 2 files changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/serve.c b/serve.c\nindex 2b07d922b3..2ce513cf2d 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -97,9 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value)\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n-\t/* Currently only size is supported */\n+\t/* Currently only size and type are supported */\n \tif (value && advertise_object_info)\n-\t\tstrbuf_addstr(value, \"size\");\n+\t\tstrbuf_addstr(value, \"size type\");\n \treturn advertise_object_info;\n }\n \ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 116862f9d0..175f778cc9 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -7,6 +7,7 @@ test_description='git cat-file --batch-command with remote-object-info command'\n \n hello_content=\"Hello World\"\n hello_size=$(strlen \"$hello_content\")\n+hello_type=\"blob\"\n hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n \n@@ -19,6 +20,7 @@ unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdi\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+tree_type=\"tree\"\n \n commit_message=\"Initial commit\"\n \n@@ -31,6 +33,7 @@ commit_message=\"Initial commit\"\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+commit_type=\"commit\"\n \n tag_header_without_oid=\"type blob\n tag hellotag\n@@ -44,6 +47,7 @@ $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+tag_type=\"tag\"\n \n set_transport_variables () {\n \thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n@@ -256,14 +260,12 @@ 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+test_expect_success 'objecttype is supported by remote-object-info' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n \n-\t\techo \"\" >expect &&\n+\t\techo $hello_type >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\n-- \n2.54.0\n\n"},{"id":"548963","messageId":"20260725-objecttype-support-v1-5-2d4ca3bbabf1@gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"[PATCH GSoC 5/5] cat-file: unify default format","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-25T11:55:06Z","receivedAt":"2026-07-25T11:55:39Z","isPatch":true,"body":"%(objecttype) is supported both by the client and by the server.\nChange the temporary default format to the unified version that other\ncommands use.\n\nUpdate documentation to remove %(objecttype) from the caveats of\nremote-object-info.\n\nUpdate tests that used the default format to expect type.\nUpdate documentation to show %(objecttype) support.\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        | 17 ++++-----\n Documentation/gitprotocol-v2.adoc      | 15 ++++++--\n builtin/cat-file.c                     |  7 ----\n t/t1017-cat-file-remote-object-info.sh | 70 ++++++++++++----------------------\n 4 files changed, 42 insertions(+), 67 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex ac3b528c6f..514bfc0032 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -348,15 +348,12 @@ 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+The command `remote-object-info` only supports the `%(objectname)`,\n+`%(objectsize)` and `%(objecttype)` placeholders. See `CAVEATS` below for more\n+information.\n \n If no format is specified, the default format is `%(objectname)\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+%(objecttype) %(objectsize)`.\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@@ -453,9 +450,9 @@ 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+Note that only `%(objectname)`, `%(objectsize)` and `%(objecttype)` are\n+currently supported by the `remote-object-info` command. Using any other\n+placeholder in 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/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 7bf62014c3..de4bfb776e 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -558,14 +558,17 @@ object-info\n \n `object-info` is the command to retrieve information about one or more objects.\n Its main purpose is to allow a client to make decisions based on this\n-information without having to fully fetch objects. Object size is the only\n-information that is currently supported.\n+information without having to fully fetch objects. Object size and type are the\n+only information that is currently supported.\n \n An `object-info` request takes the following arguments:\n \n \tsize\n \tRequests size information to be returned for each listed object id.\n \n+\ttype\n+\tRequests type information to be returned for each listed object id.\n+\n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n \tinformation for. They must be full OIDs.\n@@ -580,11 +583,15 @@ space.\n \tinfo = *PKT-LINE(attr LF)\n \t       *PKT-LINE(obj-info LF)\n \n-\tattr = \"size\"\n+\tattr = \"size\" | \"type\"\n \n \tobj-size = 1*DIGIT\n \n-\tobj-info = obj-id [SP [obj-size]]\n+\tobj-type = \"blob\" | \"tree\" | \"commit\" | \"tag\"\n+\n+\tobj-val = obj-size | obj-type\n+\n+\tobj-info = obj-id [SP [obj-val *(SP obj-val)]]\n \n If the server does not recognize the OID, the response will be `<oid> SP`\n regardless of the number of attributes requested.\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 884b6d5ad3..8288511b19 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -841,15 +841,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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 \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@@ -904,7 +898,6 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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]);\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 175f778cc9..741bdf34a0 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -139,10 +139,10 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\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\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -152,28 +152,6 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\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@@ -209,10 +187,10 @@ test_expect_success 'batch-command -Z remote-object-info git:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -432,10 +410,10 @@ test_expect_success 'batch-command remote-object-info file:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -451,10 +429,10 @@ test_expect_success 'batch-command -Z remote-object-info file:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -602,10 +580,10 @@ test_expect_success 'batch-command remote-object-info http:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -620,10 +598,10 @@ test_expect_success 'batch-command -Z remote-object-info http:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $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-- \n2.54.0\n\n"},{"id":"549189","messageId":"CA+J6zkSvwqy4QdL16oQ_W=Noi81i-dLtAdAQqU3u9A1mQV3izQ@mail.gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"Re: [PATCH GSoC 0/5] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-07-29T09:52:12Z","receivedAt":"2026-07-29T09:52:40Z","isPatch":true,"body":"On Sat, 25 Jul 2026 at 17:25, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> \"%(objecttype)\" is already known by the client's allow-list, but neither\n> the client nor the server knows how to handle type. This series continues\n> the work for git cat-file --batch-command extending remote-object-info\n> to support \"%(objecttype)\" end to end. It is based on its predecessor\n> series \"cat-file: add remote-object-info to batch-command\" [1].\n>\n> Type is the last property that is identical on the server and on the\n> client once fetched.\n>\n> Whether to support more metadata such as:\n>\n> - objectsize:disk\n> - deltabase\n> - objectmode (needs context)\n>\n> should be discussed, unlike size and type, the rest depend on how things\n> are packed on the server and on what the client already has, so they\n> cannot reliably match local once fetched. IMO they are not worth\n> supporting, I can't find a use case for them.\n\nMakes sense to me. The metadata you listed depends on the local packfile\nstorage where the repository lives.\n\n`objectmode` depends on the tree pointing to the blob, so it wouldn't be\naccessible using the current infrastructure anyway.\n\n> Adding new placeholders has become trivial.\n> To add a new placeholder, follow the steps in this series and add it to\n> the client's allow-list at 'builtin/cat-file.c'.\n>\n> Based-on: <20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com>\n>\n> Github CI: https://github.com/pabloosabaterr/git/actions/runs/30155586279\n>\n> [1]: https://lore.kernel.org/git/20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com/\n>\n> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n> ---\n> Pablo Sabater (5):\n>       protocol-caps: add type support to object-info\n>       fetch-object-info: parse type from server response\n>       fetch-object-info: request all supported options dynamically\n>       serve: advertise type capability\n>       cat-file: unify default format\n\nThe current incremental approach is safe and ensures every commit\ncompiles and passes tests. However, from a storytelling perspective for\nthe reviewers, I believe it's better to do any 'preparatory refactoring' before\nstarting the new feature.\n\nPatch 3 (dynamically requesting supported options) doesn't actually depend\non type existing yet. I suggest bumping Patch 3 to be Patch 1 in V2.\nThat way, the client is already dynamic and ready, and the feature patches\ncan strictly focus on adding type:\n\n- fetch-object-info: request all supported options dynamically (Current Patch 3)\n- protocol-caps: add type support to object-info (Current Patch 1)\n- fetch-object-info: parse type from server response (Current Patch 2)\n- serve: advertise type capability (Current Patch 4)\n- cat-file: unify default format (Current Patch 5)\n\n>  Documentation/git-cat-file.adoc        | 17 +++-----\n>  Documentation/gitprotocol-v2.adoc      | 15 +++++--\n>  builtin/cat-file.c                     |  7 ---\n>  fetch-object-info.c                    | 23 +++++++---\n>  protocol-caps.c                        | 21 +++++++--\n>  serve.c                                |  4 +-\n>  t/t1017-cat-file-remote-object-info.sh | 80 +++++++++++++---------------------\n>  t/t5701-git-serve.sh                   | 27 ++++++++++++\n>  8 files changed, 113 insertions(+), 81 deletions(-)\n\nThis series is definitely a lot smaller than I thought it would be. Looks like\nmost of the heavy lifting was already done with the previous series.\n\nGood for us!\n"},{"id":"549190","messageId":"CA+J6zkQFAqZvi-6UaQi6v_OBiT4ihZtCN45vyGCGTbo9TJLJbg@mail.gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-1-2d4ca3bbabf1@gmail.com","subject":"Re: [PATCH GSoC 1/5] protocol-caps: add type support to object-info","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-07-29T09:53:23Z","receivedAt":"2026-07-29T09:53:51Z","isPatch":true,"body":"On Sat, 25 Jul 2026 at 17:25, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> Teach the server-side object-info handler to accept type as a requested\n> field. When the client includes type in its object-info request, the\n> server returns the requested object type.\n>\n> While at it, fix requested_info->size bit field style.\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>  protocol-caps.c      | 21 ++++++++++++++++++---\n>  t/t5701-git-serve.sh | 27 +++++++++++++++++++++++++++\n>  2 files changed, 45 insertions(+), 3 deletions(-)\n>\n> diff --git a/protocol-caps.c b/protocol-caps.c\n> index 02261be14d..5531d388f0 100644\n> --- a/protocol-caps.c\n> +++ b/protocol-caps.c\n> @@ -11,7 +11,8 @@\n>  #include \"strbuf.h\"\n>\n>  struct requested_info {\n> -       unsigned size : 1;\n> +       unsigned size:1;\n> +       unsigned type:1;\n>  };\n>\n>  /*\n> @@ -73,15 +74,20 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n>         if (info->size)\n>                 packet_writer_write(writer, \"size\");\n>\n> +       if (info->type)\n> +               packet_writer_write(writer, \"type\");\n> +\n>         for_each_string_list_item (item, oid_str_list) {\n>                 const char *oid_str = item->string;\n>                 struct object_id oid;\n>                 size_t object_size;\n> +               enum object_type object_type;\n>\n>                 if (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n>                         packet_writer_error(\n>                                 writer,\n> -                               \"object-info: protocol error, expected to get oid, not '%s'\",\n> +                               \"object-info: protocol error, expected to get \"\n> +                               \"oid, not '%s'\",\n\nI assume this is a style change? The original line doesn't seem\nlong enough to wrap though.\n\nAlso, this would break the grep-ability of this error string.\n\n>                                 oid_str);\n>                         continue;\n>                 }\n> @@ -93,7 +99,8 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n>                  * If an object is not recognized by the server append SP to\n>                  * the response.\n>                  */\n> -               if (get_object_info(r->objects, &oid, &object_size) <= OBJ_NONE) {\n> +               object_type = get_object_info(r->objects, &oid, &object_size);\n> +               if (object_type <= OBJ_NONE) {\n>                         strbuf_addstr(&send_buffer, \" \");\n>                         goto write;\n>                 }\n> @@ -103,6 +110,9 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n>                                     (uintmax_t)object_size);\n>                 }\n>\n> +               if (info->type)\n> +                       strbuf_addf(&send_buffer, \" %s\", type_name(object_type));\n> +\n>  write:\n>                 packet_writer_write(writer, \"%s\", send_buffer.buf);\n>                 strbuf_reset(&send_buffer);\n> @@ -124,6 +134,11 @@ int cap_object_info(struct repository *r, struct packet_reader *request)\n>                         continue;\n>                 }\n>\n> +               if (!strcmp(\"type\", request->line)) {\n> +                       info.type = 1;\n> +                       continue;\n> +               }\n> +\n>                 if (parse_oid(request->line, &oid_str_list))\n>                         continue;\n>\n> diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n> index 9a575aa098..d7c93b5b55 100755\n> --- a/t/t5701-git-serve.sh\n> +++ b/t/t5701-git-serve.sh\n> @@ -366,6 +366,33 @@ test_expect_success 'basics of object-info' '\n>         test_cmp expect actual\n>  '\n>\n> +test_expect_success 'type' '\n> +       test_config transfer.advertiseObjectInfo true &&\n> +\n> +       test-tool pkt-line pack >in <<-EOF &&\n> +       command=object-info\n> +       object-format=$(test_oid algo)\n> +       0001\n> +       size\n> +       type\n> +       oid $(git rev-parse two:two.t)\n> +       oid $(git rev-parse two:two.t)\n> +       0000\n> +       EOF\n> +\n> +       cat >expect <<-EOF &&\n> +       size\n> +       type\n> +       $(git rev-parse two:two.t) $(wc -c <two.t | xargs) blob\n> +       $(git rev-parse two:two.t) $(wc -c <two.t | xargs) blob\n\nCan we not use the `test_file_size` tool to do this instead?\nThat should also be much more portable.\n"},{"id":"549191","messageId":"CA+J6zkSQYuK-ZJoiQkEJDS9fBypOrBEmgYZRj1yYU00ws2u_HA@mail.gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-2-2d4ca3bbabf1@gmail.com","subject":"Re: [PATCH GSoC 2/5] fetch-object-info: parse type from server response","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-07-29T09:57:02Z","receivedAt":"2026-07-29T09:57:29Z","isPatch":true,"body":"On Sat, 25 Jul 2026 at 17:25, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> The server can handle type requests but does not advertise the\n> capability yet. Prepare the client to know how to parse the server\n> response once the server advertises the capability.\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>  fetch-object-info.c | 12 +++++++++++-\n>  1 file changed, 11 insertions(+), 1 deletion(-)\n>\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> index ba7e179c44..cf6b94afb8 100644\n> --- a/fetch-object-info.c\n> +++ b/fetch-object-info.c\n> @@ -50,6 +50,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>                       const int stateless_rpc, const int fd_out)\n>  {\n>         int size_index = -1;\n> +       int type_index = -1;\n>\n>         switch (version) {\n>         case protocol_v2:\n> @@ -101,8 +102,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>                         for (size_t j = 0; j < args->oids->nr; j++)\n>                                 object_info_data[j].sizep =\n>                                         xcalloc(1, sizeof(*object_info_data[j].sizep));\n> +               } else if (!strcmp(reader->line, \"type\")) {\n> +                       type_index = (int)i;\n> +                       for (size_t j = 0; j < args->oids->nr; j++)\n> +                               object_info_data[j].typep =\n> +                                       xcalloc(1, sizeof(*object_info_data[j].typep));\n>                 } else {\n> -                       BUG(\"only size is supported\");\n> +                       BUG(\"unexpected object-info option: %s\", reader->line);\n>                 }\n>         }\n>\n> @@ -148,6 +154,10 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>                             object_info_values.items[0].string,\n>                             object_info_values.items[size_index + 1].string);\n>\n> +               if (type_index >= 0)\n> +                       *object_info_data[i].typep =\n> +                               type_from_string(object_info_values.items[type_index + 1].string);\n> +\n>                 string_list_clear(&object_info_values, 0);\n\nIs there a risk of an out-of-bounds array access here if the server\nresponds with a truncated or malformed packet?\n\nIf object_info_values.nr <= type_index + 1, this will segfault.\n\nIf there isn't a bounds check slightly higher up in this loop, we should\nadd one. Either way, we should definitely add a test using a mocked\nserver response (e.g., via test-tool pkt-line) to ensure the client\ngracefully dies with a protocol error rather than segfaulting when it\nreceives a malformed packet.\n"},{"id":"549192","messageId":"CA+J6zkQCa-rCJYQgfsjLyyJqrW+0-Z8pUJ4EJxY9HSTkJH=u4A@mail.gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-3-2d4ca3bbabf1@gmail.com","subject":"Re: [PATCH GSoC 3/5] fetch-object-info: request all supported options dynamically","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-07-29T09:57:34Z","receivedAt":"2026-07-29T09:58:02Z","isPatch":true,"body":"On Sat, 25 Jul 2026 at 17:25, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> In send_object_info_request(), size is hardcoded to be the only option\n> sent. In order to support type and future capabilities, replace the\n> hardcoded size with a loop that requests everything on\n> object_info_options list.\n>\n> This is safe because the list has already been trimmed previously in\n> fetch_object_info() to only contain options that the server supports.\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>  fetch-object-info.c | 11 +++++++----\n>  1 file changed, 7 insertions(+), 4 deletions(-)\n>\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> index cf6b94afb8..e5cfdafe68 100644\n> --- a/fetch-object-info.c\n> +++ b/fetch-object-info.c\n> @@ -15,10 +15,13 @@ static void send_object_info_request(const int fd_out, struct object_info_args *\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> -       else if (args->object_info_options->nr)\n> -               BUG(\"only size should be in object_info_options\");\n> +       /*\n> +        * The list is already checked to only request valid and supported fields\n> +        * no need to check, just request everything left on the list\n\nNit: ...valid and supported fields no need to check.. -> valid and\nsupported fields.\nJust request everything remaining on the list.\n\n> +        */\n> +       for (size_t i = 0; i < args->object_info_options->nr; i++)\n> +               packet_buf_write(&req_buf, \"%s\",\n> +                                args->object_info_options->items[i].string);\n\nPerfect place to use `for_each_string_list_item()`.\n\n\n>         if (args->oids)\n>                 for (size_t i = 0; i < args->oids->nr; i++)\n>\n> --\n> 2.54.0\n>\n"},{"id":"549193","messageId":"CA+J6zkQKHVhDLSXYz=hFtfnACHviSVZEDignagbCVLhEWBZiJg@mail.gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-4-2d4ca3bbabf1@gmail.com","subject":"Re: [PATCH GSoC 4/5] serve: advertise type capability","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-07-29T09:58:01Z","receivedAt":"2026-07-29T09:58:30Z","isPatch":true,"body":"[snip]\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> +test_expect_success 'objecttype is supported by remote-object-info' '\n>         (\n>                 set_transport_variables \"$daemon_parent\" &&\n>                 cd \"$daemon_parent/daemon_client_empty\" &&\n>\n> -               echo \"\" >expect &&\n> +               echo $hello_type >expect &&\n>                 git cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n>                 remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n>                 EOF\n\nInstead of this, what about creating a single test that verifies\n'type' is supported,\nand modifying this test to verify that the other options are not?\n\nThat would actually preserve this test's behaviour and make it easier\nto extend in the future. Something like:\n\n+ test_expect_success 'type is supported by remote-object-info'\n+ test_expect_success 'unsupported placeholder on remote returns empty string'\n"},{"id":"549195","messageId":"CA+J6zkQAAkeM67+yJGLG7z9+TV0sJ2cZp-dV+xCJn2c4V9wVJQ@mail.gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-5-2d4ca3bbabf1@gmail.com","subject":"Re: [PATCH GSoC 5/5] cat-file: unify default format","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-07-29T09:59:19Z","receivedAt":"2026-07-29T09:59:48Z","isPatch":true,"body":"On Sat, 25 Jul 2026 at 17:25, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>\n> %(objecttype) is supported both by the client and by the server.\n> Change the temporary default format to the unified version that other\n> commands use.\n\ns/other/the other\n\n> Update documentation to remove %(objecttype) from the caveats of\n> remote-object-info.\n>\n> Update tests that used the default format to expect type.\n\nNot super accurate. We're updating the tests to expect the new default\nformat.\n\n> Update documentation to show %(objecttype) support.\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>  Documentation/git-cat-file.adoc        | 17 ++++-----\n>  Documentation/gitprotocol-v2.adoc      | 15 ++++++--\n>  builtin/cat-file.c                     |  7 ----\n>  t/t1017-cat-file-remote-object-info.sh | 70 ++++++++++++----------------------\n>  4 files changed, 42 insertions(+), 67 deletions(-)\n>\n> diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\n> index ac3b528c6f..514bfc0032 100644\n> --- a/Documentation/git-cat-file.adoc\n> +++ b/Documentation/git-cat-file.adoc\n> @@ -348,15 +348,12 @@ newline. The available atoms are:\n>         after that first run of whitespace (i.e., the \"rest\" of the\n>         line) 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> +The command `remote-object-info` only supports the `%(objectname)`,\n> +`%(objectsize)` and `%(objecttype)` placeholders. See `CAVEATS` below for more\n> +information.\n>\n>  If no format is specified, the default format is `%(objectname)\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> +%(objecttype) %(objectsize)`.\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> @@ -453,9 +450,9 @@ 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> +Note that only `%(objectname)`, `%(objectsize)` and `%(objecttype)` are\n> +currently supported by the `remote-object-info` command. Using any other\n> +placeholder in 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> diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\n> index 7bf62014c3..de4bfb776e 100644\n> --- a/Documentation/gitprotocol-v2.adoc\n> +++ b/Documentation/gitprotocol-v2.adoc\n> @@ -558,14 +558,17 @@ object-info\n>\n>  `object-info` is the command to retrieve information about one or more objects.\n>  Its main purpose is to allow a client to make decisions based on this\n> -information without having to fully fetch objects. Object size is the only\n> -information that is currently supported.\n> +information without having to fully fetch objects. Object size and type are the\n> +only information that is currently supported.\n\ns/is currently/are currently\n\n>  An `object-info` request takes the following arguments:\n>\n>         size\n>         Requests size information to be returned for each listed object id.\n>\n> +       type\n> +       Requests type information to be returned for each listed object id.\n> +\n>         oid <oid>\n>         Indicates to the server an object which the client wants to obtain\n>         information for. They must be full OIDs.\n> @@ -580,11 +583,15 @@ space.\n>         info = *PKT-LINE(attr LF)\n>                *PKT-LINE(obj-info LF)\n>\n> -       attr = \"size\"\n> +       attr = \"size\" | \"type\"\n>\n>         obj-size = 1*DIGIT\n>\n> -       obj-info = obj-id [SP [obj-size]]\n> +       obj-type = \"blob\" | \"tree\" | \"commit\" | \"tag\"\n> +\n> +       obj-val = obj-size | obj-type\n> +\n> +       obj-info = obj-id [SP [obj-val *(SP obj-val)]]\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> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 884b6d5ad3..8288511b19 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -841,15 +841,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>         struct object_info *remote_object_info = NULL;\n>         struct oid_array object_info_oids = OID_ARRAY_INIT;\n>         struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n> -       const char *saved_format = opt->format;\n>\n>         if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n>                 die(_(\"remote-object-info command too long\"));\n> -       /*\n> -        * TODO: Use the default format once %(objecttype) is supported.\n> -        */\n> -       if (!opt->format)\n> -               opt->format = \"%(objectname) %(objectsize)\";\n>\n>         line_to_split = xstrdup(line);\n>         count = split_cmdline(line_to_split, &argv);\n> @@ -904,7 +898,6 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>                 data->is_remote = 0;\n>         }\n>         data->skip_object_info = 0;\n> -       opt->format = saved_format;\n>\n>         for (size_t i = 0; i < object_info_oids.nr; i++)\n>                 free_object_info_contents(&remote_object_info[i]);\n> diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\n> index 175f778cc9..741bdf34a0 100755\n> --- a/t/t1017-cat-file-remote-object-info.sh\n> +++ b/t/t1017-cat-file-remote-object-info.sh\n> @@ -139,10 +139,10 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\n>                 set_transport_variables \"$daemon_parent\" &&\n>                 cd \"$daemon_parent/daemon_client_empty\" &&\n>\n> -               echo \"$hello_oid $hello_size\" >expect &&\n> -               echo \"$tree_oid $tree_size\" >>expect &&\n> -               echo \"$commit_oid $commit_size\" >>expect &&\n> -               echo \"$tag_oid $tag_size\" >>expect &&\n> +               echo \"$hello_oid $hello_type $hello_size\" >expect &&\n> +               echo \"$tree_oid $tree_type $tree_size\" >>expect &&\n> +               echo \"$commit_oid $commit_type $commit_size\" >>expect &&\n> +               echo \"$tag_oid $tag_type $tag_size\" >>expect &&\n>\n>                 git cat-file --batch-command >actual <<-EOF &&\n>                 remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n> @@ -152,28 +152,6 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\n>         )\n>  '\n>\n> -test_expect_success 'remote-object-info does not change the default format of info' '\n> -       (\n> -               set_transport_variables \"$daemon_parent\" &&\n> -               cd \"$daemon_parent/daemon_client_empty\" &&\n> -\n> -               local_content=\"local object\" &&\n> -               local_oid=$(echo_without_newline \"$local_content\" | git hash-object -w --stdin) &&\n> -               local_size=$(strlen \"$local_content\") &&\n> -\n> -               echo \"$local_oid blob $local_size\" >expect &&\n> -               echo \"$hello_oid $hello_size\" >>expect &&\n> -               echo \"$local_oid blob $local_size\" >>expect &&\n> -\n> -               git cat-file --batch-command >actual <<-EOF &&\n> -               info $local_oid\n> -               remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n> -               info $local_oid\n> -               EOF\n> -               test_cmp expect actual\n> -       )\n> -'\n> -\n\nI feel like deleting this test removes the only test in this file that validates\ncalling info and remote-object-info in the same cat-file --batch-command\nsession.\n\nInstead of deleting it, we should update it. Perhaps something like this:\n\ntest \"remote-object-info and info can be mixed using the unified\ndefault format\":\n    // 1. Environment setup\n    // 2. Prepare a local object for the 'info' command\n    // 3. Construct the expected output. Since the default format is\nnow unified,\n    // both commands should output exactly: <OID> <TYPE> <SIZE>\n    // 4. Execute the batch command\n    // 5. Validate\n"},{"id":"549197","messageId":"DKB0I229LCE8.CU4ME582YISN@gmail.com","threadId":"66063","inReplyTo":"CA+J6zkQFAqZvi-6UaQi6v_OBiT4ihZtCN45vyGCGTbo9TJLJbg@mail.gmail.com","subject":"Re: [PATCH GSoC 1/5] protocol-caps: add type support to object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-29T11:18:13Z","receivedAt":"2026-07-29T11:18:16Z","isPatch":true,"body":"On Wed Jul 29, 2026 at 11:53 AM CEST, Chandra Pratap wrote:\n> On Sat, 25 Jul 2026 at 17:25, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>>\n>> Teach the server-side object-info handler to accept type as a requested\n>> field. When the client includes type in its object-info request, the\n>> server returns the requested object type.\n>>\n>> While at it, fix requested_info->size bit field style.\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>>  protocol-caps.c      | 21 ++++++++++++++++++---\n>>  t/t5701-git-serve.sh | 27 +++++++++++++++++++++++++++\n>>  2 files changed, 45 insertions(+), 3 deletions(-)\n>>\n>> diff --git a/protocol-caps.c b/protocol-caps.c\n>> index 02261be14d..5531d388f0 100644\n>> --- a/protocol-caps.c\n>> +++ b/protocol-caps.c\n>> @@ -11,7 +11,8 @@\n>>  #include \"strbuf.h\"\n>>\n>>  struct requested_info {\n>> -       unsigned size : 1;\n>> +       unsigned size:1;\n>> +       unsigned type:1;\n>>  };\n>>\n>>  /*\n>> @@ -73,15 +74,20 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n>>         if (info->size)\n>>                 packet_writer_write(writer, \"size\");\n>>\n>> +       if (info->type)\n>> +               packet_writer_write(writer, \"type\");\n>> +\n>>         for_each_string_list_item (item, oid_str_list) {\n>>                 const char *oid_str = item->string;\n>>                 struct object_id oid;\n>>                 size_t object_size;\n>> +               enum object_type object_type;\n>>\n>>                 if (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n>>                         packet_writer_error(\n>>                                 writer,\n>> -                               \"object-info: protocol error, expected to get oid, not '%s'\",\n>> +                               \"object-info: protocol error, expected to get \"\n>> +                               \"oid, not '%s'\",\n>\n> I assume this is a style change? The original line doesn't seem\n> long enough to wrap though.\n>\n> Also, this would break the grep-ability of this error string.\n\nYes It is a style change and it's ~60columns long, I'll drop the change.\nTurns out my nvim settings is showing the vertical guide at ~60 columns instead\nof 80, but that's on me.\n\n>\n>>                                 oid_str);\n>>                         continue;\n>>                 }\n>> @@ -93,7 +99,8 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n>>                  * If an object is not recognized by the server append SP to\n>>                  * the response.\n>>                  */\n>> -               if (get_object_info(r->objects, &oid, &object_size) <= OBJ_NONE) {\n>> +               object_type = get_object_info(r->objects, &oid, &object_size);\n>> +               if (object_type <= OBJ_NONE) {\n>>                         strbuf_addstr(&send_buffer, \" \");\n>>                         goto write;\n>>                 }\n>> @@ -103,6 +110,9 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n>>                                     (uintmax_t)object_size);\n>>                 }\n>>\n>> +               if (info->type)\n>> +                       strbuf_addf(&send_buffer, \" %s\", type_name(object_type));\n>> +\n>>  write:\n>>                 packet_writer_write(writer, \"%s\", send_buffer.buf);\n>>                 strbuf_reset(&send_buffer);\n>> @@ -124,6 +134,11 @@ int cap_object_info(struct repository *r, struct packet_reader *request)\n>>                         continue;\n>>                 }\n>>\n>> +               if (!strcmp(\"type\", request->line)) {\n>> +                       info.type = 1;\n>> +                       continue;\n>> +               }\n>> +\n>>                 if (parse_oid(request->line, &oid_str_list))\n>>                         continue;\n>>\n>> diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n>> index 9a575aa098..d7c93b5b55 100755\n>> --- a/t/t5701-git-serve.sh\n>> +++ b/t/t5701-git-serve.sh\n>> @@ -366,6 +366,33 @@ test_expect_success 'basics of object-info' '\n>>         test_cmp expect actual\n>>  '\n>>\n>> +test_expect_success 'type' '\n>> +       test_config transfer.advertiseObjectInfo true &&\n>> +\n>> +       test-tool pkt-line pack >in <<-EOF &&\n>> +       command=object-info\n>> +       object-format=$(test_oid algo)\n>> +       0001\n>> +       size\n>> +       type\n>> +       oid $(git rev-parse two:two.t)\n>> +       oid $(git rev-parse two:two.t)\n>> +       0000\n>> +       EOF\n>> +\n>> +       cat >expect <<-EOF &&\n>> +       size\n>> +       type\n>> +       $(git rev-parse two:two.t) $(wc -c <two.t | xargs) blob\n>> +       $(git rev-parse two:two.t) $(wc -c <two.t | xargs) blob\n>\n> Can we not use the `test_file_size` tool to do this instead?\n> That should also be much more portable.\n\nYes, I will use it, I didn't know about it.\nA test on top of this one does the same pattern, I will fix it too.\n\nThanks for the feedback,\nPablo\n\n"},{"id":"549198","messageId":"DKB1II4Z88SG.38KG7RAF9Q7VW@gmail.com","threadId":"66063","inReplyTo":"CA+J6zkSQYuK-ZJoiQkEJDS9fBypOrBEmgYZRj1yYU00ws2u_HA@mail.gmail.com","subject":"Re: [PATCH GSoC 2/5] fetch-object-info: parse type from server response","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-29T12:05:49Z","receivedAt":"2026-07-29T12:05:52Z","isPatch":true,"body":"On Wed Jul 29, 2026 at 11:57 AM CEST, Chandra Pratap wrote:\n> On Sat, 25 Jul 2026 at 17:25, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>>\n>> The server can handle type requests but does not advertise the\n>> capability yet. Prepare the client to know how to parse the server\n>> response once the server advertises the capability.\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>>  fetch-object-info.c | 12 +++++++++++-\n>>  1 file changed, 11 insertions(+), 1 deletion(-)\n>>\n>> diff --git a/fetch-object-info.c b/fetch-object-info.c\n>> index ba7e179c44..cf6b94afb8 100644\n>> --- a/fetch-object-info.c\n>> +++ b/fetch-object-info.c\n>> @@ -50,6 +50,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>>                       const int stateless_rpc, const int fd_out)\n>>  {\n>>         int size_index = -1;\n>> +       int type_index = -1;\n>>\n>>         switch (version) {\n>>         case protocol_v2:\n>> @@ -101,8 +102,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>>                         for (size_t j = 0; j < args->oids->nr; j++)\n>>                                 object_info_data[j].sizep =\n>>                                         xcalloc(1, sizeof(*object_info_data[j].sizep));\n>> +               } else if (!strcmp(reader->line, \"type\")) {\n>> +                       type_index = (int)i;\n>> +                       for (size_t j = 0; j < args->oids->nr; j++)\n>> +                               object_info_data[j].typep =\n>> +                                       xcalloc(1, sizeof(*object_info_data[j].typep));\n>>                 } else {\n>> -                       BUG(\"only size is supported\");\n>> +                       BUG(\"unexpected object-info option: %s\", reader->line);\n>>                 }\n>>         }\n>>\n>> @@ -148,6 +154,10 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>>                             object_info_values.items[0].string,\n>>                             object_info_values.items[size_index + 1].string);\n>>\n>> +               if (type_index >= 0)\n>> +                       *object_info_data[i].typep =\n>> +                               type_from_string(object_info_values.items[type_index + 1].string);\n>> +\n>>                 string_list_clear(&object_info_values, 0);\n>\n> Is there a risk of an out-of-bounds array access here if the server\n> responds with a truncated or malformed packet?\n>\n> If object_info_values.nr <= type_index + 1, this will segfault.\n\nThis shouldn't be a possible case because of:\n\nfetch_object_info()\n\n\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n\n\t\t[snip]\n\n\t\t} else if (!strcmp(reader->line, \"type\")) {\n\t\t\ttype_index = (int)i;\n\n\t\t[snip]\n\ntype_index is set based of the range of object_info_options->nr so:\n  type_index < object_info_options->nr\n\nand a few lines below:\n\n\tif (args->object_info_options->nr + 1 != object_info_values.nr)\n\t\tdie(\"object-info: unexpected number of attributes: %s\",\n\t\t    reader->line);\n\nso we also know that type_index + 1 < object_info_values.nr.\nAfter that we get to those lines that this patch introduced:\n\n\n +               if (type_index >= 0)\n +                       *object_info_data[i].typep =\n +                               type_from_string(object_info_values.items[type_index + 1].string);\n\n And because type_index + 1 < object_info_values.nr we can be sure that\n this cannot segfault once we reach this code.\n\n>\n> If there isn't a bounds check slightly higher up in this loop, we should\n> add one. Either way, we should definitely add a test using a mocked\n> server response (e.g., via test-tool pkt-line) to ensure the client\n> gracefully dies with a protocol error rather than segfaulting when it\n> receives a malformed packet.\n\nOk, that's sounds a good test, I think there's none where a malicious\nserver is simulated, in part because I don't know how and I think I\nhaven't seen a test that does that yet.\ntest-tool and pkt-line are used for the opposite: simulating the\nclient to test the real server.\n\nI'll see what I can do about it.\n\nThanks for the feedback,\nPablo\n\n"},{"id":"549199","messageId":"DKB1K4BKHK2G.2HZ0HM22UTWXD@gmail.com","threadId":"66063","inReplyTo":"CA+J6zkQCa-rCJYQgfsjLyyJqrW+0-Z8pUJ4EJxY9HSTkJH=u4A@mail.gmail.com","subject":"Re: [PATCH GSoC 3/5] fetch-object-info: request all supported options dynamically","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-29T12:07:56Z","receivedAt":"2026-07-29T12:07:59Z","isPatch":true,"body":"On Wed Jul 29, 2026 at 11:57 AM CEST, Chandra Pratap wrote:\n> On Sat, 25 Jul 2026 at 17:25, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>>\n>> In send_object_info_request(), size is hardcoded to be the only option\n>> sent. In order to support type and future capabilities, replace the\n>> hardcoded size with a loop that requests everything on\n>> object_info_options list.\n>>\n>> This is safe because the list has already been trimmed previously in\n>> fetch_object_info() to only contain options that the server supports.\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>>  fetch-object-info.c | 11 +++++++----\n>>  1 file changed, 7 insertions(+), 4 deletions(-)\n>>\n>> diff --git a/fetch-object-info.c b/fetch-object-info.c\n>> index cf6b94afb8..e5cfdafe68 100644\n>> --- a/fetch-object-info.c\n>> +++ b/fetch-object-info.c\n>> @@ -15,10 +15,13 @@ static void send_object_info_request(const int fd_out, struct object_info_args *\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>> -       else if (args->object_info_options->nr)\n>> -               BUG(\"only size should be in object_info_options\");\n>> +       /*\n>> +        * The list is already checked to only request valid and supported fields\n>> +        * no need to check, just request everything left on the list\n>\n> Nit: ...valid and supported fields no need to check.. -> valid and\n> supported fields.\n> Just request everything remaining on the list.\n\nOk, will fix it, thanks.\n\n>\n>> +        */\n>> +       for (size_t i = 0; i < args->object_info_options->nr; i++)\n>> +               packet_buf_write(&req_buf, \"%s\",\n>> +                                args->object_info_options->items[i].string);\n>\n> Perfect place to use `for_each_string_list_item()`.\n\nDidn't think of that, I will change it, thanks.\n\n>\n>\n>>         if (args->oids)\n>>                 for (size_t i = 0; i < args->oids->nr; i++)\n>>\n>> --\n>> 2.54.0\n>>\n\n"},{"id":"549200","messageId":"DKB1PUMUWM9K.31JZRJNIO79CU@gmail.com","threadId":"66063","inReplyTo":"CA+J6zkQKHVhDLSXYz=hFtfnACHviSVZEDignagbCVLhEWBZiJg@mail.gmail.com","subject":"Re: [PATCH GSoC 4/5] serve: advertise type capability","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-29T12:15:25Z","receivedAt":"2026-07-29T12:15:28Z","isPatch":true,"body":"On Wed Jul 29, 2026 at 11:58 AM CEST, Chandra Pratap wrote:\n> [snip]\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>> +test_expect_success 'objecttype is supported by remote-object-info' '\n>>         (\n>>                 set_transport_variables \"$daemon_parent\" &&\n>>                 cd \"$daemon_parent/daemon_client_empty\" &&\n>>\n>> -               echo \"\" >expect &&\n>> +               echo $hello_type >expect &&\n>>                 git cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n>>                 remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n>>                 EOF\n>\n> Instead of this, what about creating a single test that verifies\n> 'type' is supported,\n> and modifying this test to verify that the other options are not?\n>\n> That would actually preserve this test's behaviour and make it easier\n> to extend in the future. Something like:\n>\n> + test_expect_success 'type is supported by remote-object-info'\n> + test_expect_success 'unsupported placeholder on remote returns empty string'\n\nHmmm, it is true that after this test there are no test with the git://\nthat test for known but unsupported placeholders returning empty\nstrings, but there are tests from the previous series that does test for\nothers that are still unsupported like objectmode, deltabase,\nobjectsize:disk.\n\nI think on more test grouping all the one that remain unsupported will\nbe good and document.\n\nI'll do exactly what you proposed, thanks,\nPablo\n"},{"id":"549202","messageId":"DKB1W6RATEE8.SZDO9CJ5V8WT@gmail.com","threadId":"66063","inReplyTo":"CA+J6zkQAAkeM67+yJGLG7z9+TV0sJ2cZp-dV+xCJn2c4V9wVJQ@mail.gmail.com","subject":"Re: [PATCH GSoC 5/5] cat-file: unify default format","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-29T12:23:41Z","receivedAt":"2026-07-29T12:23:45Z","isPatch":true,"body":"On Wed Jul 29, 2026 at 11:59 AM CEST, Chandra Pratap wrote:\n> On Sat, 25 Jul 2026 at 17:25, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>>\n>> %(objecttype) is supported both by the client and by the server.\n>> Change the temporary default format to the unified version that other\n>> commands use.\n>\n> s/other/the other\n\nWill fix thanks.\n\n>\n>> Update documentation to remove %(objecttype) from the caveats of\n>> remote-object-info.\n>>\n>> Update tests that used the default format to expect type.\n>\n> Not super accurate. We're updating the tests to expect the new default\n> format.\n\nI will reword it to something like:\n\nNow that type is supported and the default format unified, update the\ntests to expect the new default format.\n\n>\n>> Update documentation to show %(objecttype) support.\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>>  Documentation/git-cat-file.adoc        | 17 ++++-----\n>>  Documentation/gitprotocol-v2.adoc      | 15 ++++++--\n>>  builtin/cat-file.c                     |  7 ----\n>>  t/t1017-cat-file-remote-object-info.sh | 70 ++++++++++++----------------------\n>>  4 files changed, 42 insertions(+), 67 deletions(-)\n>>\n>> diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\n>> index ac3b528c6f..514bfc0032 100644\n>> --- a/Documentation/git-cat-file.adoc\n>> +++ b/Documentation/git-cat-file.adoc\n>> @@ -348,15 +348,12 @@ newline. The available atoms are:\n>>         after that first run of whitespace (i.e., the \"rest\" of the\n>>         line) 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>> +The command `remote-object-info` only supports the `%(objectname)`,\n>> +`%(objectsize)` and `%(objecttype)` placeholders. See `CAVEATS` below for more\n>> +information.\n>>\n>>  If no format is specified, the default format is `%(objectname)\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>> +%(objecttype) %(objectsize)`.\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>> @@ -453,9 +450,9 @@ 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>> +Note that only `%(objectname)`, `%(objectsize)` and `%(objecttype)` are\n>> +currently supported by the `remote-object-info` command. Using any other\n>> +placeholder in 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>> diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\n>> index 7bf62014c3..de4bfb776e 100644\n>> --- a/Documentation/gitprotocol-v2.adoc\n>> +++ b/Documentation/gitprotocol-v2.adoc\n>> @@ -558,14 +558,17 @@ object-info\n>>\n>>  `object-info` is the command to retrieve information about one or more objects.\n>>  Its main purpose is to allow a client to make decisions based on this\n>> -information without having to fully fetch objects. Object size is the only\n>> -information that is currently supported.\n>> +information without having to fully fetch objects. Object size and type are the\n>> +only information that is currently supported.\n>\n> s/is currently/are currently\n\nWill fix.\n\n>\n>>  An `object-info` request takes the following arguments:\n>>\n>>         size\n>>         Requests size information to be returned for each listed object id.\n>>\n>> +       type\n>> +       Requests type information to be returned for each listed object id.\n>> +\n>>         oid <oid>\n>>         Indicates to the server an object which the client wants to obtain\n>>         information for. They must be full OIDs.\n>> @@ -580,11 +583,15 @@ space.\n>>         info = *PKT-LINE(attr LF)\n>>                *PKT-LINE(obj-info LF)\n>>\n>> -       attr = \"size\"\n>> +       attr = \"size\" | \"type\"\n>>\n>>         obj-size = 1*DIGIT\n>>\n>> -       obj-info = obj-id [SP [obj-size]]\n>> +       obj-type = \"blob\" | \"tree\" | \"commit\" | \"tag\"\n>> +\n>> +       obj-val = obj-size | obj-type\n>> +\n>> +       obj-info = obj-id [SP [obj-val *(SP obj-val)]]\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>> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n>> index 884b6d5ad3..8288511b19 100644\n>> --- a/builtin/cat-file.c\n>> +++ b/builtin/cat-file.c\n>> @@ -841,15 +841,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>>         struct object_info *remote_object_info = NULL;\n>>         struct oid_array object_info_oids = OID_ARRAY_INIT;\n>>         struct string_list object_info_options = STRING_LIST_INIT_NODUP;\n>> -       const char *saved_format = opt->format;\n>>\n>>         if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)\n>>                 die(_(\"remote-object-info command too long\"));\n>> -       /*\n>> -        * TODO: Use the default format once %(objecttype) is supported.\n>> -        */\n>> -       if (!opt->format)\n>> -               opt->format = \"%(objectname) %(objectsize)\";\n>>\n>>         line_to_split = xstrdup(line);\n>>         count = split_cmdline(line_to_split, &argv);\n>> @@ -904,7 +898,6 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n>>                 data->is_remote = 0;\n>>         }\n>>         data->skip_object_info = 0;\n>> -       opt->format = saved_format;\n>>\n>>         for (size_t i = 0; i < object_info_oids.nr; i++)\n>>                 free_object_info_contents(&remote_object_info[i]);\n>> diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\n>> index 175f778cc9..741bdf34a0 100755\n>> --- a/t/t1017-cat-file-remote-object-info.sh\n>> +++ b/t/t1017-cat-file-remote-object-info.sh\n>> @@ -139,10 +139,10 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\n>>                 set_transport_variables \"$daemon_parent\" &&\n>>                 cd \"$daemon_parent/daemon_client_empty\" &&\n>>\n>> -               echo \"$hello_oid $hello_size\" >expect &&\n>> -               echo \"$tree_oid $tree_size\" >>expect &&\n>> -               echo \"$commit_oid $commit_size\" >>expect &&\n>> -               echo \"$tag_oid $tag_size\" >>expect &&\n>> +               echo \"$hello_oid $hello_type $hello_size\" >expect &&\n>> +               echo \"$tree_oid $tree_type $tree_size\" >>expect &&\n>> +               echo \"$commit_oid $commit_type $commit_size\" >>expect &&\n>> +               echo \"$tag_oid $tag_type $tag_size\" >>expect &&\n>>\n>>                 git cat-file --batch-command >actual <<-EOF &&\n>>                 remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid $tree_oid\n>> @@ -152,28 +152,6 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\n>>         )\n>>  '\n>>\n>> -test_expect_success 'remote-object-info does not change the default format of info' '\n>> -       (\n>> -               set_transport_variables \"$daemon_parent\" &&\n>> -               cd \"$daemon_parent/daemon_client_empty\" &&\n>> -\n>> -               local_content=\"local object\" &&\n>> -               local_oid=$(echo_without_newline \"$local_content\" | git hash-object -w --stdin) &&\n>> -               local_size=$(strlen \"$local_content\") &&\n>> -\n>> -               echo \"$local_oid blob $local_size\" >expect &&\n>> -               echo \"$hello_oid $hello_size\" >>expect &&\n>> -               echo \"$local_oid blob $local_size\" >>expect &&\n>> -\n>> -               git cat-file --batch-command >actual <<-EOF &&\n>> -               info $local_oid\n>> -               remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n>> -               info $local_oid\n>> -               EOF\n>> -               test_cmp expect actual\n>> -       )\n>> -'\n>> -\n>\n> I feel like deleting this test removes the only test in this file that validates\n> calling info and remote-object-info in the same cat-file --batch-command\n> session.\n>\n> Instead of deleting it, we should update it. Perhaps something like this:\n>\n> test \"remote-object-info and info can be mixed using the unified\n> default format\":\n>     // 1. Environment setup\n>     // 2. Prepare a local object for the 'info' command\n>     // 3. Construct the expected output. Since the default format is\n> now unified,\n>     // both commands should output exactly: <OID> <TYPE> <SIZE>\n>     // 4. Execute the batch command\n>     // 5. Validate\n\nThis test was because we were modifying the default format whenever\nremote-object-info was used. The workaround to have them work together\nwas to store the previous format, save the format as the capped one for\nremote-object-info and at the end restore it.\n\nNow that it has been unified for both it is implicit that they can work\ntogether. However, no harm in having a test explicitly proving it. I will\nmake what you suggest.\n\nThanks for the feedback,\nPablo\n\n"},{"id":"549203","messageId":"DKB24AZJXEMW.1X3CA3AGA1GAE@gmail.com","threadId":"66063","inReplyTo":"CA+J6zkSvwqy4QdL16oQ_W=Noi81i-dLtAdAQqU3u9A1mQV3izQ@mail.gmail.com","subject":"Re: [PATCH GSoC 0/5] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-29T12:34:17Z","receivedAt":"2026-07-29T12:34:22Z","isPatch":true,"body":"On Wed Jul 29, 2026 at 11:52 AM CEST, Chandra Pratap wrote:\n> On Sat, 25 Jul 2026 at 17:25, Pablo Sabater <pabloosabaterr@gmail.com> wrote:\n>>\n>> \"%(objecttype)\" is already known by the client's allow-list, but neither\n>> the client nor the server knows how to handle type. This series continues\n>> the work for git cat-file --batch-command extending remote-object-info\n>> to support \"%(objecttype)\" end to end. It is based on its predecessor\n>> series \"cat-file: add remote-object-info to batch-command\" [1].\n>>\n>> Type is the last property that is identical on the server and on the\n>> client once fetched.\n>>\n>> Whether to support more metadata such as:\n>>\n>> - objectsize:disk\n>> - deltabase\n>> - objectmode (needs context)\n>>\n>> should be discussed, unlike size and type, the rest depend on how things\n>> are packed on the server and on what the client already has, so they\n>> cannot reliably match local once fetched. IMO they are not worth\n>> supporting, I can't find a use case for them.\n>\n> Makes sense to me. The metadata you listed depends on the local packfile\n> storage where the repository lives.\n>\n> `objectmode` depends on the tree pointing to the blob, so it wouldn't be\n> accessible using the current infrastructure anyway.\n\nYup, sounds like this will be it (for now) for remote-object-info placeholders.\n\n>\n>> Adding new placeholders has become trivial.\n>> To add a new placeholder, follow the steps in this series and add it to\n>> the client's allow-list at 'builtin/cat-file.c'.\n>>\n>> Based-on: <20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com>\n>>\n>> Github CI: https://github.com/pabloosabaterr/git/actions/runs/30155586279\n>>\n>> [1]: https://lore.kernel.org/git/20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com/\n>>\n>> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n>> ---\n>> Pablo Sabater (5):\n>>       protocol-caps: add type support to object-info\n>>       fetch-object-info: parse type from server response\n>>       fetch-object-info: request all supported options dynamically\n>>       serve: advertise type capability\n>>       cat-file: unify default format\n>\n> The current incremental approach is safe and ensures every commit\n> compiles and passes tests. However, from a storytelling perspective for\n> the reviewers, I believe it's better to do any 'preparatory refactoring' before\n> starting the new feature.\n>\n> Patch 3 (dynamically requesting supported options) doesn't actually depend\n> on type existing yet. I suggest bumping Patch 3 to be Patch 1 in V2.\n> That way, the client is already dynamic and ready, and the feature patches\n> can strictly focus on adding type:\n\nTrue, I'll move patch 3 to be the first, thanks for noticing.\n\n>\n> - fetch-object-info: request all supported options dynamically (Current Patch 3)\n> - protocol-caps: add type support to object-info (Current Patch 1)\n> - fetch-object-info: parse type from server response (Current Patch 2)\n> - serve: advertise type capability (Current Patch 4)\n> - cat-file: unify default format (Current Patch 5)\n>\n>>  Documentation/git-cat-file.adoc        | 17 +++-----\n>>  Documentation/gitprotocol-v2.adoc      | 15 +++++--\n>>  builtin/cat-file.c                     |  7 ---\n>>  fetch-object-info.c                    | 23 +++++++---\n>>  protocol-caps.c                        | 21 +++++++--\n>>  serve.c                                |  4 +-\n>>  t/t1017-cat-file-remote-object-info.sh | 80 +++++++++++++---------------------\n>>  t/t5701-git-serve.sh                   | 27 ++++++++++++\n>>  8 files changed, 113 insertions(+), 81 deletions(-)\n>\n> This series is definitely a lot smaller than I thought it would be. Looks like\n> most of the heavy lifting was already done with the previous series.\n\nYes I also got surprised by how little I had to do in order to make this\nseries happen (I expected a lot more) but it turned out to be very brief.\n\n>\n> Good for us!\n\nYay! :)\n\n"},{"id":"549216","messageId":"xmqqecglajnj.fsf@gitster.g","threadId":"66063","inReplyTo":"CA+J6zkQFAqZvi-6UaQi6v_OBiT4ihZtCN45vyGCGTbo9TJLJbg@mail.gmail.com","subject":"Re: [PATCH GSoC 1/5] protocol-caps: add type support to object-info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-29T15:40:48Z","receivedAt":"2026-07-29T15:40:50Z","isPatch":true,"body":"Chandra Pratap <chandrapratap3519@gmail.com> writes:\n\n>>                 if (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n>>                         packet_writer_error(\n>>                                 writer,\n>> -                               \"object-info: protocol error, expected to get oid, not '%s'\",\n>> +                               \"object-info: protocol error, expected to get \"\n>> +                               \"oid, not '%s'\",\n>\n> I assume this is a style change? The original line doesn't seem\n> long enough to wrap though.\n\nA 92-column line is much wider than our usual ~70 column limit.\n\n> Also, this would break the grep-ability of this error string.\n\nI've heard this a few times, but so what?  You can still grep for\n\"object-info: protocol error,\" and will find it just fine.\n\n>> +       cat >expect <<-EOF &&\n>> +       size\n>> +       type\n>> +       $(git rev-parse two:two.t) $(wc -c <two.t | xargs) blob\n>> +       $(git rev-parse two:two.t) $(wc -c <two.t | xargs) blob\n>\n> Can we not use the `test_file_size` tool to do this instead?\n> That should also be much more portable.\n\nWell spotted.\n"},{"id":"549219","messageId":"CA+J6zkQQ2F4P1dr+ix8HrKth5W=Kw+AA5EXKwm4QHY5DKjt-Hg@mail.gmail.com","threadId":"66063","inReplyTo":"DKB1II4Z88SG.38KG7RAF9Q7VW@gmail.com","subject":"Re: [PATCH GSoC 2/5] fetch-object-info: parse type from server response","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-07-29T17:06:07Z","receivedAt":"2026-07-29T17:06:36Z","isPatch":true,"body":"[snip]\n> >> @@ -148,6 +154,10 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n> >>                             object_info_values.items[0].string,\n> >>                             object_info_values.items[size_index + 1].string);\n> >>\n> >> +               if (type_index >= 0)\n> >> +                       *object_info_data[i].typep =\n> >> +                               type_from_string(object_info_values.items[type_index + 1].string);\n> >> +\n> >>                 string_list_clear(&object_info_values, 0);\n> >\n> > Is there a risk of an out-of-bounds array access here if the server\n> > responds with a truncated or malformed packet?\n> >\n> > If object_info_values.nr <= type_index + 1, this will segfault.\n>\n> This shouldn't be a possible case because of:\n>\n> fetch_object_info()\n>\n>         for (size_t i = 0; i < args->object_info_options->nr; i++) {\n>\n>                 [snip]\n>\n>                 } else if (!strcmp(reader->line, \"type\")) {\n>                         type_index = (int)i;\n>\n>                 [snip]\n>\n> type_index is set based of the range of object_info_options->nr so:\n>   type_index < object_info_options->nr\n>\n> and a few lines below:\n>\n>         if (args->object_info_options->nr + 1 != object_info_values.nr)\n>                 die(\"object-info: unexpected number of attributes: %s\",\n>                     reader->line);\n>\n> so we also know that type_index + 1 < object_info_values.nr.\n> After that we get to those lines that this patch introduced:\n>\n>\n>  +               if (type_index >= 0)\n>  +                       *object_info_data[i].typep =\n>  +                               type_from_string(object_info_values.items[type_index + 1].string);\n>\n>  And because type_index + 1 < object_info_values.nr we can be sure that\n>  this cannot segfault once we reach this code.\n\nMakes sense to me.\n\n> >\n> > If there isn't a bounds check slightly higher up in this loop, we should\n> > add one. Either way, we should definitely add a test using a mocked\n> > server response (e.g., via test-tool pkt-line) to ensure the client\n> > gracefully dies with a protocol error rather than segfaulting when it\n> > receives a malformed packet.\n>\n> Ok, that's sounds a good test, I think there's none where a malicious\n> server is simulated, in part because I don't know how and I think I\n> haven't seen a test that does that yet.\n> test-tool and pkt-line are used for the opposite: simulating the\n> client to test the real server.\n>\n> I'll see what I can do about it.\n\nYeah, I wouldn't recommend breaking your back for it though. We\nalready have tests exploring the happy paths, so something that\nsimply verifies our expectations for error paths (printing an empty\nstring in this case) should be good enough.\n"},{"id":"549241","messageId":"CAOLa=ZSJvOrTXypnh1Bk_Z3kkXtJVwueSH1U-vHALwS=XQTouQ@mail.gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-1-2d4ca3bbabf1@gmail.com","subject":"Re: [PATCH GSoC 1/5] protocol-caps: add type support to object-info","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-07-29T22:39:06Z","receivedAt":"2026-07-29T22:39:09Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> Teach the server-side object-info handler to accept type as a requested\n> field. When the client includes type in its object-info request, the\n> server returns the requested object type.\n>\n> While at it, fix requested_info->size bit field style.\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>  protocol-caps.c      | 21 ++++++++++++++++++---\n>  t/t5701-git-serve.sh | 27 +++++++++++++++++++++++++++\n>  2 files changed, 45 insertions(+), 3 deletions(-)\n>\n> diff --git a/protocol-caps.c b/protocol-caps.c\n> index 02261be14d..5531d388f0 100644\n> --- a/protocol-caps.c\n> +++ b/protocol-caps.c\n> @@ -11,7 +11,8 @@\n>  #include \"strbuf.h\"\n>\n>  struct requested_info {\n> -\tunsigned size : 1;\n> +\tunsigned size:1;\n> +\tunsigned type:1;\n>  };\n>\n>  /*\n> @@ -73,15 +74,20 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n>  \tif (info->size)\n>  \t\tpacket_writer_write(writer, \"size\");\n>\n> +\tif (info->type)\n> +\t\tpacket_writer_write(writer, \"type\");\n> +\n>  \tfor_each_string_list_item (item, oid_str_list) {\n>  \t\tconst char *oid_str = item->string;\n>  \t\tstruct object_id oid;\n>  \t\tsize_t object_size;\n> +\t\tenum object_type object_type;\n>\n\nThis is fine, but If you do reiterate, maybe we can follow the reverse\nchristmas tree [1] format and move this field up? Or maybe its just\nme...\n\n>  \t\tif (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n>  \t\t\tpacket_writer_error(\n>  \t\t\t\twriter,\n> -\t\t\t\t\"object-info: protocol error, expected to get oid, not '%s'\",\n> +\t\t\t\t\"object-info: protocol error, expected to get \"\n> +\t\t\t\t\"oid, not '%s'\",\n\nI know this is to fix styling, but we generally don't do such changes\nunless we're touching the code or around it. It just tends to be a\ndistraction :)\n\n\n[snip]\n\n[1]: https://lwn.net/Articles/758613/\n"},{"id":"549242","messageId":"CAOLa=ZQbf2vdYOkFBHQ2Kkk=Yc0uSZXEPy+FQPyzp9C8=sUFEQ@mail.gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-2-2d4ca3bbabf1@gmail.com","subject":"Re: [PATCH GSoC 2/5] fetch-object-info: parse type from server response","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-07-29T22:47:17Z","receivedAt":"2026-07-29T22:47:18Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> The server can handle type requests but does not advertise the\n> capability yet. Prepare the client to know how to parse the server\n> response once the server advertises the capability.\n>\n\nNit, I was a bit confused by the flow from the first to the second\nsentence. Maybe:\n\n    The server can handle type requests but does not advertise the\n    capability yet. As a percursor, prepare the client to know how to\n    parse the server response.\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>  fetch-object-info.c | 12 +++++++++++-\n>  1 file changed, 11 insertions(+), 1 deletion(-)\n>\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> index ba7e179c44..cf6b94afb8 100644\n> --- a/fetch-object-info.c\n> +++ b/fetch-object-info.c\n> @@ -50,6 +50,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>  \t\t      const int stateless_rpc, const int fd_out)\n>  {\n>  \tint size_index = -1;\n> +\tint type_index = -1;\n>\n>  \tswitch (version) {\n>  \tcase protocol_v2:\n> @@ -101,8 +102,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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 if (!strcmp(reader->line, \"type\")) {\n> +\t\t\ttype_index = (int)i;\n> +\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n> +\t\t\t\tobject_info_data[j].typep =\n> +\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].typep));\n>  \t\t} else {\n> -\t\t\tBUG(\"only size is supported\");\n> +\t\t\tBUG(\"unexpected object-info option: %s\", reader->line);\n>  \t\t}\n>  \t}\n>\n> @@ -148,6 +154,10 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tif (type_index >= 0)\n> +\t\t\t*object_info_data[i].typep =\n> +\t\t\t\ttype_from_string(object_info_values.items[type_index + 1].string);\n> +\n\nFor size, passing the value as a number makes sense, since the value\nrepresents the field directly.\n\nFor type however, we're passing in a value, while the actual field is a\nstring. We rely on `type_from_string()` to make that translation for us,\nwhich internatlly depends on `object_type_strings[]`. What if there is a\nmismatch between the server and the client? Shouldn't we be sending in\nthe string itself?\n\n>  \t\tstring_list_clear(&object_info_values, 0);\n>  \t}\n>  \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n>\n> --\n> 2.54.0\n"},{"id":"549243","messageId":"CAOLa=ZSdh8EeikBfTn58sNpfSQ=M2VwE5eA25c2ckQYhx6wjBQ@mail.gmail.com","threadId":"66063","inReplyTo":"CAOLa=ZQbf2vdYOkFBHQ2Kkk=Yc0uSZXEPy+FQPyzp9C8=sUFEQ@mail.gmail.com","subject":"Re: [PATCH GSoC 2/5] fetch-object-info: parse type from server response","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-07-29T22:53:27Z","receivedAt":"2026-07-29T22:53:29Z","isPatch":true,"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n>> @@ -148,6 +154,10 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tif (type_index >= 0)\n>> +\t\t\t*object_info_data[i].typep =\n>> +\t\t\t\ttype_from_string(object_info_values.items[type_index + 1].string);\n>> +\n>\n> For size, passing the value as a number makes sense, since the value\n> represents the field directly.\n>\n> For type however, we're passing in a value, while the actual field is a\n> string. We rely on `type_from_string()` to make that translation for us,\n> which internatlly depends on `object_type_strings[]`. What if there is a\n> mismatch between the server and the client? Shouldn't we be sending in\n> the string itself?\n>\n>>  \t\tstring_list_clear(&object_info_values, 0);\n>>  \t}\n>>  \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n>>\n>> --\n>> 2.54.0\n\nAh! We do send in the string itself, all good here :)\n"},{"id":"549363","messageId":"20260731-objecttype-support-v2-0-af577461ed57@gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"[PATCH GSoC v2 0/6] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-31T19:49:33Z","receivedAt":"2026-07-31T19:49:57Z","isPatch":true,"body":"\"%(objecttype)\" is already known by the client's allow-list, but neither\nthe client nor the server knows how to handle type. This series continues\nthe work for git cat-file --batch-command extending remote-object-info\nto support \"%(objecttype)\" end to end. It is based on its predecessor\nseries \"cat-file: add remote-object-info to batch-command\" [1].\n\nType is the last property that is identical on the server and on the\nclient once fetched.\n\nWhether to support more metadata such as:\n\n- objectsize:disk\n- deltabase\n- objectmode (needs context)\n\nshould be discussed, unlike size and type, the rest depend on how things\nare packed on the server and on what the client already has, so they\ncannot reliably match local once fetched. IMO they are not worth\nsupporting, I can't find a use case for them.\n\nAdding new placeholders has become trivial.\nTo add a new placeholder, follow the steps in this series and add it to\nthe client's allow-list at 'builtin/cat-file.c'.\n\nBased-on: <20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com>\n\nGithub CI: https://github.com/pabloosabaterr/git/actions/runs/30660227202\n\n[1]: https://lore.kernel.org/git/20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com/\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nChanges in v2:\n- Changed t5701 test to use test_file_size.\n- Changed the for loop to use for_each_string_list_item().\n- Moved \"fetch-object-info: request all supported options dynamically\"\n  patch to be the first one as it is prep patch.\n- Typos and nits.\n- Restored the test mixing info and remote-object-info, adapted to the\n  unified format.\n- Link to v1: https://lore.kernel.org/git/20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com/\n\n---\nPablo Sabater (6):\n      fetch-object-info: request all supported options dynamically\n      t5701: use the test_file_size() helper\n      protocol-caps: add type support to object-info\n      fetch-object-info: parse type from server response\n      serve: advertise type capability\n      cat-file: unify default format\n\n Documentation/git-cat-file.adoc        | 17 +++-----\n Documentation/gitprotocol-v2.adoc      | 15 +++++--\n builtin/cat-file.c                     |  7 ---\n fetch-object-info.c                    | 23 +++++++---\n protocol-caps.c                        | 21 +++++++--\n serve.c                                |  4 +-\n t/t1017-cat-file-remote-object-info.sh | 78 +++++++++++++++++++++-------------\n t/t5701-git-serve.sh                   | 31 +++++++++++++-\n 8 files changed, 133 insertions(+), 63 deletions(-)\n\nRange-diff versus v1:\n\n-:  ---------- > 1:  f635361786 fetch-object-info: request all supported options dynamically\n-:  ---------- > 2:  5195b2db2e t5701: use the test_file_size() helper\n1:  acdff1bbd3 ! 3:  08a4a86ac9 protocol-caps: add type support to object-info\n    @@ Commit message\n         field. When the client includes type in its object-info request, the\n         server returns the requested object type.\n     \n    -    While at it, fix requested_info->size bit field style.\n    +    While touching send_info(), wrap an over-long line and fix the bit field\n    +    style of requested_info.size.\n     \n         Mentored-by: Karthik Nayak <karthik.188@gmail.com>\n         Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\n    @@ protocol-caps.c: static void send_info(struct repository *r, struct packet_write\n     +\n      \tfor_each_string_list_item (item, oid_str_list) {\n      \t\tconst char *oid_str = item->string;\n    ++\t\tenum object_type object_type;\n      \t\tstruct object_id oid;\n      \t\tsize_t object_size;\n    -+\t\tenum object_type object_type;\n      \n      \t\tif (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n      \t\t\tpacket_writer_error(\n    @@ t/t5701-git-serve.sh: test_expect_success 'basics of object-info' '\n      \ttest_cmp expect actual\n      '\n      \n    -+test_expect_success 'type' '\n    ++test_expect_success 'object-info supports type' '\n     +\ttest_config transfer.advertiseObjectInfo true &&\n     +\n     +\ttest-tool pkt-line pack >in <<-EOF &&\n    @@ t/t5701-git-serve.sh: test_expect_success 'basics of object-info' '\n     +\tcat >expect <<-EOF &&\n     +\tsize\n     +\ttype\n    -+\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs) blob\n    -+\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs) blob\n    ++\t$(git rev-parse two:two.t) $(test_file_size two.t) blob\n    ++\t$(git rev-parse two:two.t) $(test_file_size two.t) blob\n     +\t0000\n     +\tEOF\n     +\n2:  c1a8a83acf = 4:  4d8e2ddb5d fetch-object-info: parse type from server response\n3:  ed0215c3e5 < -:  ---------- fetch-object-info: request all supported options dynamically\n4:  27efd907bd ! 5:  973b490b75 serve: advertise type capability\n    @@ t/t1017-cat-file-remote-object-info.sh: test_expect_success 'remote-object-info\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/t1017-cat-file-remote-object-info.sh: test_expect_success 'unsupported placeholder on remote returns empty string' '\n    + \t)\n    + '\n    + \n    ++test_expect_success 'unsupported placeholders on remote return empty string' '\n    ++\t(\n    ++\t\tset_transport_variables \"$daemon_parent\" &&\n    ++\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n    ++\n    ++\t\tfmt=\"%(objectmode) %(objectsize:disk) %(rest) %(deltabase)\" &&\n    ++\n    ++\t\t# The hardcoded SPs between the atoms are respected.\n    ++\t\techo \"   \" >expect &&\n    ++\t\tgit cat-file --batch-command=\"$fmt\" >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 'requesting only objectname echoes back' '\n    + \t(\n    + \t\tset_transport_variables \"$daemon_parent\" &&\n5:  ab5a44d40e ! 6:  1668033b31 cat-file: unify default format\n    @@ Commit message\n         cat-file: unify default format\n     \n         %(objecttype) is supported both by the client and by the server.\n    -    Change the temporary default format to the unified version that other\n    -    commands use.\n    +    Change the temporary default format to the unified version that the\n    +    other commands use.\n     \n         Update documentation to remove %(objecttype) from the caveats of\n    -    remote-object-info.\n    +    remote-object-info and show %(objecttype) support.\n     \n    -    Update tests that used the default format to expect type.\n    -    Update documentation to show %(objecttype) support.\n    +    Now that type is supported and the default format unified, update the\n    +    tests to expect the new default format.\n     \n         Mentored-by: Karthik Nayak <karthik.188@gmail.com>\n         Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\n    @@ Documentation/gitprotocol-v2.adoc: object-info\n     -information without having to fully fetch objects. Object size is the only\n     -information that is currently supported.\n     +information without having to fully fetch objects. Object size and type are the\n    -+only information that is currently supported.\n    ++only information currently supported.\n      \n      An `object-info` request takes the following arguments:\n      \n    @@ t/t1017-cat-file-remote-object-info.sh: test_expect_success 'batch-command remot\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    ++test_expect_success 'remote-object-info and info can be mixed using the unified default format' '\n      \t(\n      \t\tset_transport_variables \"$daemon_parent\" &&\n    + \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n    +@@ t/t1017-cat-file-remote-object-info.sh: test_expect_success 'remote-object-info does not change the default format of in\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 \"$hello_oid blob $hello_size\" >>expect &&\n    + \t\techo \"$local_oid blob $local_size\" >>expect &&\n    + \n    + \t\tgit cat-file --batch-command >actual <<-EOF &&\n     @@ t/t1017-cat-file-remote-object-info.sh: test_expect_success 'batch-command -Z remote-object-info git:// default filter'\n      \t\tset_transport_variables \"$daemon_parent\" &&\n      \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n\n---\nbase-commit: 71e19e8d2713f385c3fcef59cf6f29bcbd93d91f\nchange-id: 20260724-objecttype-support-ea1ef6941d07\n\n"},{"id":"549364","messageId":"20260731-objecttype-support-v2-1-af577461ed57@gmail.com","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-0-af577461ed57@gmail.com","subject":"[PATCH GSoC v2 1/6] fetch-object-info: request all supported options dynamically","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-31T19:49:34Z","receivedAt":"2026-07-31T19:49:58Z","isPatch":true,"body":"In send_object_info_request(), size is hardcoded to be the only option\nsent. In order to support type and future capabilities, replace the\nhardcoded size with a loop that requests everything on\nobject_info_options list.\n\nThis is safe because the list has already been trimmed previously in\nfetch_object_info() to only contain options that the server supports.\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 fetch-object-info.c | 11 +++++++----\n 1 file changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex ba7e179c44..ec8a80b3be 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -12,13 +12,16 @@\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+\tstruct string_list_item *item;\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+\t/*\n+\t * The list has already been checked to only contain valid and\n+\t * supported fields, so just request everything remaining on it.\n+\t */\n+\tfor_each_string_list_item(item, args->object_info_options)\n+\t\tpacket_buf_write(&req_buf, \"%s\", item->string);\n \n \tif (args->oids)\n \t\tfor (size_t i = 0; i < args->oids->nr; i++)\n\n-- \n2.54.0\n\n"},{"id":"549365","messageId":"20260731-objecttype-support-v2-2-af577461ed57@gmail.com","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-0-af577461ed57@gmail.com","subject":"[PATCH GSoC v2 2/6] t5701: use the test_file_size() helper","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-31T19:49:35Z","receivedAt":"2026-07-31T19:49:59Z","isPatch":true,"body":"An object-info test uses 'wc -c <two.t | xargs' to get the file size.\nUpdate it to use the test_file_size() helper instead.\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 t/t5701-git-serve.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 9a575aa098..b4d6beef11 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -356,8 +356,8 @@ test_expect_success 'basics of object-info' '\n \n \tcat >expect <<-EOF &&\n \tsize\n-\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n-\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n+\t$(git rev-parse two:two.t) $(test_file_size two.t)\n+\t$(git rev-parse two:two.t) $(test_file_size two.t)\n \t0000\n \tEOF\n \n\n-- \n2.54.0\n\n"},{"id":"549366","messageId":"20260731-objecttype-support-v2-3-af577461ed57@gmail.com","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-0-af577461ed57@gmail.com","subject":"[PATCH GSoC v2 3/6] protocol-caps: add type support to object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-31T19:49:36Z","receivedAt":"2026-07-31T19:50:01Z","isPatch":true,"body":"Teach the server-side object-info handler to accept type as a requested\nfield. When the client includes type in its object-info request, the\nserver returns the requested object type.\n\nWhile touching send_info(), wrap an over-long line and fix the bit field\nstyle of requested_info.size.\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 protocol-caps.c      | 21 ++++++++++++++++++---\n t/t5701-git-serve.sh | 27 +++++++++++++++++++++++++++\n 2 files changed, 45 insertions(+), 3 deletions(-)\n\ndiff --git a/protocol-caps.c b/protocol-caps.c\nindex 02261be14d..27e0f85b10 100644\n--- a/protocol-caps.c\n+++ b/protocol-caps.c\n@@ -11,7 +11,8 @@\n #include \"strbuf.h\"\n \n struct requested_info {\n-\tunsigned size : 1;\n+\tunsigned size:1;\n+\tunsigned type:1;\n };\n \n /*\n@@ -73,15 +74,20 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \tif (info->size)\n \t\tpacket_writer_write(writer, \"size\");\n \n+\tif (info->type)\n+\t\tpacket_writer_write(writer, \"type\");\n+\n \tfor_each_string_list_item (item, oid_str_list) {\n \t\tconst char *oid_str = item->string;\n+\t\tenum object_type object_type;\n \t\tstruct object_id oid;\n \t\tsize_t object_size;\n \n \t\tif (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n \t\t\tpacket_writer_error(\n \t\t\t\twriter,\n-\t\t\t\t\"object-info: protocol error, expected to get oid, not '%s'\",\n+\t\t\t\t\"object-info: protocol error, expected to get \"\n+\t\t\t\t\"oid, not '%s'\",\n \t\t\t\toid_str);\n \t\t\tcontinue;\n \t\t}\n@@ -93,7 +99,8 @@ static void send_info(struct repository *r, struct packet_writer *writer,\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\tobject_type = get_object_info(r->objects, &oid, &object_size);\n+\t\tif (object_type <= OBJ_NONE) {\n \t\t\tstrbuf_addstr(&send_buffer, \" \");\n \t\t\tgoto write;\n \t\t}\n@@ -103,6 +110,9 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \t\t\t\t    (uintmax_t)object_size);\n \t\t}\n \n+\t\tif (info->type)\n+\t\t\tstrbuf_addf(&send_buffer, \" %s\", type_name(object_type));\n+\n write:\n \t\tpacket_writer_write(writer, \"%s\", send_buffer.buf);\n \t\tstrbuf_reset(&send_buffer);\n@@ -124,6 +134,11 @@ int cap_object_info(struct repository *r, struct packet_reader *request)\n \t\t\tcontinue;\n \t\t}\n \n+\t\tif (!strcmp(\"type\", request->line)) {\n+\t\t\tinfo.type = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif (parse_oid(request->line, &oid_str_list))\n \t\t\tcontinue;\n \ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex b4d6beef11..d7445571b1 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -366,6 +366,33 @@ test_expect_success 'basics of object-info' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'object-info supports type' '\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+\ttype\n+\toid $(git rev-parse two:two.t)\n+\toid $(git rev-parse two:two.t)\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tsize\n+\ttype\n+\t$(git rev-parse two:two.t) $(test_file_size two.t) blob\n+\t$(git rev-parse two:two.t) $(test_file_size two.t) blob\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 'bare OID request' '\n \ttest_config transfer.advertiseObjectInfo true &&\n \n\n-- \n2.54.0\n\n"},{"id":"549367","messageId":"20260731-objecttype-support-v2-4-af577461ed57@gmail.com","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-0-af577461ed57@gmail.com","subject":"[PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-31T19:49:37Z","receivedAt":"2026-07-31T19:50:02Z","isPatch":true,"body":"The server can handle type requests but does not advertise the\ncapability yet. Prepare the client to know how to parse the server\nresponse once the server advertises the capability.\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 fetch-object-info.c | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex ec8a80b3be..0f6d063164 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -53,6 +53,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\t      const int stateless_rpc, const int fd_out)\n {\n \tint size_index = -1;\n+\tint type_index = -1;\n \n \tswitch (version) {\n \tcase protocol_v2:\n@@ -104,8 +105,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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 if (!strcmp(reader->line, \"type\")) {\n+\t\t\ttype_index = (int)i;\n+\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n+\t\t\t\tobject_info_data[j].typep =\n+\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].typep));\n \t\t} else {\n-\t\t\tBUG(\"only size is supported\");\n+\t\t\tBUG(\"unexpected object-info option: %s\", reader->line);\n \t\t}\n \t}\n \n@@ -151,6 +157,10 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tif (type_index >= 0)\n+\t\t\t*object_info_data[i].typep =\n+\t\t\t\ttype_from_string(object_info_values.items[type_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-- \n2.54.0\n\n"},{"id":"549368","messageId":"20260731-objecttype-support-v2-5-af577461ed57@gmail.com","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-0-af577461ed57@gmail.com","subject":"[PATCH GSoC v2 5/6] serve: advertise type capability","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-31T19:49:38Z","receivedAt":"2026-07-31T19:50:03Z","isPatch":true,"body":"The server and the client can handle type requests but the client won't\nask for it until the server advertises it.\n\nAdd type to the advertised capabilities so the client knows that it can\nrequest 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 serve.c                                |  4 ++--\n t/t1017-cat-file-remote-object-info.sh | 26 ++++++++++++++++++++++----\n 2 files changed, 24 insertions(+), 6 deletions(-)\n\ndiff --git a/serve.c b/serve.c\nindex 2b07d922b3..2ce513cf2d 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -97,9 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value)\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n-\t/* Currently only size is supported */\n+\t/* Currently only size and type are supported */\n \tif (value && advertise_object_info)\n-\t\tstrbuf_addstr(value, \"size\");\n+\t\tstrbuf_addstr(value, \"size type\");\n \treturn advertise_object_info;\n }\n \ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 116862f9d0..93a70f65b7 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -7,6 +7,7 @@ test_description='git cat-file --batch-command with remote-object-info command'\n \n hello_content=\"Hello World\"\n hello_size=$(strlen \"$hello_content\")\n+hello_type=\"blob\"\n hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n \n@@ -19,6 +20,7 @@ unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdi\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+tree_type=\"tree\"\n \n commit_message=\"Initial commit\"\n \n@@ -31,6 +33,7 @@ commit_message=\"Initial commit\"\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+commit_type=\"commit\"\n \n tag_header_without_oid=\"type blob\n tag hellotag\n@@ -44,6 +47,7 @@ $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+tag_type=\"tag\"\n \n set_transport_variables () {\n \thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n@@ -256,14 +260,12 @@ 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+test_expect_success 'objecttype is supported by remote-object-info' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n \n-\t\techo \"\" >expect &&\n+\t\techo $hello_type >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@@ -271,6 +273,22 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '\n \t)\n '\n \n+test_expect_success 'unsupported placeholders on remote return empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tfmt=\"%(objectmode) %(objectsize:disk) %(rest) %(deltabase)\" &&\n+\n+\t\t# The hardcoded SPs between the atoms are respected.\n+\t\techo \"   \" >expect &&\n+\t\tgit cat-file --batch-command=\"$fmt\" >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 'requesting only objectname echoes back' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n\n-- \n2.54.0\n\n"},{"id":"549369","messageId":"20260731-objecttype-support-v2-6-af577461ed57@gmail.com","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-0-af577461ed57@gmail.com","subject":"[PATCH GSoC v2 6/6] cat-file: unify default format","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-31T19:49:39Z","receivedAt":"2026-07-31T19:50:04Z","isPatch":true,"body":"%(objecttype) is supported both by the client and by the server.\nChange the temporary default format to the unified version that the\nother commands use.\n\nUpdate documentation to remove %(objecttype) from the caveats of\nremote-object-info and show %(objecttype) support.\n\nNow that type is supported and the default format unified, update the\ntests to expect the new default format.\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        | 17 +++++------\n Documentation/gitprotocol-v2.adoc      | 15 +++++++---\n builtin/cat-file.c                     |  7 -----\n t/t1017-cat-file-remote-object-info.sh | 52 +++++++++++++++++-----------------\n 4 files changed, 44 insertions(+), 47 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex ac3b528c6f..514bfc0032 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -348,15 +348,12 @@ 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+The command `remote-object-info` only supports the `%(objectname)`,\n+`%(objectsize)` and `%(objecttype)` placeholders. See `CAVEATS` below for more\n+information.\n \n If no format is specified, the default format is `%(objectname)\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+%(objecttype) %(objectsize)`.\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@@ -453,9 +450,9 @@ 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+Note that only `%(objectname)`, `%(objectsize)` and `%(objecttype)` are\n+currently supported by the `remote-object-info` command. Using any other\n+placeholder in 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/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 7bf62014c3..f0a0573e17 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -558,14 +558,17 @@ object-info\n \n `object-info` is the command to retrieve information about one or more objects.\n Its main purpose is to allow a client to make decisions based on this\n-information without having to fully fetch objects. Object size is the only\n-information that is currently supported.\n+information without having to fully fetch objects. Object size and type are the\n+only information currently supported.\n \n An `object-info` request takes the following arguments:\n \n \tsize\n \tRequests size information to be returned for each listed object id.\n \n+\ttype\n+\tRequests type information to be returned for each listed object id.\n+\n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n \tinformation for. They must be full OIDs.\n@@ -580,11 +583,15 @@ space.\n \tinfo = *PKT-LINE(attr LF)\n \t       *PKT-LINE(obj-info LF)\n \n-\tattr = \"size\"\n+\tattr = \"size\" | \"type\"\n \n \tobj-size = 1*DIGIT\n \n-\tobj-info = obj-id [SP [obj-size]]\n+\tobj-type = \"blob\" | \"tree\" | \"commit\" | \"tag\"\n+\n+\tobj-val = obj-size | obj-type\n+\n+\tobj-info = obj-id [SP [obj-val *(SP obj-val)]]\n \n If the server does not recognize the OID, the response will be `<oid> SP`\n regardless of the number of attributes requested.\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 884b6d5ad3..8288511b19 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -841,15 +841,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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 \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@@ -904,7 +898,6 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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]);\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 93a70f65b7..20a3e99355 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -139,10 +139,10 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\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\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -152,7 +152,7 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\n \t)\n '\n \n-test_expect_success 'remote-object-info does not change the default format of info' '\n+test_expect_success 'remote-object-info and info can be mixed using the unified default format' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n@@ -162,7 +162,7 @@ test_expect_success 'remote-object-info does not change the default format of in\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 \"$hello_oid blob $hello_size\" >>expect &&\n \t\techo \"$local_oid blob $local_size\" >>expect &&\n \n \t\tgit cat-file --batch-command >actual <<-EOF &&\n@@ -209,10 +209,10 @@ test_expect_success 'batch-command -Z remote-object-info git:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -448,10 +448,10 @@ test_expect_success 'batch-command remote-object-info file:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -467,10 +467,10 @@ test_expect_success 'batch-command -Z remote-object-info file:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -618,10 +618,10 @@ test_expect_success 'batch-command remote-object-info http:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -636,10 +636,10 @@ test_expect_success 'batch-command -Z remote-object-info http:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $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-- \n2.54.0\n\n"},{"id":"549370","messageId":"xmqqwluaagxl.fsf@gitster.g","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-1-af577461ed57@gmail.com","subject":"Re: [PATCH GSoC v2 1/6] fetch-object-info: request all supported options dynamically","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-31T23:16:22Z","receivedAt":"2026-07-31T23:16:25Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> In send_object_info_request(), size is hardcoded to be the only option\n> sent. In order to support type and future capabilities, replace the\n> hardcoded size with a loop that requests everything on\n> object_info_options list.\n>\n> This is safe because the list has already been trimmed previously in\n> fetch_object_info() to only contain options that the server supports.\n\nThinking along and aloud to follow the code, my understanding of how\nthe relevant data flows in the code path to get here is as follows:\n\n * 'cat-file --batch-command' processes the remote-object-info\n   command, and parse_cmd_remote_object_info() populates the\n   object_info_options string list.\n\n * It puts 'size' and 'type' into the list if needed.\n\n * get_remote_info() is called, and the string list is attached to\n   the '.object_info_options' member of the '.smart_options'\n   structure of transport.\n\n * transport_fetch_object_info() calls fetch_object_info_via_pack(),\n   where the local args structure receives in its\n   '.object_info_options' member the value pointed to by the\n   '.object_info_options' member of the '.smart_options' structure\n   of 'gtransport'.\n\n * fetch_object_info_via_pack() finally calls fetch_object_info(),\n   which uses server_supports_feature() to check and discard\n   elements from this list that are not supported by the server.\n\nThis is how the 'args' structure seen by send_object_info_request()\ngets prepared.  The \"already been checked to only contain\" comment\nin the code refers to the loop in fetch_object_info() that uses\nserver_supports_feature().\n\nSo, after tracing the code flow up to this point, I agree with the\n\"This is safe\" claim made in the proposed commit log message.\n\nI always get confused while following code paths in the transport\nlayer; my ulterior motivation for this comment is that writing it\ndown once may help refresh my memory the next time I need it.\n\nThanks.\n\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>  fetch-object-info.c | 11 +++++++----\n>  1 file changed, 7 insertions(+), 4 deletions(-)\n>\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> index ba7e179c44..ec8a80b3be 100644\n> --- a/fetch-object-info.c\n> +++ b/fetch-object-info.c\n> @@ -12,13 +12,16 @@\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> +\tstruct string_list_item *item;\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> +\t/*\n> +\t * The list has already been checked to only contain valid and\n> +\t * supported fields, so just request everything remaining on it.\n> +\t */\n> +\tfor_each_string_list_item(item, args->object_info_options)\n> +\t\tpacket_buf_write(&req_buf, \"%s\", item->string);\n>  \n>  \tif (args->oids)\n>  \t\tfor (size_t i = 0; i < args->oids->nr; i++)\n"},{"id":"549373","messageId":"xmqqqzkia2is.fsf@gitster.g","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-2-af577461ed57@gmail.com","subject":"Re: [PATCH GSoC v2 2/6] t5701: use the test_file_size() helper","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-01T04:27:39Z","receivedAt":"2026-08-01T04:27:42Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> An object-info test uses 'wc -c <two.t | xargs' to get the file size.\n> Update it to use the test_file_size() helper instead.\n\nWhat is missing from this description is what is wrong with the use\nof that \"wc -c | xargs\" construct.  What benefit is this change\nsupposed to gain?\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>  t/t5701-git-serve.sh | 4 ++--\n>  1 file changed, 2 insertions(+), 2 deletions(-)\n>\n> diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n> index 9a575aa098..b4d6beef11 100755\n> --- a/t/t5701-git-serve.sh\n> +++ b/t/t5701-git-serve.sh\n> @@ -356,8 +356,8 @@ test_expect_success 'basics of object-info' '\n>  \n>  \tcat >expect <<-EOF &&\n>  \tsize\n> -\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n> -\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n> +\t$(git rev-parse two:two.t) $(test_file_size two.t)\n> +\t$(git rev-parse two:two.t) $(test_file_size two.t)\n>  \t0000\n>  \tEOF\n\nIt is not like we want to avoid piping wc -c into xargs and hide the\nexit status from \"wc -c\".  We are already losing the exit status of\n\"git rev-parse\" anyway.\n\nIf the test after the change were like this\n\n\ttwo_object=$(git rev-parse two:two.t) &&\n\ttwo_size=$(test_file_size two.t) &&\n\tcat >expect <<-EOF &&\n\tsize\n\t$two_object $two_size\n\t$two_object $two_size\n\t0000\n\tEOF\n\nyou can sell it as \"we do not want to lose exit status of 'git\nrev-parse'\", \"we do not need to run the same command twice\", etc.\nBut it is unclear what we gain by rewriting the wc-piped-to-xargs\nto test_file_size.\n\n"},{"id":"549374","messageId":"xmqqecgia181.fsf@gitster.g","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-3-af577461ed57@gmail.com","subject":"Re: [PATCH GSoC v2 3/6] protocol-caps: add type support to object-info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-01T04:55:42Z","receivedAt":"2026-08-01T04:55:44Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> Teach the server-side object-info handler to accept type as a requested\n> field. When the client includes type in its object-info request, the\n> server returns the requested object type.\n>\n> While touching send_info(), wrap an over-long line and fix the bit field\n> style of requested_info.size.\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>  protocol-caps.c      | 21 ++++++++++++++++++---\n>  t/t5701-git-serve.sh | 27 +++++++++++++++++++++++++++\n>  2 files changed, 45 insertions(+), 3 deletions(-)\n>\n> diff --git a/protocol-caps.c b/protocol-caps.c\n> index 02261be14d..27e0f85b10 100644\n> --- a/protocol-caps.c\n> +++ b/protocol-caps.c\n> @@ -11,7 +11,8 @@\n>  #include \"strbuf.h\"\n>  \n>  struct requested_info {\n> -\tunsigned size : 1;\n> +\tunsigned size:1;\n> +\tunsigned type:1;\n>  };\n\nOK.  This matches this bit in our .clang-format file:\n\n    # Add no space around the bit field\n    # unsigned bf:2;\n    BitFieldColonSpacing: None\n\n> @@ -73,15 +74,20 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n>  \tif (info->size)\n>  \t\tpacket_writer_write(writer, \"size\");\n>  \n> +\tif (info->type)\n> +\t\tpacket_writer_write(writer, \"type\");\n> +\n>  \tfor_each_string_list_item (item, oid_str_list) {\n>  \t\tconst char *oid_str = item->string;\n> +\t\tenum object_type object_type;\n>  \t\tstruct object_id oid;\n>  \t\tsize_t object_size;\n>  \n>  \t\tif (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n>  \t\t\tpacket_writer_error(\n>  \t\t\t\twriter,\n> -\t\t\t\t\"object-info: protocol error, expected to get oid, not '%s'\",\n> +\t\t\t\t\"object-info: protocol error, expected to get \"\n> +\t\t\t\t\"oid, not '%s'\",\n>  \t\t\t\toid_str);\n>  \t\t\tcontinue;\n>  \t\t}\n> @@ -93,7 +99,8 @@ static void send_info(struct repository *r, struct packet_writer *writer,\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\tobject_type = get_object_info(r->objects, &oid, &object_size);\n> +\t\tif (object_type <= OBJ_NONE) {\n>  \t\t\tstrbuf_addstr(&send_buffer, \" \");\n>  \t\t\tgoto write;\n>  \t\t}\n\nWe were already learning the object type as part of the existence\ncheck anyway, so we will ...\n\n> @@ -103,6 +110,9 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n>  \t\t\t\t    (uintmax_t)object_size);\n>  \t\t}\n>  \n> +\t\tif (info->type)\n> +\t\t\tstrbuf_addf(&send_buffer, \" %s\", type_name(object_type));\n> +\n\n... add it to the payload.\n\n>  write:\n>  \t\tpacket_writer_write(writer, \"%s\", send_buffer.buf);\n>  \t\tstrbuf_reset(&send_buffer);\n\nANd then the payload is sent in one go.\n\n> @@ -124,6 +134,11 @@ int cap_object_info(struct repository *r, struct packet_reader *request)\n>  \t\t\tcontinue;\n>  \t\t}\n>  \n> +\t\tif (!strcmp(\"type\", request->line)) {\n> +\t\t\tinfo.type = 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n>  \t\tif (parse_oid(request->line, &oid_str_list))\n>  \t\t\tcontinue;\n>  \n> diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n> index b4d6beef11..d7445571b1 100755\n> --- a/t/t5701-git-serve.sh\n> +++ b/t/t5701-git-serve.sh\n> @@ -366,6 +366,33 @@ test_expect_success 'basics of object-info' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'object-info supports type' '\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> +\ttype\n> +\toid $(git rev-parse two:two.t)\n> +\toid $(git rev-parse two:two.t)\n> +\t0000\n> +\tEOF\n\nThis is not something we can change in the middle of this topic, but\nthe input format looks rather curious.  We tell the other side that\nwe are going to ask about size and type but on two separate lines,\nand then throw each object one by one.\n\n> +\tcat >expect <<-EOF &&\n> +\tsize\n> +\ttype\n> +\t$(git rev-parse two:two.t) $(test_file_size two.t) blob\n> +\t$(git rev-parse two:two.t) $(test_file_size two.t) blob\n> +\t0000\n> +\tEOF\n\nAnd the output format is even more curious.  Again, we say size and\ntype on two separate lines, but (object name, size, type) come on a\nsingle line.  I would probably have designed the \"these are the\nfields\" declaration at the beginning to also be on a single line,\nin both directions.  It is not like we are afraid that a line would\ngrow too long.  If we were worried that placing these \"size\" and\n\"type\" labels on the same line would make the line too long, we\nwould certainly be showing object name, size, and type on separate\nlines.\n\nAnyway, the code change looks like what anybody would expect to see\nin a change to add support of \"type\" to a codebase that supports\n\"size\".  As an incremental change, I didn't see anything wrong in\nit, even though the basic protocol design smelled a bit strange.\n\nThanks.\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 'bare OID request' '\n>  \ttest_config transfer.advertiseObjectInfo true &&\n"},{"id":"549375","messageId":"xmqq7bmaa0sw.fsf@gitster.g","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-4-af577461ed57@gmail.com","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-01T05:04:47Z","receivedAt":"2026-08-01T05:04:49Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> @@ -104,8 +105,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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 if (!strcmp(reader->line, \"type\")) {\n> +\t\t\ttype_index = (int)i;\n> +\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n> +\t\t\t\tobject_info_data[j].typep =\n> +\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].typep));\n\nDo object_info_data[j].typep and object_info_data[k].typep need to\nbe independently freeable?  Separate allocations by calling calloc\nargs->oids->nr times would allow that, but if there is no such need,\nnr contiguous allocation of them,\n\n\t\tenum object_type *types;\n\n\t\t*types = xcalloc(args->oids->nr, sizeof(*types));\n\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n\t\t\tobject_info_data[j].typep = &types[j];\n\nwould be simpler to manage and easier to get rid of once you are\ndone.\n\n>  \t\t} else {\n> -\t\t\tBUG(\"only size is supported\");\n> +\t\t\tBUG(\"unexpected object-info option: %s\", reader->line);\n>  \t\t}\n>  \t}\n>  \n> @@ -151,6 +157,10 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tif (type_index >= 0)\n> +\t\t\t*object_info_data[i].typep =\n> +\t\t\t\ttype_from_string(object_info_values.items[type_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"},{"id":"549388","messageId":"CA+J6zkQQsuB3LA=Zg_oEaX7Z-AMhyJf2=ZJYZER=XMPiqaea-w@mail.gmail.com","threadId":"66063","inReplyTo":"20260731-objecttype-support-v2-5-af577461ed57@gmail.com","subject":"Re: [PATCH GSoC v2 5/6] serve: advertise type capability","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-08-01T12:12:44Z","receivedAt":"2026-08-01T12:13:11Z","isPatch":true,"body":"[snip]\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> +test_expect_success 'objecttype is supported by remote-object-info' '\n>         (\n>                 set_transport_variables \"$daemon_parent\" &&\n>                 cd \"$daemon_parent/daemon_client_empty\" &&\n>\n> -               echo \"\" >expect &&\n> +               echo $hello_type >expect &&\n\nShouldn't this be echo \"$hello_type\" instead?\n\n>                 git cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n>                 remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n>                 EOF\n> @@ -271,6 +273,22 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '\n>         )\n>  '\n>\n> +test_expect_success 'unsupported placeholders on remote return empty string' '\n> +       (\n> +               set_transport_variables \"$daemon_parent\" &&\n> +               cd \"$daemon_parent/daemon_client_empty\" &&\n> +\n> +               fmt=\"%(objectmode) %(objectsize:disk) %(rest) %(deltabase)\" &&\n> +\n> +               # The hardcoded SPs between the atoms are respected.\n> +               echo \"   \" >expect &&\n> +               git cat-file --batch-command=\"$fmt\" >actual <<-EOF &&\n> +               remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n> +               EOF\n> +               test_cmp expect actual\n> +       )\n> +'\n> +\n>  test_expect_success 'requesting only objectname echoes back' '\n>         (\n>                 set_transport_variables \"$daemon_parent\" &&\n>\n> --\n> 2.54.0\n>\n"},{"id":"549389","messageId":"xmqqzez67yg1.fsf@gitster.g","threadId":"66063","inReplyTo":"xmqq7bmaa0sw.fsf@gitster.g","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-01T13:38:38Z","receivedAt":"2026-08-01T13:38:41Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> +\t\t} else if (!strcmp(reader->line, \"type\")) {\n>> +\t\t\ttype_index = (int)i;\n>> +\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n>> +\t\t\t\tobject_info_data[j].typep =\n>> +\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].typep));\n>\n> Do object_info_data[j].typep and object_info_data[k].typep need to\n> be independently freeable?  Separate allocations by calling calloc\n> args->oids->nr times would allow that, but if there is no such need,\n> nr contiguous allocation of them,\n\nStepping back a bit, the design of \"odb.h:struct object_info\" look\nrather curious.\n\nWhy does the struct store scalar values like \"enum object_type\" and\n\"size_t\" as a pointer to elsewhere, and does not store the values\nright there in the structure itself?  By forcing the caller to\nallocate an \"enum object_type\" for each of these object_info[]\nelements, the design requires 8-byte for a pointer to the heap and\nmalloc overhead, probably ~16 bytes or more, in addition to store a\nsingle \"enum object_type\" that can be stored in a single byte.\n\nWe are probably using this pointer indirection to say \"ah, typep is\nNULL so the caller did not ask for this information and the object\nlayer does not have to provide one\", plus \"typep is NULL so the\nengine did not give this information for the object\".  But we can do\nso with two bitfields \"unsigned typep_asked:1, typep_valid:1;\"\ninstead of paying ~24-byte or more heap allocation overhead.\n\nAgain, this is not something we can change in the middle of this\ntopic, but since I noticed it and found iffy, I'll leave a note here\nto stir the pot anyway.\n\nStepping back a bit, the design of odb.h:struct object_info looks\nrather curious.\n\nWhy does the struct store scalar values like enum object_type and\nsize_t as pointers to elsewhere, rather than storing the values\nright there in the structure itself?  By forcing the caller to\nallocate an enum object_type for each of these object_info[]\nelements, the design requires an 8-byte pointer to the heap and\nmalloc overhead, probably ~16 bytes or more, to store a single\nenum object_type that could fit in a single byte.\n\nWe are probably using this pointer indirection to say \"ah, '.typep'\nis NULL so the caller did not ask for this information and the\nobject layer does not have to provide it\", plus \"'.typep' is NULL\nso the engine did not give this information for the object\".  But we\ncan do so with two bitfields \n\n    unsigned type_asked:1,\n             type_valid:1;\n\ninstead of paying ~24 bytes or more of heap allocation overhead.\n\nAgain, this is not something we can change in the middle of this\ntopic, but since I noticed it and found it iffy, I'll leave a note\nhere to stir the pot anyway.  It could be something we may want to\nclean-up much later after all the dust settles from this year's\nGSoC.  I dunno.\n"},{"id":"549413","messageId":"DKDWIXJQF4E8.1L2ZF7HSLKNAW@gmail.com","threadId":"66063","inReplyTo":"xmqqqzkia2is.fsf@gitster.g","subject":"Re: [PATCH GSoC v2 2/6] t5701: use the test_file_size() helper","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-01T20:49:17Z","receivedAt":"2026-08-01T20:49:21Z","isPatch":true,"body":"On Sat Aug 1, 2026 at 6:27 AM CEST, Junio C Hamano wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> An object-info test uses 'wc -c <two.t | xargs' to get the file size.\n>> Update it to use the test_file_size() helper instead.\n>\n> What is missing from this description is what is wrong with the use\n> of that \"wc -c | xargs\" construct.  What benefit is this change\n> supposed to gain?\n\nTrue, the patch was small and I did not write the commit message\nproperly.\n\nThe xargs is there to strip the leading blanks that wc adds on some\nOS (I tested this on macOS).\ntest_file_size() reports the size without any padding, so nothing needs\nto be stripped.\n\nI'll write the log correctly next reroll.\n\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>>  t/t5701-git-serve.sh | 4 ++--\n>>  1 file changed, 2 insertions(+), 2 deletions(-)\n>>\n>> diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n>> index 9a575aa098..b4d6beef11 100755\n>> --- a/t/t5701-git-serve.sh\n>> +++ b/t/t5701-git-serve.sh\n>> @@ -356,8 +356,8 @@ test_expect_success 'basics of object-info' '\n>>\n>>  \tcat >expect <<-EOF &&\n>>  \tsize\n>> -\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n>> -\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n>> +\t$(git rev-parse two:two.t) $(test_file_size two.t)\n>> +\t$(git rev-parse two:two.t) $(test_file_size two.t)\n>>  \t0000\n>>  \tEOF\n>\n> It is not like we want to avoid piping wc -c into xargs and hide the\n> exit status from \"wc -c\".  We are already losing the exit status of\n> \"git rev-parse\" anyway.\n>\n> If the test after the change were like this\n>\n> \ttwo_object=$(git rev-parse two:two.t) &&\n> \ttwo_size=$(test_file_size two.t) &&\n> \tcat >expect <<-EOF &&\n> \tsize\n> \t$two_object $two_size\n> \t$two_object $two_size\n> \t0000\n> \tEOF\n>\n> you can sell it as \"we do not want to lose exit status of 'git\n> rev-parse'\", \"we do not need to run the same command twice\", etc.\n> But it is unclear what we gain by rewriting the wc-piped-to-xargs\n> to test_file_size.\n\nI will do that, I'll move the object name and size to variables so they\nare not recomputed.\n\nThanks for the review,\nPablo\n\n\n"},{"id":"549414","messageId":"DKDXCRLFABZK.2DP7XB6NXY9SA@gmail.com","threadId":"66063","inReplyTo":"xmqq7bmaa0sw.fsf@gitster.g","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-01T21:28:15Z","receivedAt":"2026-08-01T21:28:20Z","isPatch":true,"body":"On Sat Aug 1, 2026 at 7:04 AM CEST, Junio C Hamano wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> @@ -104,8 +105,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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 if (!strcmp(reader->line, \"type\")) {\n>> +\t\t\ttype_index = (int)i;\n>> +\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n>> +\t\t\t\tobject_info_data[j].typep =\n>> +\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].typep));\n>\n> Do object_info_data[j].typep and object_info_data[k].typep need to\n> be independently freeable?  Separate allocations by calling calloc\n> args->oids->nr times would allow that, but if there is no such need,\n> nr contiguous allocation of them,\n>\n> \t\tenum object_type *types;\n>\n> \t\t*types = xcalloc(args->oids->nr, sizeof(*types));\n> \t\tfor (size_t j = 0; j < args->oids->nr; j++)\n> \t\t\tobject_info_data[j].typep = &types[j];\n>\n> would be simpler to manage and easier to get rid of once you are\n> done.\n\nHmmmm, they don't need to be independently freeable but they are freed\nby free_object_info_contents() called at the end of\nparse_cmd_remote_object_info() at 'builtin/cat-file.c' in a loop:\n\n\tfor (size_t i = 0; i < object_info_oids.nr; i++)\n\t\tfree_object_info_contents(&remote_object_info[i]);\n\nfree_object_info_contents() is:\n\n\tvoid free_object_info_contents(struct object_info *object_info)\n\t{\n\t\tif (!object_info)\n\t\t\treturn;\n\t\tfree(object_info->typep);\n\t\tfree(object_info->sizep);\n\t\tfree(object_info->disk_sizep);\n\t\tfree(object_info->delta_base_oid);\n\t}\n\nThis function was implemented by the series that introduced\nremote-object-info (the one that this series is based on) so\nparse_cmd_remote_object_info() is the only caller.\n\nThinking about it, your suggestion can be done easily. To free types it\nis enough to do free(remote_object_info[0].typep); (same for sizep).\n\nI'll make it work as a prep patch for size and modify this one to do the\nsame.\nfree_object_info_contents() gets dropped in the prep patch because it\nwould have no callers after it.\n\n>\n>>  \t\t} else {\n>> -\t\t\tBUG(\"only size is supported\");\n>> +\t\t\tBUG(\"unexpected object-info option: %s\", reader->line);\n>>  \t\t}\n>>  \t}\n>>\n>> @@ -151,6 +157,10 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tif (type_index >= 0)\n>> +\t\t\t*object_info_data[i].typep =\n>> +\t\t\t\ttype_from_string(object_info_values.items[type_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\nThanks for the review,\nPablo\n"},{"id":"549415","messageId":"DKDXENJ2KWS8.1ZFRML59BECCW@gmail.com","threadId":"66063","inReplyTo":"CA+J6zkQQsuB3LA=Zg_oEaX7Z-AMhyJf2=ZJYZER=XMPiqaea-w@mail.gmail.com","subject":"Re: [PATCH GSoC v2 5/6] serve: advertise type capability","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-01T21:30:43Z","receivedAt":"2026-08-01T21:30:46Z","isPatch":true,"body":"On Sat Aug 1, 2026 at 2:12 PM CEST, Chandra Pratap wrote:\n> [snip]\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>> +test_expect_success 'objecttype is supported by remote-object-info' '\n>>         (\n>>                 set_transport_variables \"$daemon_parent\" &&\n>>                 cd \"$daemon_parent/daemon_client_empty\" &&\n>>\n>> -               echo \"\" >expect &&\n>> +               echo $hello_type >expect &&\n>\n> Shouldn't this be echo \"$hello_type\" instead?\n\nBecause hello_type is \"blob\" (no SPs) it works but you are right, it\nshould have quotes.\n\nWill fix it.\n\n>\n>>                 git cat-file --batch-command=\"%(objecttype)\" >actual <<-EOF &&\n>>                 remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n>>                 EOF\n>> @@ -271,6 +273,22 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '\n>>         )\n>>  '\n>>\n>> +test_expect_success 'unsupported placeholders on remote return empty string' '\n>> +       (\n>> +               set_transport_variables \"$daemon_parent\" &&\n>> +               cd \"$daemon_parent/daemon_client_empty\" &&\n>> +\n>> +               fmt=\"%(objectmode) %(objectsize:disk) %(rest) %(deltabase)\" &&\n>> +\n>> +               # The hardcoded SPs between the atoms are respected.\n>> +               echo \"   \" >expect &&\n>> +               git cat-file --batch-command=\"$fmt\" >actual <<-EOF &&\n>> +               remote-object-info \"$GIT_DAEMON_URL/parent\" $hello_oid\n>> +               EOF\n>> +               test_cmp expect actual\n>> +       )\n>> +'\n>> +\n>>  test_expect_success 'requesting only objectname echoes back' '\n>>         (\n>>                 set_transport_variables \"$daemon_parent\" &&\n>>\n>> --\n>> 2.54.0\n>>\n\nThanks,\nPablo\n"},{"id":"549419","messageId":"DKDYGQRTSF2W.25OU81K306HJN@gmail.com","threadId":"66063","inReplyTo":"xmqqzez67yg1.fsf@gitster.g","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-01T22:20:28Z","receivedAt":"2026-08-01T22:20:31Z","isPatch":true,"body":"On Sat Aug 1, 2026 at 3:38 PM CEST, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>>> +\t\t} else if (!strcmp(reader->line, \"type\")) {\n>>> +\t\t\ttype_index = (int)i;\n>>> +\t\t\tfor (size_t j = 0; j < args->oids->nr; j++)\n>>> +\t\t\t\tobject_info_data[j].typep =\n>>> +\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].typep));\n>>\n>> Do object_info_data[j].typep and object_info_data[k].typep need to\n>> be independently freeable?  Separate allocations by calling calloc\n>> args->oids->nr times would allow that, but if there is no such need,\n>> nr contiguous allocation of them,\n>\n> Stepping back a bit, the design of \"odb.h:struct object_info\" look\n> rather curious.\n>\n> Why does the struct store scalar values like \"enum object_type\" and\n> \"size_t\" as a pointer to elsewhere, and does not store the values\n> right there in the structure itself?  By forcing the caller to\n> allocate an \"enum object_type\" for each of these object_info[]\n> elements, the design requires 8-byte for a pointer to the heap and\n> malloc overhead, probably ~16 bytes or more, in addition to store a\n> single \"enum object_type\" that can be stored in a single byte.\n>\n> We are probably using this pointer indirection to say \"ah, typep is\n> NULL so the caller did not ask for this information and the object\n> layer does not have to provide one\", plus \"typep is NULL so the\n> engine did not give this information for the object\".  But we can do\n> so with two bitfields \"unsigned typep_asked:1, typep_valid:1;\"\n> instead of paying ~24-byte or more heap allocation overhead.\n>\n> Again, this is not something we can change in the middle of this\n> topic, but since I noticed it and found iffy, I'll leave a note here\n> to stir the pot anyway.\n>\n> Stepping back a bit, the design of odb.h:struct object_info looks\n> rather curious.\n>\n> Why does the struct store scalar values like enum object_type and\n> size_t as pointers to elsewhere, rather than storing the values\n> right there in the structure itself?  By forcing the caller to\n> allocate an enum object_type for each of these object_info[]\n> elements, the design requires an 8-byte pointer to the heap and\n> malloc overhead, probably ~16 bytes or more, to store a single\n> enum object_type that could fit in a single byte.\n>\n> We are probably using this pointer indirection to say \"ah, '.typep'\n> is NULL so the caller did not ask for this information and the\n> object layer does not have to provide it\", plus \"'.typep' is NULL\n> so the engine did not give this information for the object\".  But we\n> can do so with two bitfields\n>\n>     unsigned type_asked:1,\n>              type_valid:1;\n>\n> instead of paying ~24 bytes or more of heap allocation overhead.\n>\n> Again, this is not something we can change in the middle of this\n> topic, but since I noticed it and found it iffy, I'll leave a note\n> here to stir the pot anyway.  It could be something we may want to\n> clean-up much later after all the dust settles from this year's\n> GSoC.  I dunno.\n\n[CC'ing peff]\n\nHi!\n\nI haven't stopped to think about that but it does look strange.\n\nThis is related to what had to be done to fix a bug at \"contents\"\ncommands a few days ago [1].\n\nIn that patch it had to save the previous state of typep and then\nrestore it, because other commands like \"info\" and this series one\n\"remote-object-info\" use this pointer for the \"is this asked?\" question.\n\nIf we take a look at expand_atom():\n\n\t...\n\t} else if (is_atom(\"objecttype\", atom, len)) {\n\t\tif (data->mark_query) {\n\t\t\tdata->info.typep = &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...\n\nexpand_atom() has two responsibilities, it is called at the start to map\nwhich atoms are asked (when data->mark_query), and a second to expand\nthose atoms.\n\nFor example, typep being non-NULL does this effect on these commands:\n\ninfo: makes a type lookup, and fills type.\n\nremote-object-info: typep is directly used to know whether a client has\n                    asked for %(objecttype).\n\nFor both commands what we pay is extra work because at the end the data\nshown is the one expanded from the format.\n\nIt's out of scope for this series but I wanted to add what I know.\n\n[1]: https://lore.kernel.org/git/20260728150031.GA41931@coredump.intra.peff.net/\n\nThanks,\nPablo\n"},{"id":"549420","messageId":"20260801231437.GA2097059@coredump.intra.peff.net","threadId":"66063","inReplyTo":"DKDYGQRTSF2W.25OU81K306HJN@gmail.com","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-08-01T23:14:37Z","receivedAt":"2026-08-01T23:14:39Z","isPatch":true,"body":"On Sun, Aug 02, 2026 at 12:20:28AM +0200, Pablo Sabater wrote:\n\n> > We are probably using this pointer indirection to say \"ah, '.typep'\n> > is NULL so the caller did not ask for this information and the\n> > object layer does not have to provide it\", plus \"'.typep' is NULL\n> > so the engine did not give this information for the object\".  But we\n> > can do so with two bitfields\n> >\n> >     unsigned type_asked:1,\n> >              type_valid:1;\n> >\n> > instead of paying ~24 bytes or more of heap allocation overhead.\n\nYes, this conditional loading is exactly how we use the pointers. I\nagree that a bool would be smaller, though I doubt it really matters in\npractice. You shouldn't have a large number of object_info structs. You\nshould have one that you use over and over. And it does not point to a\nheap allocation, but usually to a stack variable in the caller.\n\nI don't think you'd need type_valid (at least not as the object_info\ncode is written now). If you ask for it, then either the query is\nsatisfied, or we return an error.\n\nI think the pointer system goes all the way back to 9a49059022\n(sha1_object_info_extended(): expose a bit more info, 2011-05-12). It is\nmostly just mirroring the pointers that would be passed directly to the\nfunction (but marshalling them in a struct so callers don't have to pass\na zillion NULLs). So:\n\n  read_object_info(oid, &size);\n\nbecame:\n\n  struct object_info query;\n  query.sizep = &size;\n  read_object_info(oid, &query);\n\nOne minor benefit the pointer system gets you is that the compiler can\nmore easily tell what has been loaded. Imagine that we had a type_asked\nbool, but you forgot to set it. Now you look at oi.type, and it's\ngarbage (or maybe some sentinel value). But the compiler has no clue\nwithout looking at the innards of the read_object_info() function.\n\nWhereas with the pointers, you do this:\n\n  enum object_type type;\n  struct object_info oi = OBJECT_INFO_INIT;\n\n  oi.typep = &type; /* what if we forget this? */\n\n  read_object_info(oid, &oi);\n  do_something(type);\n\nIf you forget the pointer assignment, the compiler will realize that\n\"type\" never got passed to anybody and complain.\n\nI don't know how valuable that is in practice, though.\n\nAnyway, that is the history.\n\n> This is related to what had to be done to fix a bug at \"contents\"\n> commands a few days ago [1].\n> \n> In that patch it had to save the previous state of typep and then\n> restore it, because other commands like \"info\" and this series one\n> \"remote-object-info\" use this pointer for the \"is this asked?\" question.\n\nYes, though you'd have the same thing with bools. You'd have to save\ntype_asked, set it, and then restore it.\n\n> If we take a look at expand_atom():\n> \n> \t...\n> \t} else if (is_atom(\"objecttype\", atom, len)) {\n> \t\tif (data->mark_query) {\n> \t\t\tdata->info.typep = &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...\n> \n> expand_atom() has two responsibilities, it is called at the start to map\n> which atoms are asked (when data->mark_query), and a second to expand\n> those atoms.\n\nYep. But again, you'd have to set the bools somewhere. And it would be\nhere (in the mark_query half).\n\n> For example, typep being non-NULL does this effect on these commands:\n> \n> info: makes a type lookup, and fills type.\n> \n> remote-object-info: typep is directly used to know whether a client has\n>                     asked for %(objecttype).\n> \n> For both commands what we pay is extra work because at the end the data\n> shown is the one expanded from the format.\n\nThere should be no extra work. We do a single read_object_info() that\ngrabs all of the data and writes it into expand_data. If we are getting\ndata from elsewhere (say, a remote server) then we should not be using\nobject_info at all! The concrete data goes into expand_data, which is a\ndata structure specific to cat-file expansion.\n\n-Peff\n"},{"id":"549421","messageId":"20260801232941.GA2097163@coredump.intra.peff.net","threadId":"66063","inReplyTo":"20260801231437.GA2097059@coredump.intra.peff.net","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-08-01T23:29:41Z","receivedAt":"2026-08-01T23:29:43Z","isPatch":true,"body":"On Sat, Aug 01, 2026 at 07:14:37PM -0400, Jeff King wrote:\n\n> Yes, this conditional loading is exactly how we use the pointers. I\n> agree that a bool would be smaller, though I doubt it really matters in\n> practice. You shouldn't have a large number of object_info structs. You\n> should have one that you use over and over. And it does not point to a\n> heap allocation, but usually to a stack variable in the caller.\n> \n> I don't think you'd need type_valid (at least not as the object_info\n> code is written now). If you ask for it, then either the query is\n> satisfied, or we return an error.\n> \n> I think the pointer system goes all the way back to 9a49059022\n> (sha1_object_info_extended(): expose a bit more info, 2011-05-12). It is\n> mostly just mirroring the pointers that would be passed directly to the\n> function (but marshalling them in a struct so callers don't have to pass\n> a zillion NULLs). So:\n> \n>   read_object_info(oid, &size);\n> \n> became:\n> \n>   struct object_info query;\n>   query.sizep = &size;\n>   read_object_info(oid, &query);\n\nOK, I tried to read through the patches here, not having looked at the\ntopic previously. I think it doesn't make sent to use object_info here.\nIt is about collecting the options to make the query for _one_ object,\nso it expects you to point to where it should write the results.\n\nBut what you want is to query N objects and get all of the results back.\nYou _could_ do that with N object_info queries, one per object, like\nthis:\n\n   enum object_type types;\n   struct object_info queries;\n\n   ALLOC_ARRAY(types, nr);\n   CALLOC_ARRAY(queries, nr);\n\n   for (size_t i = 0; i < nr; i++)\n\tqueries[i].typep = &types[i];\n\n   /* you can imagine this is calling read_object_info() in a loop\n    * under the hood */\n   do_many_queries(&oids, nr, &queries);\n\n   /* now we have our answers */\n   for (size_t i = 0; i < nr; i++)\n\tdo_something(oids[i], types[i]);\n\nBut it's kind of silly. Every query is the same, and you'd rather just\npass _one_ query struct that says what you're interested in. But since\nyou are not calling read_object_info() yourself here, why use its query\nstruct? You can make your own using boolean flags or whatever.\n\nAnd I guess that's what started this conversation. The fundamental\ndifference is asking about one object (and using pointers to tell where\nto put the answer) versus asking about N.\n\n-Peff\n"},{"id":"549424","messageId":"xmqqpl015lfl.fsf@gitster.g","threadId":"66063","inReplyTo":"20260801232941.GA2097163@coredump.intra.peff.net","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-02T02:02:38Z","receivedAt":"2026-08-02T02:02:41Z","isPatch":true,"body":"Jeff King <peff@peff.net> writes:\n\n> And I guess that's what started this conversation. The fundamental\n> difference is asking about one object (and using pointers to tell where\n> to put the answer) versus asking about N.\n\nThanks for framing the trouble I had so cleanly.  Yes.\n\nThe origin of the pointer pattern you mentioned, 9a49059022\n(sha1_object_info_extended(): expose a bit more info, 2011-05-12),\ndesigned the object_info structure to be passed as a set of extended\nparameters to sha1_object_info_extended().\n\nInstead of passing 'size_t *size_p' (which can be NULL) as a\nparameter to signal that (1) if NULL we are not interested in the\nvalue, and (2) if not NULL, that is where you are expected to write\nthe answer, and having to keep adding such a pointer parameter\nevery time we need to optionally ask the function for a different\naspect of the object, it defined the function to take an object_info\nstructure to allow us to add new members to the struct as the set\nof queries grows without having to change the function signature.\n\nAs a set of extended parameters, of course, it was natural for the\ncaller's variables that receive the answers to be pointed to by\nmembers in the struct.  So the pointers in the struct are\njustifiable, but strictly as parameters to the function.\n\nThe troubling thing I saw in the patch (and I suspect it was not a\nproblem introduced in this series, but by earlier changes that added\nother kinds of fields) is exactly as you identified.\n\nThe pointers in this struct were meant to point at real variables or\nstructure members that receive values from the function, and were\nnever meant to be the final structure that receives and retains\nreturned values.  If we need 5 calls to the function, we either:\n\n (1) Have a single object_info, and a set of local variables that\n     are pointed at by the members of the object_info structure, and\n     have a loop that runs 5 times where each iteration calls the\n     function to store the returned values in local variables and\n     consumes them, i.e.\n\n        struct oid oid[5];\n        struct object_info oi;\n        for (int i = 0; i < 5; i++) {\n                size_t size;\n                enum object_type type;\n                oi.size_p = &size;\n                oi.type_p = &type;\n                object_info_extended(oid[i], &oi);\n                ... use 'size' and 'type' here ...\n        }\n\n     if you can consume and forget about the object in each\n     iteration, or\n\n (2) Have a single object_info, and 5 sets of local variables.  A\n     loop runs 5 times; in the nth iteration of the loop,\n     object_info points at the nth set of local variables and the\n     function is called.  After the loop runs, we have 5 sets of\n     local variables populated and we use them, i.e.\n\n        struct oid oid[5];\n        struct { size_t size; enum object_type type; } trait[5];\n        struct object_info oi;\n        for (int i = 0; i < 5; i++) {\n                oi.size_p = &trait[i].size;\n                oi.type_p = &trait[i].type;\n                object_info_extended(oid[i], &oi);\n        }\n        ... now you have 'size' and 'type' for all these 5 objects ...\n\n     if you have to return all 5 results to your caller.\n\nIn either case, you do not need more than one object_info\nstructure.  Having an array of object_info structures was what\nlooked so weird to me.\n\n\nThanks.\n"},{"id":"549436","messageId":"DKEGM4BYZ4UW.UVJ1H8IGVF0Q@gmail.com","threadId":"66063","inReplyTo":"xmqqpl015lfl.fsf@gitster.g","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-02T12:33:49Z","receivedAt":"2026-08-02T12:33:52Z","isPatch":true,"body":"On Sun Aug 2, 2026 at 4:02 AM CEST, Junio C Hamano wrote:\n> Jeff King <peff@peff.net> writes:\n>\n>> And I guess that's what started this conversation. The fundamental\n>> difference is asking about one object (and using pointers to tell where\n>> to put the answer) versus asking about N.\n>\n> Thanks for framing the trouble I had so cleanly.  Yes.\n>\n> The origin of the pointer pattern you mentioned, 9a49059022\n> (sha1_object_info_extended(): expose a bit more info, 2011-05-12),\n> designed the object_info structure to be passed as a set of extended\n> parameters to sha1_object_info_extended().\n>\n> Instead of passing 'size_t *size_p' (which can be NULL) as a\n> parameter to signal that (1) if NULL we are not interested in the\n> value, and (2) if not NULL, that is where you are expected to write\n> the answer, and having to keep adding such a pointer parameter\n> every time we need to optionally ask the function for a different\n> aspect of the object, it defined the function to take an object_info\n> structure to allow us to add new members to the struct as the set\n> of queries grows without having to change the function signature.\n>\n> As a set of extended parameters, of course, it was natural for the\n> caller's variables that receive the answers to be pointed to by\n> members in the struct.  So the pointers in the struct are\n> justifiable, but strictly as parameters to the function.\n>\n> The troubling thing I saw in the patch (and I suspect it was not a\n> problem introduced in this series, but by earlier changes that added\n> other kinds of fields) is exactly as you identified.\n>\n> The pointers in this struct were meant to point at real variables or\n> structure members that receive values from the function, and were\n> never meant to be the final structure that receives and retains\n> returned values.  If we need 5 calls to the function, we either:\n>\n>  (1) Have a single object_info, and a set of local variables that\n>      are pointed at by the members of the object_info structure, and\n>      have a loop that runs 5 times where each iteration calls the\n>      function to store the returned values in local variables and\n>      consumes them, i.e.\n>\n>         struct oid oid[5];\n>         struct object_info oi;\n>         for (int i = 0; i < 5; i++) {\n>                 size_t size;\n>                 enum object_type type;\n>                 oi.size_p = &size;\n>                 oi.type_p = &type;\n>                 object_info_extended(oid[i], &oi);\n>                 ... use 'size' and 'type' here ...\n>         }\n>\n>      if you can consume and forget about the object in each\n>      iteration, or\n>\n>  (2) Have a single object_info, and 5 sets of local variables.  A\n>      loop runs 5 times; in the nth iteration of the loop,\n>      object_info points at the nth set of local variables and the\n>      function is called.  After the loop runs, we have 5 sets of\n>      local variables populated and we use them, i.e.\n>\n>         struct oid oid[5];\n>         struct { size_t size; enum object_type type; } trait[5];\n>         struct object_info oi;\n>         for (int i = 0; i < 5; i++) {\n>                 oi.size_p = &trait[i].size;\n>                 oi.type_p = &trait[i].type;\n>                 object_info_extended(oid[i], &oi);\n>         }\n>         ... now you have 'size' and 'type' for all these 5 objects ...\n>\n>      if you have to return all 5 results to your caller.\n>\n> In either case, you do not need more than one object_info\n> structure.  Having an array of object_info structures was what\n> looked so weird to me.\n>\n>\n> Thanks.\n\nThanks, I think I got it.\n\nI have the doubt of whether this change is desired for this series as\nprep or if I should keep on and later make a cleanup series as this\ndoesn't make a change for a user.\n\nWhat I understood is that fetch_object_info shouldn't use object_info to\nstore the results, because it doesn't call read_object_info() like other\ncommands like 'info' do. Then, it should use its own data structure to\nhold the results with flags like wants_size and wants_type. Something\nlike:\n\n\tstruct object_info_results {\n\t\tenum object_type *types;\n\t\tsize_t *sizes;\n\t\tunsigned *unrecognized;\n\t\tsize_t nr;\n\t\tunsigned wants_size:1;\n\t\tunsigned wants_type:1;\n\t};\n\nAll three of the pointers are nr long.\nThis could be done in two patches, as I was going to do a prep to\nprepare the current code (size only) and this patch would add type for\nfetch_object_info().\n\nAt the start I read this:\n\n> It could be something we may want to\n> clean-up much later after all the dust settles from this year's\n> GSoC.  I dunno.\n\nSo I'm a bit lost about what to do, I'm happy to make that in this\nseries or as a cleanup series later after GSoC which ends in a couple\nweeks.\n\nWhatever is preferred.\n\nThanks,\nPablo\n"},{"id":"549439","messageId":"20260802154309.GA17844@coredump.intra.peff.net","threadId":"66063","inReplyTo":"DKEGM4BYZ4UW.UVJ1H8IGVF0Q@gmail.com","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-08-02T15:43:09Z","receivedAt":"2026-08-02T15:43:18Z","isPatch":true,"body":"On Sun, Aug 02, 2026 at 02:33:49PM +0200, Pablo Sabater wrote:\n\n> I have the doubt of whether this change is desired for this series as\n> prep or if I should keep on and later make a cleanup series as this\n> doesn't make a change for a user.\n\nIMHO it is worth doing now. I've spent a bit of time looking through\nthis code and I found it kind of confusing. In particular:\n\n  - there are a lot of semi-opaque structs, like object_info_args. It\n    would seem simpler to me to pass those elements around independently\n    to the functions that need them. Likewise, we seem to stuff a lot of\n    data into the transport struct rather than passing it to the\n    relevant functions, even though many of those elements are really\n    just used for one function call, and aren't a property of the\n    transport at all.\n\n  - it's hard to see who is ultimately responsible for deciding whether\n    a field is requested. I _think_ it comes down to sticking those\n    strings into the object_info_options struct (which I might have\n    called \"attrs\" or \"atoms\" or something; they are syntactically\n    \"options\" in the v2 protocol, but I think from the perspective of\n    this code that is not what they are).\n\n    One thing you could do is have the caller pass in pointers for\n    \"types\" and \"sizes\", and use their NULLness as an indication of what\n    they want.  That would be more like how object_info works, and then\n    it really becomes a low-level protocol details to form those into\n    the v2 protocol option strings.\n\n    But unlike local object_info, we sometimes find that the remote is\n    not willing or able to provide a particular type. So we have to\n    return a flag somehow for \"I was / was not able to get types\". You\n    could do that with a bool in the response struct.\n\n    Or you could flip it on its head. Have the caller provide a bool\n    saying \"I want types\", and then the low-level code is responsible\n    for allocating the \"types\" array, and leaves it NULL if types were\n    not available. That means the caller has to clean up the result, but\n    they'd have had to clean up their local arrays anyway.\n\n    I _think_ that's what you're getting at with your example below.\n\n  - the protocol makes this unnecessarily complex. In particular, if we\n    ask for \"type size\", the server is free to return them in an\n    arbitrary order, or even to omit one of them (even if it told us it\n    supported it!). So we have to map their returned ordering onto our\n    arrays. Gross. I guess we are stuck with it, though, as the server\n    side has been around for a while. And it does indeed choose its own\n    ordering independent of what the client sent.\n\n    I also find the framing needlessly restrictive. Rather than one\n    pkt-line per item, we get packets with space-separated values. What\n    happens when a future item value has spaces in it?\n\n    As a side note, I think this is a good reason not to ship half of a\n    protocol implementation. Without seeing both sides, you don't know\n    what gotchas are lurking. But once one side ships, then it's hard to\n    change the protocol later. This critique may all just me being\n    cranky, though. ;)\n\n> What I understood is that fetch_object_info shouldn't use object_info to\n> store the results, because it doesn't call read_object_info() like other\n> commands like 'info' do. Then, it should use its own data structure to\n> hold the results with flags like wants_size and wants_type. Something\n> like:\n> \n> \tstruct object_info_results {\n> \t\tenum object_type *types;\n> \t\tsize_t *sizes;\n> \t\tunsigned *unrecognized;\n> \t\tsize_t nr;\n> \t\tunsigned wants_size:1;\n> \t\tunsigned wants_type:1;\n> \t};\n> \n> All three of the pointers are nr long.\n\nYeah. At first I thought your wants_size is redundant, but I guess if\nthe goal is for the low-level code to allocate the \"sizes\" array, then\nwe cannot use it as a signal (i.e., this is the \"flip it on its head\"\ndirection I gave above).\n\nI do think you want to be careful with how wants_size interacts with the\n\"object_info_options\" string list. IMHO it would make things easier if\nthat string-ification happened deep down, probably in\nsend_object_info_request(). And then the rest of the code can\nconsistently use wants_size to see if we want sizes (and checking\n\"sizes\" for NULL to cover the case that the server did not support it).\n\n> This could be done in two patches, as I was going to do a prep to\n> prepare the current code (size only) and this patch would add type for\n> fetch_object_info().\n\nYeah, that would make sense.\n\n> > It could be something we may want to\n> > clean-up much later after all the dust settles from this year's\n> > GSoC.  I dunno.\n> \n> So I'm a bit lost about what to do, I'm happy to make that in this\n> series or as a cleanup series later after GSoC which ends in a couple\n> weeks.\n\nI think the \"after the dust settles\" suggestion was for changing the\ninterface of \"struct object_info\". That all becomes moot if we stop\nusing it here entirely. So I think you should proceed along the lines of\nthe object_info_results you showed above.\n\n-Peff\n"},{"id":"549441","messageId":"xmqqcxw04hjm.fsf@gitster.g","threadId":"66063","inReplyTo":"DKEGM4BYZ4UW.UVJ1H8IGVF0Q@gmail.com","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-02T16:24:13Z","receivedAt":"2026-08-02T16:24:15Z","isPatch":true,"body":"\"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n\n> What I understood is that fetch_object_info shouldn't use object_info to\n> store the results, because it doesn't call read_object_info() like other\n> commands like 'info' do. Then, it should use its own data structure to\n> hold the results with flags like wants_size and wants_type. Something\n> like:\n>\n> \tstruct object_info_results {\n> \t\tenum object_type *types;\n> \t\tsize_t *sizes;\n> \t\tunsigned *unrecognized;\n> \t\tsize_t nr;\n> \t\tunsigned wants_size:1;\n> \t\tunsigned wants_type:1;\n> \t};\n\nI would have expected this to be an array of struct, i.e.\n\n\tstruct {\n\t\tstruct oid *oid;\n\t\tenum object_type type;\n\t\tsize_t size;\n\t} *result;\n\tsize_t result_nr, result_alloc;\n\nif you do not have the number of things you query upfront, or it may\nbe an array of fixed size (i.e. no nr/alloc, just nr).\n\nIf you'll be making the same query for many different objects, you\nknow if you are asking for type for all of them or for none of them,\nso depending on how the caller uses it, you may not need the valid\nbit.  Or type==OBJ_NONE could signal \"we have no info\".\n\t\nAnd you'd be using the second pattern I outlined, i.e.\n\n\tfor (size_t it = 0; it < result_nr; it++) {\n        \t/*\n\t\t * you may selectively populate the oi to signal\n\t\t * you do not need some values, but you get the\n        \t * idea.\n\t\t */\n\t\tstruct object_info oi = {\n\t\t\ttype_p = &result[it].type,\n\t\t\tsize_p = &result[it].size,\n\t\t\t...\n\t\t};\n\t\t... ask about result[it].oid using &oi ...\n\t}\n\nto populate the result[] array with values, I would imagine.\n"},{"id":"549442","messageId":"xmqq8q6o4h09.fsf@gitster.g","threadId":"66063","inReplyTo":"20260802154309.GA17844@coredump.intra.peff.net","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-02T16:35:50Z","receivedAt":"2026-08-02T16:35:54Z","isPatch":true,"body":"Jeff King <peff@peff.net> writes:\n\n> I think the \"after the dust settles\" suggestion was for changing the\n> interface of \"struct object_info\". That all becomes moot if we stop\n> using it here entirely. So I think you should proceed along the lines of\n> the object_info_results you showed above.\n\nOK, if we are not using object_info structure at all, then I do not\nthink it matters all that much if it is a struct of arrays or an\narray of structs (even though I suspect the latter would be\ncleaner).\n\nBut I'd rather see it named differently, leaving no room to be\nconfused with the existing object_info structure.\n\nThanks.\n\n"},{"id":"549443","messageId":"20260802163806.GA21296@coredump.intra.peff.net","threadId":"66063","inReplyTo":"xmqqcxw04hjm.fsf@gitster.g","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-08-02T16:38:06Z","receivedAt":"2026-08-02T16:38:08Z","isPatch":true,"body":"On Sun, Aug 02, 2026 at 09:24:13AM -0700, Junio C Hamano wrote:\n\n> \"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n> \n> > What I understood is that fetch_object_info shouldn't use object_info to\n> > store the results, because it doesn't call read_object_info() like other\n> > commands like 'info' do. Then, it should use its own data structure to\n> > hold the results with flags like wants_size and wants_type. Something\n> > like:\n> >\n> > \tstruct object_info_results {\n> > \t\tenum object_type *types;\n> > \t\tsize_t *sizes;\n> > \t\tunsigned *unrecognized;\n> > \t\tsize_t nr;\n> > \t\tunsigned wants_size:1;\n> > \t\tunsigned wants_type:1;\n> > \t};\n> \n> I would have expected this to be an array of struct, i.e.\n> \n> \tstruct {\n> \t\tstruct oid *oid;\n> \t\tenum object_type type;\n> \t\tsize_t size;\n> \t} *result;\n> \tsize_t result_nr, result_alloc;\n> \n> if you do not have the number of things you query upfront, or it may\n> be an array of fixed size (i.e. no nr/alloc, just nr).\n\nI think that could work, but two gotchas:\n\n  - an array-of-struct allocates each item for every object. So if we\n    are only asking about type, we have to allocate nr * size_t space to\n    hold \"size\" fields nobody cares about.\n\n    This is true of object_info, too, but there we don't care about\n    memory cost because we're only using one at a time. Whereas here the\n    intent is to hold many results at once.\n\n  - you do need to signal somewhere whether \"type\" is valid (i.e.,\n    whether the remote side supported it). You can put that flag into\n    the result struct, but it is a little wasteful. It is really a\n    property of the whole query, not of each individual object. So you'd\n    have to carry extra flags around (one per type). Whereas NULL-ness\n    of the array can signal that same information.\n\n> If you'll be making the same query for many different objects, you\n> know if you are asking for type for all of them or for none of them,\n> so depending on how the caller uses it, you may not need the valid\n> bit.  Or type==OBJ_NONE could signal \"we have no info\".\n\nYeah, we sometimes use OBJ_NONE or OBJ_BAD as a sentinel value for type.\nBut if we're not asking for a type field at all, I think that gets\nawkward.\n\nSo for unknown objects, I think a separate bit is less awkward.\n\nFor signaling \"the server refused to tell us this item\" we could use\nsentinel types like OBJ_NONE. But I don't think that extends to other\nfields (e.g., there is no useful sentinel value for \"size\").\n\n> And you'd be using the second pattern I outlined, i.e.\n> \n> \tfor (size_t it = 0; it < result_nr; it++) {\n>         \t/*\n> \t\t * you may selectively populate the oi to signal\n> \t\t * you do not need some values, but you get the\n>         \t * idea.\n> \t\t */\n> \t\tstruct object_info oi = {\n> \t\t\ttype_p = &result[it].type,\n> \t\t\tsize_p = &result[it].size,\n> \t\t\t...\n> \t\t};\n> \t\t... ask about result[it].oid using &oi ...\n> \t}\n> \n> to populate the result[] array with values, I would imagine.\n\nI think that is a perfectly reasonable direction for asking many\nresponses from read_object_info(). But ultimately this is all getting\nshipped to the remote over the object-info protocol. So we never need an\nobject_info at all, and even if we used one, we really would need N of\nthem, because we're going to fill N requests at once (to reduce server\nround-trips).\n\n-Peff\n"},{"id":"549454","messageId":"xmqq33ww40vw.fsf@gitster.g","threadId":"66063","inReplyTo":"20260802163806.GA21296@coredump.intra.peff.net","subject":"Re: [PATCH GSoC v2 4/6] fetch-object-info: parse type from server response","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-02T22:24:03Z","receivedAt":"2026-08-02T22:24:06Z","isPatch":true,"body":"Jeff King <peff@peff.net> writes:\n\n> So for unknown objects, I think a separate bit is less awkward.\n\nOK.\n\n> For signaling \"the server refused to tell us this item\" we could use\n> sentinel types like OBJ_NONE. But I don't think that extends to other\n> fields (e.g., there is no useful sentinel value for \"size\").\n\nTrue, unless we abuse things like ((size_t)-1), which I think I saw\nsomewhere in recently posted patches.\n"},{"id":"549488","messageId":"20260803-objecttype-support-v3-0-7176fecf7950@gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"[PATCH GSoC v3 0/8] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T14:39:27Z","receivedAt":"2026-08-03T14:40:09Z","isPatch":true,"body":"Neither the client nor the server knows how to handle type. This series\ncontinues the work for git cat-file --batch-command extending\nremote-object-info to support \"%(objecttype)\" end to end. It is based on\nits predecessor series \"cat-file: add remote-object-info to\nbatch-command\" [1].\n\nType is the last property that is identical on the server and on the\nclient once fetched.\n\nWhether to support more metadata such as:\n\n- objectsize:disk\n- deltabase\n- objectmode (needs context)\n\nshould be discussed, unlike size and type, the rest depend on how things\nare packed on the server and on what the client already has, so they\ncannot reliably match local once fetched. IMO they are not worth\nsupporting, I can't find a use case for them.\n\nTo add a new placeholder, follow the steps in this series and add its\nwants_* flag to struct fetch_object_info_results.\n\nPatches 1-4 are preparatory. They don't change what the command does:\n- 1/8 is a test cleanup.\n- 2/8 fixes a possible bug in case of a truncated response.\n- 3/8 and 4/8 refactor how the object data is stored and handled. The\n  why about this refactor comes from [2].\n\nPatches 5-8 are the actual objecttype support:\n- 5/8 teaches the server to answer type.\n- 6/8 teaches the client to parse it.\n- 7/8 advertises the capability so the client can start asking it.\n- 8/8 unifies the default format.\n\nNote that wants_type is added in 4/8 while the types array only appears\nin 6/8. This is intentional, it shows that asking for an attribute the\nserver does not advertise just outputs an empty string, as it would for\nany other placeholder.\n\nBased-on: <20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com>\n\nGithub CI: https://github.com/pabloosabaterr/git/actions/runs/30821717187\n\n[1]: https://lore.kernel.org/git/20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com/\n[2]: https://lore.kernel.org/git/xmqqzez67yg1.fsf@gitster.g/\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nChanges in v3:\n- 1/8 (was 2/6): reword the commit message to explain that \"wc -c | xargs\"\n  only strips the padding some platforms add.\n- New 2/8: die if the server sends fewer object-info lines than\n  requested OIDs. Preexisting bug.\n- New 3/8: drop struct object_info_args and pass its members directly to\n  fetch_object_info().\n- New 4/8: replace struct object_info with struct\n  fetch_object_info_results to hold the results. This also drops the\n  object_info_options string list, remote_atom_map[] and\n  free_object_info_contents().\n- Dropped 1/6, which replaced the hardcoded \"size\" in\n  send_object_info_request() with a loop over object_info_options, as\n  that list no longer reaches it.\n- 6/8: follows the new result struct, so it only adds the type array.\n- 7/8: quote $hello_type in the test.\n- Link to v2: https://lore.kernel.org/git/20260731-objecttype-support-v2-0-af577461ed57@gmail.com/\n\nChanges in v2:\n- Changed t5701 test to use test_file_size.\n- Changed the for loop to use for_each_string_list_item().\n- Moved \"fetch-object-info: request all supported options dynamically\"\n  patch to be the first one as it is prep patch.\n- Typos and nits.\n- Restored the test mixing info and remote-object-info, adapted to the\n  unified format.\n- Link to v1: https://lore.kernel.org/git/20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com/\n\n---\nPablo Sabater (8):\n      t5701: use test_file_size() to get the size of a file\n      fetch-object-info: detect truncated server responses\n      fetch-object-info: pass arguments directly instead of a struct\n      fetch-object-info: use dedicated struct for the results\n      protocol-caps: add type support to object-info\n      fetch-object-info: parse type from server response\n      serve: advertise type capability\n      cat-file: unify default format\n\n Documentation/git-cat-file.adoc        |  17 ++--\n Documentation/gitprotocol-v2.adoc      |  18 ++++-\n builtin/cat-file.c                     |  69 +++++-----------\n fetch-object-info.c                    | 141 +++++++++++++++++++++------------\n fetch-object-info.h                    |  37 ++++++---\n object-file.c                          |  10 ---\n odb.h                                  |   3 -\n protocol-caps.c                        |  21 ++++-\n serve.c                                |   4 +-\n t/t1017-cat-file-remote-object-info.sh |  78 +++++++++++-------\n t/t5701-git-serve.sh                   |  41 +++++++++-\n transport.c                            |  12 ++-\n transport.h                            |   5 +-\n 13 files changed, 271 insertions(+), 185 deletions(-)\n\nRange-diff versus v2:\n\n1:  3fb0ea6af4 < -:  ---------- fetch-object-info: request all supported options dynamically\n2:  641911ed87 < -:  ---------- t5701: use the test_file_size() helper\n-:  ---------- > 1:  84810a5500 t5701: use test_file_size() to get the size of a file\n-:  ---------- > 2:  16e2735bd5 fetch-object-info: detect truncated server responses\n-:  ---------- > 3:  f42882ee03 fetch-object-info: pass arguments directly instead of a struct\n-:  ---------- > 4:  3053d33534 fetch-object-info: use dedicated struct for the results\n3:  a77e38e171 ! 5:  1331fab35f protocol-caps: add type support to object-info\n    @@ t/t5701-git-serve.sh: test_expect_success 'basics of object-info' '\n     +test_expect_success 'object-info supports type' '\n     +\ttest_config transfer.advertiseObjectInfo true &&\n     +\n    ++\ttwo_oid=$(git rev-parse two:two.t) &&\n    ++\ttwo_size=$(test_file_size two.t) &&\n    ++\n     +\ttest-tool pkt-line pack >in <<-EOF &&\n     +\tcommand=object-info\n     +\tobject-format=$(test_oid algo)\n     +\t0001\n     +\tsize\n     +\ttype\n    -+\toid $(git rev-parse two:two.t)\n    -+\toid $(git rev-parse two:two.t)\n    ++\toid $two_oid\n    ++\toid $two_oid\n     +\t0000\n     +\tEOF\n     +\n     +\tcat >expect <<-EOF &&\n     +\tsize\n     +\ttype\n    -+\t$(git rev-parse two:two.t) $(test_file_size two.t) blob\n    -+\t$(git rev-parse two:two.t) $(test_file_size two.t) blob\n    ++\t$two_oid $two_size blob\n    ++\t$two_oid $two_size blob\n     +\t0000\n     +\tEOF\n     +\n4:  8fb55d9b54 < -:  ---------- fetch-object-info: parse type from server response\n-:  ---------- > 6:  3929b1a6fc fetch-object-info: parse type from server response\n5:  70e65acd83 ! 7:  35e6b6a068 serve: advertise type capability\n    @@ t/t1017-cat-file-remote-object-info.sh: test_expect_success 'remote-object-info\n      \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n      \n     -\t\techo \"\" >expect &&\n    -+\t\techo $hello_type >expect &&\n    ++\t\techo \"$hello_type\" >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\n6:  4fe367eb58 ! 8:  fe8163ebab cat-file: unify default format\n    @@ Documentation/gitprotocol-v2.adoc: object-info\n      Its main purpose is to allow a client to make decisions based on this\n     -information without having to fully fetch objects. Object size is the only\n     -information that is currently supported.\n    -+information without having to fully fetch objects. Object size and type are the\n    -+only information currently supported.\n    ++information without having to fully fetch objects. Currently only object size\n    ++and type are supported.\n      \n      An `object-info` request takes the following arguments:\n      \n    @@ Documentation/gitprotocol-v2.adoc: space.\n     +\tobj-val = obj-size | obj-type\n     +\n     +\tobj-info = obj-id [SP [obj-val *(SP obj-val)]]\n    ++\n    ++The values in `obj-info` appear in the same order as the corresponding `attr`\n    ++lines, with exactly one value per requested attribute.\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      ## builtin/cat-file.c ##\n     @@ builtin/cat-file.c: static void parse_cmd_remote_object_info(struct batch_options *opt,\n    - \tstruct object_info *remote_object_info = NULL;\n    + \tchar *line_to_split;\n    + \tstruct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;\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    @@ builtin/cat-file.c: static void parse_cmd_remote_object_info(struct batch_option\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_fetch_object_info_results(&results);\n    + \tfree(line_to_split);\n     \n      ## t/t1017-cat-file-remote-object-info.sh ##\n     @@ t/t1017-cat-file-remote-object-info.sh: test_expect_success 'batch-command remote-object-info git:// default filter' '\n\n---\nbase-commit: 71e19e8d2713f385c3fcef59cf6f29bcbd93d91f\nchange-id: 20260724-objecttype-support-ea1ef6941d07\n\n"},{"id":"549489","messageId":"20260803-objecttype-support-v3-1-7176fecf7950@gmail.com","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-0-7176fecf7950@gmail.com","subject":"[PATCH GSoC v3 1/8] t5701: use test_file_size() to get the size of a file","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T14:39:28Z","receivedAt":"2026-08-03T14:40:10Z","isPatch":true,"body":"The 'basics of object-info' test runs 'wc -c | xargs' twice to get the\nsize of two.t. The pipe to xargs is only there to strip the blanks\nthat some platforms pad the output of wc with.\n\nUse the test_file_size() helper, which outputs the size directly, and\nstore the result in a variable. Because 'git rev-parse two:two.t' is\nalso run twice, store its output in a variable as well.\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 t/t5701-git-serve.sh | 11 +++++++----\n 1 file changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 9a575aa098..51d5dd1ae6 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -344,20 +344,23 @@ test_expect_success 'unexpected lines are not allowed in fetch request' '\n test_expect_success 'basics of object-info' '\n \ttest_config transfer.advertiseObjectInfo true &&\n \n+\ttwo_oid=$(git rev-parse two:two.t) &&\n+\ttwo_size=$(test_file_size two.t) &&\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 $(git rev-parse two:two.t)\n-\toid $(git rev-parse two:two.t)\n+\toid $two_oid\n+\toid $two_oid\n \t0000\n \tEOF\n \n \tcat >expect <<-EOF &&\n \tsize\n-\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n-\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n+\t$two_oid $two_size\n+\t$two_oid $two_size\n \t0000\n \tEOF\n \n\n-- \n2.54.0\n\n"},{"id":"549490","messageId":"20260803-objecttype-support-v3-2-7176fecf7950@gmail.com","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-0-7176fecf7950@gmail.com","subject":"[PATCH GSoC v3 2/8] fetch-object-info: detect truncated server responses","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T14:39:29Z","receivedAt":"2026-08-03T14:40:11Z","isPatch":true,"body":"The loop reading the object-info response stops as soon as the reader\nreturns something other than PACKET_READ_NORMAL. A server that somehow\nanswers with fewer objects leaves the end of the result arrays empty.\n\nThe caller trusts that every requested object will be filled in.\n\ndie() if the loop doesn't reach the number of oids expected.\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 fetch-object-info.c | 10 ++++++++--\n 1 file changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex ba7e179c44..cdb7f936f9 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -49,6 +49,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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+\tsize_t i;\n \tint size_index = -1;\n \n \tswitch (version) {\n@@ -82,7 +83,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\tBUG(\"unknown protocol version\");\n \t}\n \n-\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\tfor (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@@ -106,7 +107,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\t}\n \t}\n \n-\tfor (size_t i = 0;\n+\tfor (i = 0;\n \t     packet_reader_read(reader) == PACKET_READ_NORMAL &&\n \t     i < args->oids->nr;\n \t     i++) {\n@@ -150,6 +151,11 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \n \t\tstring_list_clear(&object_info_values, 0);\n \t}\n+\n+\tif (i != args->oids->nr)\n+\t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n+\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n+\n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n \n \treturn 0;\n\n-- \n2.54.0\n\n"},{"id":"549491","messageId":"20260803-objecttype-support-v3-3-7176fecf7950@gmail.com","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-0-7176fecf7950@gmail.com","subject":"[PATCH GSoC v3 3/8] fetch-object-info: pass arguments directly instead of a struct","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T14:39:30Z","receivedAt":"2026-08-03T14:40:13Z","isPatch":true,"body":"struct object_info_args groups three pointers that already live in the\ntransport and are given to fetch_object_info().\nGrouping them into a struct reduces the number of parameters, but it\nsuggests that fetch_object_info() uses all three of them.\n\nDrop the struct and pass those parameters directly to\nfetch_object_info() and send_object_info_request(). This should have no\nchange in behavior.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Junio C Hamano <gitster@pobox.com>\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 fetch-object-info.c | 53 +++++++++++++++++++++++++++++++----------------------\n fetch-object-info.h | 17 ++++++++---------\n transport.c         | 11 +++++------\n 3 files changed, 44 insertions(+), 37 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex cdb7f936f9..a8db196928 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -9,20 +9,24 @@\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+static void send_object_info_request(const int fd_out,\n+\t\t\t\t     const struct string_list *server_options,\n+\t\t\t\t     struct oid_array *oids,\n+\t\t\t\t     struct string_list *object_info_options)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", server_options);\n \n-\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\tif (unsorted_string_list_has_string(object_info_options, \"size\"))\n \t\tpacket_buf_write(&req_buf, \"size\");\n-\telse if (args->object_info_options->nr)\n+\telse if (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+\tif (oids)\n+\t\tfor (size_t i = 0; i < oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\",\n+\t\t\t\t\t oid_to_hex(&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@@ -45,8 +49,12 @@ static int parse_object_size(const char *s, size_t *res)\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+int fetch_object_info(const enum protocol_version version,\n+\t\t      const struct string_list *server_options,\n+\t\t      struct oid_array *oids,\n+\t\t      struct string_list *object_info_options,\n+\t\t      struct packet_reader *reader,\n+\t\t      struct object_info *object_info_data,\n \t\t      const int stateless_rpc, const int fd_out)\n {\n \tsize_t i;\n@@ -65,16 +73,17 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tfor (int i = (int)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\t\t\t\t     object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(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\tsend_object_info_request(fd_out, server_options, oids,\n+\t\t\t\t\t object_info_options);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -83,14 +92,14 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\tBUG(\"unknown protocol version\");\n \t}\n \n-\tfor (i = 0; i < args->object_info_options->nr; i++) {\n+\tfor (i = 0; i < 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 (!unsorted_string_list_has_string(args->object_info_options, reader->line))\n+\t\tif (!unsorted_string_list_has_string(object_info_options, reader->line))\n \t\t\treturn -1;\n \n \t\tif (!strcmp(reader->line, \"size\")) {\n@@ -99,7 +108,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tfor (size_t j = 0; j < 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@@ -109,16 +118,16 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \n \tfor (i = 0;\n \t     packet_reader_read(reader) == PACKET_READ_NORMAL &&\n-\t     i < args->oids->nr;\n+\t     i < 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\t   oid_to_hex(&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    oid_to_hex(&oids->oid[i]),\n \t\t\t    object_info_values.items[0].string);\n \n \t\t/*\n@@ -138,7 +147,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tif (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@@ -152,9 +161,9 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\tstring_list_clear(&object_info_values, 0);\n \t}\n \n-\tif (i != args->oids->nr)\n+\tif (i != oids->nr)\n \t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n-\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n+\t\t    (uintmax_t)oids->nr, (uintmax_t)i);\n \n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n \ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 269cebb3f7..316bf917ce 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -4,22 +4,21 @@\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+struct oid_array;\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+ * Modifies 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,\n+int fetch_object_info(enum protocol_version version,\n+\t\t      const struct string_list *server_options,\n+\t\t      struct oid_array *oids,\n+\t\t      struct string_list *object_info_options,\n+\t\t      struct packet_reader *reader,\n+\t\t      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/transport.c b/transport.c\nindex f0a6a45547..c6df56129d 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -438,11 +438,6 @@ static int fetch_object_info_via_pack(struct transport *transport)\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 \n \tconnect_setup(transport, 0);\n \tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n@@ -453,7 +448,11 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tdata->version = discover_version(&reader);\n \ttransport->hash_algo = reader.hash_algo;\n \n-\tret = fetch_object_info(data->version, &args, &reader,\n+\tret = fetch_object_info(data->version,\n+\t\t\t\ttransport->server_options,\n+\t\t\t\ttransport->smart_options->object_info_oids,\n+\t\t\t\ttransport->smart_options->object_info_options,\n+\t\t\t\t&reader,\n \t\t\t\tdata->options.object_info_data,\n \t\t\t\ttransport->stateless_rpc, data->fd[1]);\n \n\n-- \n2.54.0\n\n"},{"id":"549492","messageId":"20260803-objecttype-support-v3-4-7176fecf7950@gmail.com","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-0-7176fecf7950@gmail.com","subject":"[PATCH GSoC v3 4/8] fetch-object-info: use dedicated struct for the results","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T14:39:31Z","receivedAt":"2026-08-03T14:40:14Z","isPatch":true,"body":"fetch_object_info() collects information about N objects, but it stores\nthe results in an array of object_info. That struct holds the extended\nparameters of read_object_info() (The optional outputs the caller wants\nfilled). Its pointers tell that function where to write the answers for\na single object. object_info is not meant to be the final storage, and\nsince fetch_object_info() does not call read_object_info(), there is no\nreason to use it. Using it means allocating one scalar per object per\nattribute just to have those pointers somewhere to point at.\n\nAdd struct fetch_object_info_results. The caller sets the wants_* flags\nto say what it is interested in, and fetch_object_info() allocates one\narray per attribute. A set wants_* flag means \"asked for\", while a\nnon-NULL array means \"available\". The caller releases the arrays with\nfree_fetch_object_info_results().\n\nThe object_info_options string list is no longer needed. Filtering\nagainst the server's advertisement now sets local ask_* flags, and\nsend_object_info_request() turns those into the v2 protocol option\nstrings. remote_atom_map[] existed only to map those strings back into\natom names, so drop it and build remote_allowed_atoms from the result\narrays.\n\nfree_object_info_contents() loses its only caller and is dropped.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Junio C Hamano <gitster@pobox.com>\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  | 59 +++++++++--------------------------\n fetch-object-info.c | 90 ++++++++++++++++++++++++++++-------------------------\n fetch-object-info.h | 28 ++++++++++++-----\n object-file.c       | 10 ------\n odb.h               |  3 --\n transport.c         |  3 +-\n transport.h         |  5 +--\n 7 files changed, 88 insertions(+), 110 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 884b6d5ad3..c2b88c47f3 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -31,6 +31,7 @@\n #include \"alias.h\"\n #include \"remote.h\"\n #include \"transport.h\"\n+#include \"fetch-object-info.h\"\n \n /*\n  * Maximum length for a remote URL. While no universal standard exists,\n@@ -681,9 +682,8 @@ static void batch_one_object(const char *obj_name,\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-\t\t\t   struct string_list *object_info_options)\n+\t\t\t   struct fetch_object_info_results *results,\n+\t\t\t   struct oid_array *object_info_oids)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n@@ -724,11 +724,9 @@ static int get_remote_info(int argc,\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-\tgtransport->smart_options->object_info_options = object_info_options;\n-\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\tgtransport->smart_options->object_info_results = results;\n \tretval = transport_fetch_object_info(gtransport);\n cleanup:\n \ttransport_disconnect(gtransport);\n@@ -816,21 +814,6 @@ 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@@ -838,9 +821,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tint count;\n \tconst char **argv;\n \tchar *line_to_split;\n-\tstruct object_info *remote_object_info = NULL;\n+\tstruct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;\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@@ -861,26 +843,23 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\t    MAX_ALLOWED_OBJ_LIMIT);\n \n \tif (data->info.sizep)\n-\t\tstring_list_append(&object_info_options, \"size\");\n+\t\tresults.wants_size = 1;\n \tif (data->info.typep)\n-\t\tstring_list_append(&object_info_options, \"type\");\n+\t\tresults.wants_type = 1;\n \n-\tif (get_remote_info(count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids, &object_info_options))\n+\tif (get_remote_info(count, argv, &results, &object_info_oids))\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+\tif (results.sizes)\n+\t\tstring_list_append(&data->remote_allowed_atoms, \"objectsize\");\n \n \tdata->skip_object_info = 1;\n-\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\tfor (size_t i = 0; i < results.nr; i++) {\n \t\tdata->oid = object_info_oids.oid[i];\n \n-\t\tif (remote_object_info[i].unrecognized) {\n+\t\tif (results.unrecognized[i]) {\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@@ -890,13 +869,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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}\n-\n-\t\tif (remote_object_info[i].typep) {\n-\t\t\tdata->type = *remote_object_info[i].typep;\n-\t\t}\n+\t\tif (results.sizes)\n+\t\t\tdata->size = results.sizes[i];\n \n \t\topt->batch_mode = BATCH_MODE_INFO;\n \t\tdata->is_remote = 1;\n@@ -906,12 +880,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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_fetch_object_info_results(&results);\n \tfree(line_to_split);\n \tfree(argv);\n-\tfree(remote_object_info);\n \toid_array_clear(&object_info_oids);\n }\n \ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex a8db196928..ed02c42f6b 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -12,16 +12,18 @@\n static void send_object_info_request(const int fd_out,\n \t\t\t\t     const struct string_list *server_options,\n \t\t\t\t     struct oid_array *oids,\n-\t\t\t\t     struct string_list *object_info_options)\n+\t\t\t\t     unsigned ask_size,\n+\t\t\t\t     unsigned ask_type)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n \twrite_command_and_capabilities(&req_buf, \"object-info\", server_options);\n \n-\tif (unsorted_string_list_has_string(object_info_options, \"size\"))\n+\tif (ask_size)\n \t\tpacket_buf_write(&req_buf, \"size\");\n-\telse if (object_info_options->nr)\n-\t\tBUG(\"only size should be in object_info_options\");\n+\n+\tif (ask_type)\n+\t\tpacket_buf_write(&req_buf, \"type\");\n \n \tif (oids)\n \t\tfor (size_t i = 0; i < oids->nr; i++)\n@@ -52,38 +54,39 @@ static int parse_object_size(const char *s, size_t *res)\n int fetch_object_info(const enum protocol_version version,\n \t\t      const struct string_list *server_options,\n \t\t      struct oid_array *oids,\n-\t\t      struct string_list *object_info_options,\n \t\t      struct packet_reader *reader,\n-\t\t      struct object_info *object_info_data,\n-\t\t      const int stateless_rpc, const int fd_out)\n+\t\t      struct fetch_object_info_results *results,\n+\t\t      const int stateless_rpc,\n+\t\t      const int fd_out)\n {\n-\tsize_t i;\n+\tunsigned ask_size = 0;\n+\tunsigned ask_type = 0;\n \tint size_index = -1;\n+\tsize_t wanted;\n+\tsize_t i;\n+\n+\tresults->nr = oids->nr;\n+\tCALLOC_ARRAY(results->unrecognized, results->nr);\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\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)object_info_options->nr - 1; i >= 0; i--)\n-\t\t\tif (!server_supports_feature(\"object-info\",\n-\t\t\t\t\t\t     object_info_options->items[i].string, 0))\n-\t\t\t\tunsorted_string_list_delete_item(object_info_options, i, 0);\n+\n+\t\tif (results->wants_size &&\n+\t\t    server_supports_feature(\"object-info\", \"size\", 0))\n+\t\t\task_size = 1;\n+\n+\t\tif (results->wants_type &&\n+\t\t    server_supports_feature(\"object-info\", \"type\", 0))\n+\t\t\task_type = 1;\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, server_options, oids,\n-\t\t\t\t\t object_info_options);\n+\t\tsend_object_info_request(fd_out, server_options, oids, ask_size,\n+\t\t\t\t\t ask_type);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -91,26 +94,22 @@ int fetch_object_info(const enum protocol_version version,\n \tcase protocol_unknown_version:\n \t\tBUG(\"unknown protocol version\");\n \t}\n+\twanted = ask_size + ask_type;\n \n-\tfor (i = 0; i < object_info_options->nr; i++) {\n+\tfor (i = 0; i < wanted; 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 (!unsorted_string_list_has_string(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\tif (!ask_size)\n+\t\t\t\tdie(_(\"object-info: unrequested 'size' attribute\"));\n+\t\t\tif (results->sizes)\n+\t\t\t\tdie(_(\"object-info: duplicate 'size' attribute\"));\n \t\t\tsize_index = (int)i;\n-\t\t\tfor (size_t j = 0; j < 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\tCALLOC_ARRAY(results->sizes, results->nr);\n \t\t} else {\n \t\t\tBUG(\"only size is supported\");\n \t\t}\n@@ -137,24 +136,24 @@ int fetch_object_info(const enum protocol_version version,\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\tresults->unrecognized[i] = 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 * Because we only ask for attributes the server said it\n+\t\t * supports, we expect the answer to have one value per\n+\t\t * requested attribute, plus the OID.\n \t\t */\n-\t\tif (object_info_options->nr + 1 != object_info_values.nr)\n+\t\tif (wanted + 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\tif (results->sizes &&\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\t      &results->sizes[i]))\n+\t\t\tdie(\"object-info: object %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@@ -169,3 +168,10 @@ int fetch_object_info(const enum protocol_version version,\n \n \treturn 0;\n }\n+\n+void free_fetch_object_info_results(struct fetch_object_info_results *results)\n+{\n+\tfree(results->sizes);\n+\tfree(results->unrecognized);\n+\tmemset(results, 0, sizeof(*results));\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 316bf917ce..c472c14d7e 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -4,21 +4,35 @@\n #include \"pkt-line.h\"\n #include \"protocol.h\"\n \n-struct object_info;\n+struct fetch_object_info_results {\n+\tsize_t *sizes;\n+\tuint8_t *unrecognized;\n+\tsize_t nr;\n+\tunsigned wants_size:1;\n+\tunsigned wants_type:1;\n+};\n+\n+#define FETCH_OBJECT_INFO_RESULTS_INIT { 0 }\n+\n struct oid_array;\n /*\n- * Sends git-cat-file object-info command into the request buf and read the\n+ * Sends git-cat-file object-info command into the request buf and reads the\n  * results from packets.\n  *\n- * Modifies object_info_options, on return it contains only the supported\n- * options by the server.\n+ * The caller sets the wants_* flags in \"results\" to indicate which attributes\n+ * it is interested in. On return, \"results\" holds one array per attribute that\n+ * the server both advertised and answered with. An array left NULL means the\n+ * attribute is not available.\n+ * Release them with free_fetch_object_info_results().\n  */\n int fetch_object_info(enum protocol_version version,\n \t\t      const struct string_list *server_options,\n \t\t      struct oid_array *oids,\n-\t\t      struct string_list *object_info_options,\n \t\t      struct packet_reader *reader,\n-\t\t      struct object_info *object_info_data,\n-\t\t      int stateless_rpc, int fd_out);\n+\t\t      struct fetch_object_info_results *results,\n+\t\t      int stateless_rpc,\n+\t\t      int fd_out);\n+\n+void free_fetch_object_info_results(struct fetch_object_info_results *results);\n \n #endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/object-file.c b/object-file.c\nindex c5809db598..7ff2b730ac 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1740,13 +1740,3 @@ 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 3f7c483656..b7bc0ee844 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -635,7 +635,4 @@ 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/transport.c b/transport.c\nindex c6df56129d..35d3e98d97 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -451,9 +451,8 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tret = fetch_object_info(data->version,\n \t\t\t\ttransport->server_options,\n \t\t\t\ttransport->smart_options->object_info_oids,\n-\t\t\t\ttransport->smart_options->object_info_options,\n \t\t\t\t&reader,\n-\t\t\t\tdata->options.object_info_data,\n+\t\t\t\tdata->options.object_info_results,\n \t\t\t\ttransport->stateless_rpc, data->fd[1]);\n \n \tclose(data->fd[0]);\ndiff --git a/transport.h b/transport.h\nindex a7869d18e0..6948b65db9 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -7,6 +7,8 @@\n #include \"string-list.h\"\n #include \"connect.h\"\n \n+struct fetch_object_info_results;\n+\n struct git_transport_options {\n \tunsigned thin : 1;\n \tunsigned keep : 1;\n@@ -57,8 +59,7 @@ 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 string_list *object_info_options;\n+\tstruct fetch_object_info_results *object_info_results;\n };\n \n enum transport_family {\n\n-- \n2.54.0\n\n"},{"id":"549493","messageId":"20260803-objecttype-support-v3-5-7176fecf7950@gmail.com","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-0-7176fecf7950@gmail.com","subject":"[PATCH GSoC v3 5/8] protocol-caps: add type support to object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T14:39:32Z","receivedAt":"2026-08-03T14:40:15Z","isPatch":true,"body":"Teach the server-side object-info handler to accept type as a requested\nfield. When the client includes type in its object-info request, the\nserver returns the requested object type.\n\nWhile touching send_info(), wrap an over-long line and fix the bit field\nstyle of requested_info.size.\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 protocol-caps.c      | 21 ++++++++++++++++++---\n t/t5701-git-serve.sh | 30 ++++++++++++++++++++++++++++++\n 2 files changed, 48 insertions(+), 3 deletions(-)\n\ndiff --git a/protocol-caps.c b/protocol-caps.c\nindex 02261be14d..27e0f85b10 100644\n--- a/protocol-caps.c\n+++ b/protocol-caps.c\n@@ -11,7 +11,8 @@\n #include \"strbuf.h\"\n \n struct requested_info {\n-\tunsigned size : 1;\n+\tunsigned size:1;\n+\tunsigned type:1;\n };\n \n /*\n@@ -73,15 +74,20 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \tif (info->size)\n \t\tpacket_writer_write(writer, \"size\");\n \n+\tif (info->type)\n+\t\tpacket_writer_write(writer, \"type\");\n+\n \tfor_each_string_list_item (item, oid_str_list) {\n \t\tconst char *oid_str = item->string;\n+\t\tenum object_type object_type;\n \t\tstruct object_id oid;\n \t\tsize_t object_size;\n \n \t\tif (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n \t\t\tpacket_writer_error(\n \t\t\t\twriter,\n-\t\t\t\t\"object-info: protocol error, expected to get oid, not '%s'\",\n+\t\t\t\t\"object-info: protocol error, expected to get \"\n+\t\t\t\t\"oid, not '%s'\",\n \t\t\t\toid_str);\n \t\t\tcontinue;\n \t\t}\n@@ -93,7 +99,8 @@ static void send_info(struct repository *r, struct packet_writer *writer,\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\tobject_type = get_object_info(r->objects, &oid, &object_size);\n+\t\tif (object_type <= OBJ_NONE) {\n \t\t\tstrbuf_addstr(&send_buffer, \" \");\n \t\t\tgoto write;\n \t\t}\n@@ -103,6 +110,9 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \t\t\t\t    (uintmax_t)object_size);\n \t\t}\n \n+\t\tif (info->type)\n+\t\t\tstrbuf_addf(&send_buffer, \" %s\", type_name(object_type));\n+\n write:\n \t\tpacket_writer_write(writer, \"%s\", send_buffer.buf);\n \t\tstrbuf_reset(&send_buffer);\n@@ -124,6 +134,11 @@ int cap_object_info(struct repository *r, struct packet_reader *request)\n \t\t\tcontinue;\n \t\t}\n \n+\t\tif (!strcmp(\"type\", request->line)) {\n+\t\t\tinfo.type = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif (parse_oid(request->line, &oid_str_list))\n \t\t\tcontinue;\n \ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 51d5dd1ae6..f57e36a88d 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -369,6 +369,36 @@ test_expect_success 'basics of object-info' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'object-info supports type' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttwo_oid=$(git rev-parse two:two.t) &&\n+\ttwo_size=$(test_file_size two.t) &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\tsize\n+\ttype\n+\toid $two_oid\n+\toid $two_oid\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tsize\n+\ttype\n+\t$two_oid $two_size blob\n+\t$two_oid $two_size blob\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 'bare OID request' '\n \ttest_config transfer.advertiseObjectInfo true &&\n \n\n-- \n2.54.0\n\n"},{"id":"549494","messageId":"20260803-objecttype-support-v3-6-7176fecf7950@gmail.com","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-0-7176fecf7950@gmail.com","subject":"[PATCH GSoC v3 6/8] fetch-object-info: parse type from server response","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T14:39:33Z","receivedAt":"2026-08-03T14:40:16Z","isPatch":true,"body":"The server can handle type requests but does not advertise the\ncapability yet. Prepare the client to know how to parse the server\nresponse once the server advertises the capability.\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  |  5 +++++\n fetch-object-info.c | 24 +++++++++++++++++++++++-\n fetch-object-info.h |  2 ++\n 3 files changed, 30 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex c2b88c47f3..7a3ae11a70 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -854,6 +854,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n \tif (results.sizes)\n \t\tstring_list_append(&data->remote_allowed_atoms, \"objectsize\");\n+\tif (results.types)\n+\t\tstring_list_append(&data->remote_allowed_atoms, \"objecttype\");\n \n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < results.nr; i++) {\n@@ -872,6 +874,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tif (results.sizes)\n \t\t\tdata->size = results.sizes[i];\n \n+\t\tif (results.types)\n+\t\t\tdata->type = results.types[i];\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);\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex ed02c42f6b..2a67a669f6 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n+#include \"object.h\"\n #include \"pkt-line.h\"\n #include \"connect.h\"\n #include \"oid-array.h\"\n@@ -62,6 +63,7 @@ int fetch_object_info(const enum protocol_version version,\n \tunsigned ask_size = 0;\n \tunsigned ask_type = 0;\n \tint size_index = -1;\n+\tint type_index = -1;\n \tsize_t wanted;\n \tsize_t i;\n \n@@ -110,8 +112,15 @@ int fetch_object_info(const enum protocol_version version,\n \t\t\t\tdie(_(\"object-info: duplicate 'size' attribute\"));\n \t\t\tsize_index = (int)i;\n \t\t\tCALLOC_ARRAY(results->sizes, results->nr);\n+\t\t} else if (!strcmp(reader->line, \"type\")) {\n+\t\t\tif (!ask_type)\n+\t\t\t\tdie(_(\"object-info: unrequested 'type' attribute\"));\n+\t\t\tif (results->types)\n+\t\t\t\tdie(_(\"object-info: duplicate 'type' attribute\"));\n+\t\t\ttype_index = (int)i;\n+\t\t\tCALLOC_ARRAY(results->types, results->nr);\n \t\t} else {\n-\t\t\tBUG(\"only size is supported\");\n+\t\t\tBUG(\"unexpected object-info option: %s\", reader->line);\n \t\t}\n \t}\n \n@@ -157,6 +166,18 @@ int fetch_object_info(const enum protocol_version version,\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\tif (results->types) {\n+\t\t\tconst char *type_str =\n+\t\t\t\tobject_info_values.items[type_index + 1].string;\n+\t\t\tint type = type_from_string_gently(type_str, -1, 1);\n+\n+\t\t\tif (type < 0)\n+\t\t\t\tdie(_(\"object-info: object %s has invalid type '%s'\"),\n+\t\t\t\t    object_info_values.items[0].string, type_str);\n+\n+\t\t\tresults->types[i] = type;\n+\t\t}\n+\n \t\tstring_list_clear(&object_info_values, 0);\n \t}\n \n@@ -172,6 +193,7 @@ int fetch_object_info(const enum protocol_version version,\n void free_fetch_object_info_results(struct fetch_object_info_results *results)\n {\n \tfree(results->sizes);\n+\tfree(results->types);\n \tfree(results->unrecognized);\n \tmemset(results, 0, sizeof(*results));\n }\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex c472c14d7e..310325cd98 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -1,11 +1,13 @@\n #ifndef FETCH_OBJECT_INFO_H\n #define FETCH_OBJECT_INFO_H\n \n+#include \"object.h\"\n #include \"pkt-line.h\"\n #include \"protocol.h\"\n \n struct fetch_object_info_results {\n \tsize_t *sizes;\n+\tenum object_type *types;\n \tuint8_t *unrecognized;\n \tsize_t nr;\n \tunsigned wants_size:1;\n\n-- \n2.54.0\n\n"},{"id":"549495","messageId":"20260803-objecttype-support-v3-7-7176fecf7950@gmail.com","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-0-7176fecf7950@gmail.com","subject":"[PATCH GSoC v3 7/8] serve: advertise type capability","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T14:39:34Z","receivedAt":"2026-08-03T14:40:17Z","isPatch":true,"body":"The server and the client can handle type requests but the client won't\nask for it until the server advertises it.\n\nAdd type to the advertised capabilities so the client knows that it can\nrequest 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 serve.c                                |  4 ++--\n t/t1017-cat-file-remote-object-info.sh | 26 ++++++++++++++++++++++----\n 2 files changed, 24 insertions(+), 6 deletions(-)\n\ndiff --git a/serve.c b/serve.c\nindex 2b07d922b3..2ce513cf2d 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -97,9 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value)\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n-\t/* Currently only size is supported */\n+\t/* Currently only size and type are supported */\n \tif (value && advertise_object_info)\n-\t\tstrbuf_addstr(value, \"size\");\n+\t\tstrbuf_addstr(value, \"size type\");\n \treturn advertise_object_info;\n }\n \ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 116862f9d0..190c45eefc 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -7,6 +7,7 @@ test_description='git cat-file --batch-command with remote-object-info command'\n \n hello_content=\"Hello World\"\n hello_size=$(strlen \"$hello_content\")\n+hello_type=\"blob\"\n hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n \n@@ -19,6 +20,7 @@ unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdi\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+tree_type=\"tree\"\n \n commit_message=\"Initial commit\"\n \n@@ -31,6 +33,7 @@ commit_message=\"Initial commit\"\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+commit_type=\"commit\"\n \n tag_header_without_oid=\"type blob\n tag hellotag\n@@ -44,6 +47,7 @@ $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+tag_type=\"tag\"\n \n set_transport_variables () {\n \thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n@@ -256,14 +260,12 @@ 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+test_expect_success 'objecttype is supported by remote-object-info' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n \n-\t\techo \"\" >expect &&\n+\t\techo \"$hello_type\" >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@@ -271,6 +273,22 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '\n \t)\n '\n \n+test_expect_success 'unsupported placeholders on remote return empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tfmt=\"%(objectmode) %(objectsize:disk) %(rest) %(deltabase)\" &&\n+\n+\t\t# The hardcoded SPs between the atoms are respected.\n+\t\techo \"   \" >expect &&\n+\t\tgit cat-file --batch-command=\"$fmt\" >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 'requesting only objectname echoes back' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n\n-- \n2.54.0\n\n"},{"id":"549496","messageId":"20260803-objecttype-support-v3-8-7176fecf7950@gmail.com","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-0-7176fecf7950@gmail.com","subject":"[PATCH GSoC v3 8/8] cat-file: unify default format","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T14:39:35Z","receivedAt":"2026-08-03T14:40:19Z","isPatch":true,"body":"%(objecttype) is supported both by the client and by the server.\nChange the temporary default format to the unified version that the\nother commands use.\n\nUpdate documentation to remove %(objecttype) from the caveats of\nremote-object-info and show %(objecttype) support.\n\nNow that type is supported and the default format unified, update the\ntests to expect the new default format.\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        | 17 +++++------\n Documentation/gitprotocol-v2.adoc      | 18 +++++++++---\n builtin/cat-file.c                     |  7 -----\n t/t1017-cat-file-remote-object-info.sh | 52 +++++++++++++++++-----------------\n 4 files changed, 47 insertions(+), 47 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex ac3b528c6f..514bfc0032 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -348,15 +348,12 @@ 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+The command `remote-object-info` only supports the `%(objectname)`,\n+`%(objectsize)` and `%(objecttype)` placeholders. See `CAVEATS` below for more\n+information.\n \n If no format is specified, the default format is `%(objectname)\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+%(objecttype) %(objectsize)`.\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@@ -453,9 +450,9 @@ 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+Note that only `%(objectname)`, `%(objectsize)` and `%(objecttype)` are\n+currently supported by the `remote-object-info` command. Using any other\n+placeholder in 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/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 7bf62014c3..dd52fd8110 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -558,14 +558,17 @@ object-info\n \n `object-info` is the command to retrieve information about one or more objects.\n Its main purpose is to allow a client to make decisions based on this\n-information without having to fully fetch objects. Object size is the only\n-information that is currently supported.\n+information without having to fully fetch objects. Currently only object size\n+and type are supported.\n \n An `object-info` request takes the following arguments:\n \n \tsize\n \tRequests size information to be returned for each listed object id.\n \n+\ttype\n+\tRequests type information to be returned for each listed object id.\n+\n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n \tinformation for. They must be full OIDs.\n@@ -580,11 +583,18 @@ space.\n \tinfo = *PKT-LINE(attr LF)\n \t       *PKT-LINE(obj-info LF)\n \n-\tattr = \"size\"\n+\tattr = \"size\" | \"type\"\n \n \tobj-size = 1*DIGIT\n \n-\tobj-info = obj-id [SP [obj-size]]\n+\tobj-type = \"blob\" | \"tree\" | \"commit\" | \"tag\"\n+\n+\tobj-val = obj-size | obj-type\n+\n+\tobj-info = obj-id [SP [obj-val *(SP obj-val)]]\n+\n+The values in `obj-info` appear in the same order as the corresponding `attr`\n+lines, with exactly one value per requested attribute.\n \n If the server does not recognize the OID, the response will be `<oid> SP`\n regardless of the number of attributes requested.\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 7a3ae11a70..867079a62e 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -823,15 +823,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;\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@@ -883,7 +877,6 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n-\topt->format = saved_format;\n \n \tfree_fetch_object_info_results(&results);\n \tfree(line_to_split);\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 190c45eefc..e2919aa061 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -139,10 +139,10 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\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\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -152,7 +152,7 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\n \t)\n '\n \n-test_expect_success 'remote-object-info does not change the default format of info' '\n+test_expect_success 'remote-object-info and info can be mixed using the unified default format' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n@@ -162,7 +162,7 @@ test_expect_success 'remote-object-info does not change the default format of in\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 \"$hello_oid blob $hello_size\" >>expect &&\n \t\techo \"$local_oid blob $local_size\" >>expect &&\n \n \t\tgit cat-file --batch-command >actual <<-EOF &&\n@@ -209,10 +209,10 @@ test_expect_success 'batch-command -Z remote-object-info git:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -448,10 +448,10 @@ test_expect_success 'batch-command remote-object-info file:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -467,10 +467,10 @@ test_expect_success 'batch-command -Z remote-object-info file:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -618,10 +618,10 @@ test_expect_success 'batch-command remote-object-info http:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -636,10 +636,10 @@ test_expect_success 'batch-command -Z remote-object-info http:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $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-- \n2.54.0\n\n"},{"id":"549507","messageId":"xmqqbjbjyv9s.fsf@gitster.g","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-1-7176fecf7950@gmail.com","subject":"Re: [PATCH GSoC v3 1/8] t5701: use test_file_size() to get the size of a file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-03T17:21:51Z","receivedAt":"2026-08-03T17:21:53Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> The 'basics of object-info' test runs 'wc -c | xargs' twice to get the\n> size of two.t. The pipe to xargs is only there to strip the blanks\n> that some platforms pad the output of wc with.\n>\n> Use the test_file_size() helper, which outputs the size directly, and\n> store the result in a variable. Because 'git rev-parse two:two.t' is\n> also run twice, store its output in a variable as well.\n\nIt also has the benefit of retaining the exit status from commands\nrun inside a $( ... ) construct placed within a HERE-document.\nEarlier, if your \"git rev-parse\" failed, you would not have noticed\nit directly (though you would probably have seen the \"expect\" file\ncontaining unexpected content).  Now your assignment fails when you\ncompute two_oid, if your \"git rev-parse\" segfaults.\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>  t/t5701-git-serve.sh | 11 +++++++----\n>  1 file changed, 7 insertions(+), 4 deletions(-)\n>\n> diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n> index 9a575aa098..51d5dd1ae6 100755\n> --- a/t/t5701-git-serve.sh\n> +++ b/t/t5701-git-serve.sh\n> @@ -344,20 +344,23 @@ test_expect_success 'unexpected lines are not allowed in fetch request' '\n>  test_expect_success 'basics of object-info' '\n>  \ttest_config transfer.advertiseObjectInfo true &&\n>  \n> +\ttwo_oid=$(git rev-parse two:two.t) &&\n> +\ttwo_size=$(test_file_size two.t) &&\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 $(git rev-parse two:two.t)\n> -\toid $(git rev-parse two:two.t)\n> +\toid $two_oid\n> +\toid $two_oid\n>  \t0000\n>  \tEOF\n>  \n>  \tcat >expect <<-EOF &&\n>  \tsize\n> -\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n> -\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n> +\t$two_oid $two_size\n> +\t$two_oid $two_size\n>  \t0000\n>  \tEOF\n"},{"id":"549509","messageId":"xmqq7bm7yso6.fsf@gitster.g","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-2-7176fecf7950@gmail.com","subject":"Re: [PATCH GSoC v3 2/8] fetch-object-info: detect truncated server responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-03T18:18:01Z","receivedAt":"2026-08-03T18:18:04Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> The loop reading the object-info response stops as soon as the reader\n> returns something other than PACKET_READ_NORMAL. A server that somehow\n> answers with fewer objects leaves the end of the result arrays empty.\n>\n> The caller trusts that every requested object will be filled in.\n>\n> die() if the loop doesn't reach the number of oids expected.\n\nThis tightening is obviously a good thing to do.\n\nThe above description makes me wonder what happens if the other side\nsends responses for more objects than we requested.  We allocate for\nN objects and loop for up to N iterations, so we will not read more\nthan N.  But do we detect that we are out of sync when we read the\nresponse to our next request, or before we shut down the connection\nif we do not have any further requests?\n\n> @@ -49,6 +49,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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> +\tsize_t i;\n>  \tint size_index = -1;\n>  \n>  \tswitch (version) {\n> @@ -82,7 +83,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>  \t\tBUG(\"unknown protocol version\");\n>  \t}\n>  \n> -\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n> +\tfor (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> @@ -106,7 +107,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>  \t\t}\n>  \t}\n>  \n> -\tfor (size_t i = 0;\n> +\tfor (i = 0;\n>  \t     packet_reader_read(reader) == PACKET_READ_NORMAL &&\n>  \t     i < args->oids->nr;\n>  \t     i++) {\n> @@ -150,6 +151,11 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>  \n>  \t\tstring_list_clear(&object_info_values, 0);\n>  \t}\n> +\n> +\tif (i != args->oids->nr)\n> +\t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n> +\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n\nOK.  We give 'i' a bit longer lifetime so that we can do this\ncomparison, which is inevitable.\n\n>  \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n>  \n>  \treturn 0;\n"},{"id":"549510","messageId":"xmqq33wvyseb.fsf@gitster.g","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-3-7176fecf7950@gmail.com","subject":"Re: [PATCH GSoC v3 3/8] fetch-object-info: pass arguments directly instead of a struct","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-03T18:23:56Z","receivedAt":"2026-08-03T18:23:59Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> struct object_info_args groups three pointers that already live in the\n> transport and are given to fetch_object_info().\n> Grouping them into a struct reduces the number of parameters, but it\n> suggests that fetch_object_info() uses all three of them.\n>\n> Drop the struct and pass those parameters directly to\n> fetch_object_info() and send_object_info_request(). This should have no\n> change in behavior.\n>\n> Helped-by: Jeff King <peff@peff.net>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\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>  fetch-object-info.c | 53 +++++++++++++++++++++++++++++++----------------------\n>  fetch-object-info.h | 17 ++++++++---------\n>  transport.c         | 11 +++++------\n>  3 files changed, 44 insertions(+), 37 deletions(-)\n\nThis has lots of changes but quite straight-forward.  We lose the\nintermediate wrapper \"object_info_args\" and the uses of the members\nof that wrapper structure are updated with direct reference to a\nvariable.\n\n"},{"id":"549511","messageId":"xmqqwlu7xdmb.fsf@gitster.g","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-4-7176fecf7950@gmail.com","subject":"Re: [PATCH GSoC v3 4/8] fetch-object-info: use dedicated struct for the results","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-03T18:28:28Z","receivedAt":"2026-08-03T18:28:31Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> fetch_object_info() collects information about N objects, but it stores\n> the results in an array of object_info. That struct holds the extended\n> parameters of read_object_info() (The optional outputs the caller wants\n> filled). Its pointers tell that function where to write the answers for\n> a single object. object_info is not meant to be the final storage, and\n> since fetch_object_info() does not call read_object_info(), there is no\n> reason to use it. Using it means allocating one scalar per object per\n> attribute just to have those pointers somewhere to point at.\n>\n> Add struct fetch_object_info_results. The caller sets the wants_* flags\n> to say what it is interested in, and fetch_object_info() allocates one\n> array per attribute. A set wants_* flag means \"asked for\", while a\n> non-NULL array means \"available\". The caller releases the arrays with\n> free_fetch_object_info_results().\n>\n> The object_info_options string list is no longer needed. Filtering\n> against the server's advertisement now sets local ask_* flags, and\n> send_object_info_request() turns those into the v2 protocol option\n> strings. remote_atom_map[] existed only to map those strings back into\n> atom names, so drop it and build remote_allowed_atoms from the result\n> arrays.\n>\n> free_object_info_contents() loses its only caller and is dropped.\n>\n> Helped-by: Jeff King <peff@peff.net>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\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  | 59 +++++++++--------------------------\n>  fetch-object-info.c | 90 ++++++++++++++++++++++++++++-------------------------\n>  fetch-object-info.h | 28 ++++++++++++-----\n>  object-file.c       | 10 ------\n>  odb.h               |  3 --\n>  transport.c         |  3 +-\n>  transport.h         |  5 +--\n>  7 files changed, 88 insertions(+), 110 deletions(-)\n\nThe direction this step wants to take us looks good, but at this\npoint we only support \"size\" and the client side starts parsing\n\"type\" only in [6/8], while the server side starts advertising\n\"type\" only in [7/8].  If the software at this step talks to a newer\nserver that does support \"type\", it will hit BUG() if the user\nrequests %(objecttype), no?  IOW, introduction of \"ask_type\" smells\na bit premature.\n\n"},{"id":"549514","messageId":"DKFM9ZZWDV6O.3GFN589ORNUZ0@gmail.com","threadId":"66063","inReplyTo":"xmqqbjbjyv9s.fsf@gitster.g","subject":"Re: [PATCH GSoC v3 1/8] t5701: use test_file_size() to get the size of a file","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T21:12:46Z","receivedAt":"2026-08-03T21:12:49Z","isPatch":true,"body":"On Mon Aug 3, 2026 at 7:21 PM CEST, Junio C Hamano wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> The 'basics of object-info' test runs 'wc -c | xargs' twice to get the\n>> size of two.t. The pipe to xargs is only there to strip the blanks\n>> that some platforms pad the output of wc with.\n>>\n>> Use the test_file_size() helper, which outputs the size directly, and\n>> store the result in a variable. Because 'git rev-parse two:two.t' is\n>> also run twice, store its output in a variable as well.\n>\n> It also has the benefit of retaining the exit status from commands\n> run inside a $( ... ) construct placed within a HERE-document.\n> Earlier, if your \"git rev-parse\" failed, you would not have noticed\n> it directly (though you would probably have seen the \"expect\" file\n> containing unexpected content).  Now your assignment fails when you\n> compute two_oid, if your \"git rev-parse\" segfaults.\n\nI will add that next reroll.\n\nThanks,\nPablo\n"},{"id":"549515","messageId":"DKFMNL9K3H7K.1G0N5EDW71VHQ@gmail.com","threadId":"66063","inReplyTo":"xmqq7bm7yso6.fsf@gitster.g","subject":"Re: [PATCH GSoC v3 2/8] fetch-object-info: detect truncated server responses","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T21:30:31Z","receivedAt":"2026-08-03T21:30:34Z","isPatch":true,"body":"On Mon Aug 3, 2026 at 8:18 PM CEST, Junio C Hamano wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> The loop reading the object-info response stops as soon as the reader\n>> returns something other than PACKET_READ_NORMAL. A server that somehow\n>> answers with fewer objects leaves the end of the result arrays empty.\n>>\n>> The caller trusts that every requested object will be filled in.\n>>\n>> die() if the loop doesn't reach the number of oids expected.\n>\n> This tightening is obviously a good thing to do.\n>\n> The above description makes me wonder what happens if the other side\n> sends responses for more objects than we requested.  We allocate for\n> N objects and loop for up to N iterations, so we will not read more\n> than N.  But do we detect that we are out of sync when we read the\n> response to our next request, or before we shut down the connection\n> if we do not have any further requests?\n\nAs it is now we would only notice in the stateless case. The loop will\nonly go for N lines and then leave the rest unread, then\ncheck_stateless_delimiter() reads the next packet and dies because it is\na normal packet. If it isn't stateless it will early return and we\nwon't notice.\n\nThere is nothing to get out of sync though. The connection is started\nand finished for each remote-object-info command line. So a later\nremote-object-info starts fresh.\n\nBut even if it is harmless (I think) it's not ideal and I didn't think\nabout this case. The fix should be easy, check the next packet for a\nflush after iterating, otherwise die().\n\n[snip]\n\nThanks,\nPablo\n"},{"id":"549516","messageId":"DKFMZO5TH1MW.1JD6RQLUJDK3M@gmail.com","threadId":"66063","inReplyTo":"xmqqwlu7xdmb.fsf@gitster.g","subject":"Re: [PATCH GSoC v3 4/8] fetch-object-info: use dedicated struct for the results","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-03T21:46:17Z","receivedAt":"2026-08-03T21:46:21Z","isPatch":true,"body":"On Mon Aug 3, 2026 at 8:28 PM CEST, Junio C Hamano wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> fetch_object_info() collects information about N objects, but it stores\n>> the results in an array of object_info. That struct holds the extended\n>> parameters of read_object_info() (The optional outputs the caller wants\n>> filled). Its pointers tell that function where to write the answers for\n>> a single object. object_info is not meant to be the final storage, and\n>> since fetch_object_info() does not call read_object_info(), there is no\n>> reason to use it. Using it means allocating one scalar per object per\n>> attribute just to have those pointers somewhere to point at.\n>>\n>> Add struct fetch_object_info_results. The caller sets the wants_* flags\n>> to say what it is interested in, and fetch_object_info() allocates one\n>> array per attribute. A set wants_* flag means \"asked for\", while a\n>> non-NULL array means \"available\". The caller releases the arrays with\n>> free_fetch_object_info_results().\n>>\n>> The object_info_options string list is no longer needed. Filtering\n>> against the server's advertisement now sets local ask_* flags, and\n>> send_object_info_request() turns those into the v2 protocol option\n>> strings. remote_atom_map[] existed only to map those strings back into\n>> atom names, so drop it and build remote_allowed_atoms from the result\n>> arrays.\n>>\n>> free_object_info_contents() loses its only caller and is dropped.\n>>\n>> Helped-by: Jeff King <peff@peff.net>\n>> Helped-by: Junio C Hamano <gitster@pobox.com>\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  | 59 +++++++++--------------------------\n>>  fetch-object-info.c | 90 ++++++++++++++++++++++++++++-------------------------\n>>  fetch-object-info.h | 28 ++++++++++++-----\n>>  object-file.c       | 10 ------\n>>  odb.h               |  3 --\n>>  transport.c         |  3 +-\n>>  transport.h         |  5 +--\n>>  7 files changed, 88 insertions(+), 110 deletions(-)\n>\n> The direction this step wants to take us looks good, but at this\n> point we only support \"size\" and the client side starts parsing\n> \"type\" only in [6/8], while the server side starts advertising\n> \"type\" only in [7/8].  If the software at this step talks to a newer\n> server that does support \"type\", it will hit BUG() if the user\n> requests %(objecttype), no?  IOW, introduction of \"ask_type\" smells\n> a bit premature.\n\nTrue, if a client asks type and size in this patch and the server\nsupports it, \"wanted\" will be 2 and because in the loop over wanted we\nonly expect size, we will end up BUG()'ing out for something that is\nnot a BUG(), but an old client vs a newer server.\n\nI will move ask_type int a later patch in this series where it fits\ncorrectly.\n\nThanks for noticing it,\nPablo\n"},{"id":"549573","messageId":"CAOLa=ZQbbziEK8HiPU7Lpsn0NmWfEnh=-L4wSBFt4qHJWAbJHw@mail.gmail.com","threadId":"66063","inReplyTo":"20260803-objecttype-support-v3-3-7176fecf7950@gmail.com","subject":"Re: [PATCH GSoC v3 3/8] fetch-object-info: pass arguments directly instead of a struct","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-04T15:23:14Z","receivedAt":"2026-08-04T15:23:15Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> struct object_info_args groups three pointers that already live in the\n> transport and are given to fetch_object_info().\n> Grouping them into a struct reduces the number of parameters, but it\n> suggests that fetch_object_info() uses all three of them.\n>\n\nBut it does use all of them, no? Not sure I follow the reasoning here.\n\n[snip]\n"},{"id":"549574","messageId":"DKG9PMUYWK0O.NJ4R6R7H9J0G@gmail.com","threadId":"66063","inReplyTo":"CAOLa=ZQbbziEK8HiPU7Lpsn0NmWfEnh=-L4wSBFt4qHJWAbJHw@mail.gmail.com","subject":"Re: [PATCH GSoC v3 3/8] fetch-object-info: pass arguments directly instead of a struct","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T15:34:36Z","receivedAt":"2026-08-04T15:34:40Z","isPatch":true,"body":"On Tue Aug 4, 2026 at 5:23 PM CEST, Karthik Nayak wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>> struct object_info_args groups three pointers that already live in the\n>> transport and are given to fetch_object_info().\n>> Grouping them into a struct reduces the number of parameters, but it\n>> suggests that fetch_object_info() uses all three of them.\n>>\n>\n> But it does use all of them, no? Not sure I follow the reasoning here.\n>\n> [snip]\n\nYes, I meant that it suggests the three of them are used together, but\nin the end everything is accessed as args->*.\n\nAnd because object_info_args is built just before calling\nfetch_object_info() it doesn't make much sense.\n\nciting Peff [1]:\n\n> - there are a lot of semi-opaque structs, like object_info_args. It\n>   would seem simpler to me to pass those elements around independently\n>   to the functions that need them. Likewise, we seem to stuff a lot of\n>   data into the transport struct rather than passing it to the\n>   relevant functions, even though many of those elements are really\n>   just used for one function call, and aren't a property of the\n>   transport at all.\n\n[1]: https://lore.kernel.org/git/20260802154309.GA17844@coredump.intra.peff.net/\n\nI'll reword it in the next reroll,\nPablo\n"},{"id":"549591","messageId":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"[PATCH GSoC v4 0/9] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T18:42:54Z","receivedAt":"2026-08-04T18:43:00Z","isPatch":true,"body":"Neither the client nor the server knows how to handle type. This series\ncontinues the work for git cat-file --batch-command extending\nremote-object-info to support \"%(objecttype)\" end to end. It is based on\nits predecessor series \"cat-file: add remote-object-info to\nbatch-command\" [1] merged into master.\n\nType is the last property that is identical on the server and on the\nclient once fetched.\n\nWhether to support more metadata such as:\n- objectsize:disk\n- deltabase\n- objectmode (needs context)\n\nshould be discussed, unlike size and type, the rest depend on how things\nare packed on the server and on what the client already has, so they\ncannot reliably match local once fetched. IMO they are not worth\nsupporting, I can't find a use case for them.\n\nTo add a new placeholder, follow the steps in this series and add its\nwants_* flag to struct fetch_object_info_results.\n\nPatches 1-5 are preparatory. They don't change what the command does:\n- [1/9] is a test cleanup.\n- [2/9] fixes a possible bug in case of a malformed response.\n- [3/9] and [4/9] refactor how the object data is stored and handled. The\n  why about this refactor comes from [2].\n- [5/9] drops the last error return left in fetch_object_info().\n\nPatches 6-9 are the actual objecttype support:\n- [6/9] teaches the server to answer type.\n- [7/9] teaches the client to parse it.\n- [8/9] advertises the capability so the client can start asking it.\n- [9/9] unifies the default format.\n\nNote that *_type appears from [7/9]. Before this series, \"type\" is\nalready listed in the client's allow-list, but the prep patches drop\nthat list, so the client knows nothing about type until [7/9] adds it\nback.\n\nGithub CI: https://github.com/pabloosabaterr/git/actions/runs/30938909345\n\n[1]: https://lore.kernel.org/git/20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com/\n[2]: https://lore.kernel.org/git/xmqqzez67yg1.fsf@gitster.g/\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nChanges in v4:\n- Rebased onto master now that ps/cat-file-remote-object-info was merged.\n- Dropped the Based-on trailer.\n- Reworded [1/9] so it shows that we also benefit from preserving the\n  exit status.\n- Changed [2/9] to also check if a response returns more packets.\n- New [5/9]: fetch_object_info() dies on its last error return and\n  becomes void.\n- Moved the introduction of ask_type, wants_type to [7/9] instead of\n  [4/9].\n- Link to v3: https://lore.kernel.org/git/20260803-objecttype-support-v3-0-7176fecf7950@gmail.com/\n\nChanges in v3:\n- 1/8 (was 2/6): reword the commit message to explain that \"wc -c | xargs\"\n  only strips the padding some platforms add.\n- New 2/8: die if the server sends fewer object-info lines than\n  requested OIDs. Preexisting bug.\n- New 3/8: drop struct object_info_args and pass its members directly to\n  fetch_object_info().\n- New 4/8: replace struct object_info with struct\n  fetch_object_info_results to hold the results. This also drops the\n  object_info_options string list, remote_atom_map[] and\n  free_object_info_contents().\n- Dropped 1/6, which replaced the hardcoded \"size\" in\n  send_object_info_request() with a loop over object_info_options, as\n  that list no longer reaches it.\n- 6/8: follows the new result struct, so it only adds the type array.\n- 7/8: quote $hello_type in the test.\n- Link to v2: https://lore.kernel.org/git/20260731-objecttype-support-v2-0-af577461ed57@gmail.com/\n\nChanges in v2:\n- Changed t5701 test to use test_file_size.\n- Changed the for loop to use for_each_string_list_item().\n- Moved \"fetch-object-info: request all supported options dynamically\"\n  patch to be the first one as it is prep patch.\n- Typos and nits.\n- Restored the test mixing info and remote-object-info, adapted to the\n  unified format.\n- Link to v1: https://lore.kernel.org/git/20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com/\n\n---\nPablo Sabater (9):\n      t5701: use test_file_size() to get the size of a file\n      fetch-object-info: detect malformed server responses\n      fetch-object-info: pass arguments directly instead of a struct\n      fetch-object-info: use dedicated struct for the results\n      fetch-object-info: die() on the remaining error path\n      protocol-caps: add type support to object-info\n      fetch-object-info: parse type from server response\n      serve: advertise type capability\n      cat-file: unify default format\n\n Documentation/git-cat-file.adoc        |  17 ++--\n Documentation/gitprotocol-v2.adoc      |  18 +++-\n builtin/cat-file.c                     |  69 +++++----------\n fetch-object-info.c                    | 149 +++++++++++++++++++++------------\n fetch-object-info.h                    |  37 +++++---\n object-file.c                          |  10 ---\n odb.h                                  |   3 -\n protocol-caps.c                        |  21 ++++-\n serve.c                                |   4 +-\n t/t1017-cat-file-remote-object-info.sh |  78 ++++++++++-------\n t/t5701-git-serve.sh                   |  41 ++++++++-\n transport.c                            |  14 ++--\n transport.h                            |   5 +-\n 13 files changed, 276 insertions(+), 190 deletions(-)\n\nRange-diff versus v3:\n\n 1:  41172a72ce !  1:  301d7ccca9 t5701: use test_file_size() to get the size of a file\n    @@ Commit message\n     \n         Use the test_file_size() helper, which outputs the size directly, and\n         store the result in a variable. Because 'git rev-parse two:two.t' is\n    -    also run twice, store its output in a variable as well.\n    +    also run multiple times, store its output in a variable as well.\n    +\n    +    Storing them in variables outside the HERE-document has the added\n    +    benefit of preserving their exit statuses.\n     \n         Mentored-by: Karthik Nayak <karthik.188@gmail.com>\n         Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\n 2:  a7a547c9b9 <  -:  ---------- fetch-object-info: detect truncated server responses\n -:  ---------- >  2:  445d65cc7e fetch-object-info: detect malformed server responses\n 3:  6a9b839127 !  3:  107315bb32 fetch-object-info: pass arguments directly instead of a struct\n    @@ Commit message\n         struct object_info_args groups three pointers that already live in the\n         transport and are given to fetch_object_info().\n         Grouping them into a struct reduces the number of parameters, but it\n    -    suggests that fetch_object_info() uses all three of them.\n    +    suggests that the three belong together, when they are unrelated and end\n    +    up being accessed as args->* independently.\n     \n         Drop the struct and pass those parameters directly to\n         fetch_object_info() and send_object_info_request(). This should have no\n    @@ fetch-object-info.c: static int parse_object_size(const char *s, size_t *res)\n     +\t\t      struct object_info *object_info_data,\n      \t\t      const int stateless_rpc, const int fd_out)\n      {\n    - \tsize_t i;\n    + \tint size_index = -1;\n     @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version, struct object_info_ar\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    @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n      \t\tBUG(\"unknown protocol version\");\n      \t}\n      \n    --\tfor (i = 0; i < args->object_info_options->nr; i++) {\n    -+\tfor (i = 0; i < object_info_options->nr; i++) {\n    +-\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n    ++\tfor (size_t i = 0; i < 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    @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n      \t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].sizep));\n      \t\t} else {\n     @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version, struct object_info_ar\n    + \t\t}\n    + \t}\n      \n    - \tfor (i = 0;\n    - \t     packet_reader_read(reader) == PACKET_READ_NORMAL &&\n    --\t     i < args->oids->nr;\n    -+\t     i < oids->nr;\n    - \t     i++) {\n    +-\tfor (size_t i = 0; i < args->oids->nr; i++) {\n    ++\tfor (size_t i = 0; i < oids->nr; i++) {\n      \t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n      \n    + \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n    + \t\t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n    +-\t\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n    ++\t\t\t    (uintmax_t)oids->nr, (uintmax_t)i);\n    + \n      \t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n      \n      \t\tif (strcmp(object_info_values.items[0].string,\n    @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n      \t\t\t    reader->line);\n      \n     @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version, struct object_info_ar\n    - \t\tstring_list_clear(&object_info_values, 0);\n    - \t}\n      \n    --\tif (i != args->oids->nr)\n    -+\tif (i != oids->nr)\n    - \t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n    --\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n    -+\t\t    (uintmax_t)oids->nr, (uintmax_t)i);\n    + \tif (packet_reader_read(reader) != PACKET_READ_FLUSH)\n    + \t\tdie(_(\"object-info: expected flush after %\"PRIuMAX\" objects\"),\n    +-\t\t    (uintmax_t)args->oids->nr);\n    ++\t\t    (uintmax_t)oids->nr);\n      \n      \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n      \n 4:  0633ddc7e8 !  4:  b942beb9d7 fetch-object-info: use dedicated struct for the results\n    @@ Commit message\n         atom names, so drop it and build remote_allowed_atoms from the result\n         arrays.\n     \n    +    Currently for wants_* and ask_* there is only the 'size' variant but a\n    +    subsequent commit will add '*_type'.\n    +\n         free_object_info_contents() loses its only caller and is dropped.\n     \n    +    Dropping the allow-list check makes the final else reachable from the\n    +    wire, so die() instead of BUG(): an unknown attribute is the server's\n    +    error, not ours.\n    +\n         Helped-by: Jeff King <peff@peff.net>\n         Helped-by: Junio C Hamano <gitster@pobox.com>\n         Mentored-by: Karthik Nayak <karthik.188@gmail.com>\n    @@ builtin/cat-file.c: static void parse_cmd_remote_object_info(struct batch_option\n      \n      \tif (data->info.sizep)\n     -\t\tstring_list_append(&object_info_options, \"size\");\n    -+\t\tresults.wants_size = 1;\n    - \tif (data->info.typep)\n    +-\tif (data->info.typep)\n     -\t\tstring_list_append(&object_info_options, \"type\");\n    -+\t\tresults.wants_type = 1;\n    ++\t\tresults.wants_size = 1;\n      \n     -\tif (get_remote_info(count, argv, &remote_object_info,\n     -\t\t\t    &object_info_oids, &object_info_options))\n    @@ fetch-object-info.c\n      \t\t\t\t     const struct string_list *server_options,\n      \t\t\t\t     struct oid_array *oids,\n     -\t\t\t\t     struct string_list *object_info_options)\n    -+\t\t\t\t     unsigned ask_size,\n    -+\t\t\t\t     unsigned ask_type)\n    ++\t\t\t\t     unsigned ask_size)\n      {\n      \tstruct strbuf req_buf = STRBUF_INIT;\n      \n    @@ fetch-object-info.c\n      \t\tpacket_buf_write(&req_buf, \"size\");\n     -\telse if (object_info_options->nr)\n     -\t\tBUG(\"only size should be in object_info_options\");\n    -+\n    -+\tif (ask_type)\n    -+\t\tpacket_buf_write(&req_buf, \"type\");\n      \n      \tif (oids)\n      \t\tfor (size_t i = 0; i < oids->nr; i++)\n    @@ fetch-object-info.c: static int parse_object_size(const char *s, size_t *res)\n     +\t\t      const int stateless_rpc,\n     +\t\t      const int fd_out)\n      {\n    --\tsize_t i;\n     +\tunsigned ask_size = 0;\n    -+\tunsigned ask_type = 0;\n      \tint size_index = -1;\n     +\tsize_t wanted;\n    -+\tsize_t i;\n     +\n     +\tresults->nr = oids->nr;\n     +\tCALLOC_ARRAY(results->unrecognized, results->nr);\n    @@ fetch-object-info.c: static int parse_object_size(const char *s, size_t *res)\n     +\t\tif (results->wants_size &&\n     +\t\t    server_supports_feature(\"object-info\", \"size\", 0))\n     +\t\t\task_size = 1;\n    -+\n    -+\t\tif (results->wants_type &&\n    -+\t\t    server_supports_feature(\"object-info\", \"type\", 0))\n    -+\t\t\task_type = 1;\n      \n      \t\t/*\n      \t\t * Even if no options are left, we still send the oid so we get\n    @@ fetch-object-info.c: static int parse_object_size(const char *s, size_t *res)\n      \t\t */\n     -\t\tsend_object_info_request(fd_out, server_options, oids,\n     -\t\t\t\t\t object_info_options);\n    -+\t\tsend_object_info_request(fd_out, server_options, oids, ask_size,\n    -+\t\t\t\t\t ask_type);\n    ++\t\tsend_object_info_request(fd_out, server_options, oids, ask_size);\n      \t\tbreak;\n      \tcase protocol_v1:\n      \tcase protocol_v0:\n    @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n      \tcase protocol_unknown_version:\n      \t\tBUG(\"unknown protocol version\");\n      \t}\n    -+\twanted = ask_size + ask_type;\n    ++\twanted = ask_size;\n      \n    --\tfor (i = 0; i < object_info_options->nr; i++) {\n    -+\tfor (i = 0; i < wanted; i++) {\n    +-\tfor (size_t i = 0; i < object_info_options->nr; i++) {\n    ++\tfor (size_t i = 0; i < wanted; 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    @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n     -\t\t\t\t\txcalloc(1, sizeof(*object_info_data[j].sizep));\n     +\t\t\tCALLOC_ARRAY(results->sizes, results->nr);\n      \t\t} else {\n    - \t\t\tBUG(\"only size is supported\");\n    +-\t\t\tBUG(\"only size is supported\");\n    ++\t\t\tdie(_(\"object-info: unknown attribute '%s'\"),\n    ++\t\t\t    reader->line);\n      \t\t}\n    + \t}\n    + \n     @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n      \t\t */\n      \t\tif (object_info_values.nr >= 2 &&\n    @@ fetch-object-info.h\n     +\tuint8_t *unrecognized;\n     +\tsize_t nr;\n     +\tunsigned wants_size:1;\n    -+\tunsigned wants_type:1;\n     +};\n     +\n     +#define FETCH_OBJECT_INFO_RESULTS_INIT { 0 }\n -:  ---------- >  5:  2e9cce44b6 fetch-object-info: die() on the remaining error path\n 5:  7678f2ce77 =  6:  30d9898e3b protocol-caps: add type support to object-info\n 6:  bfff160c38 !  7:  8ebdf712b8 fetch-object-info: parse type from server response\n    @@ Commit message\n     \n         The server can handle type requests but does not advertise the\n         capability yet. Prepare the client to know how to parse the server\n    -    response once the server advertises the capability.\n    +    response once the server advertises the type capability.\n     \n         Mentored-by: Karthik Nayak <karthik.188@gmail.com>\n         Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>\n    @@ Commit message\n     \n      ## builtin/cat-file.c ##\n     @@ builtin/cat-file.c: static void parse_cmd_remote_object_info(struct batch_options *opt,\n    + \n    + \tif (data->info.sizep)\n    + \t\tresults.wants_size = 1;\n    ++\tif (data->info.typep)\n    ++\t\tresults.wants_type = 1;\n    + \n    + \tif (get_remote_info(count, argv, &results, &object_info_oids))\n    + \t\tdie(_(\"failed to get object info from the remote: %s\"), argv[0]);\n    +@@ builtin/cat-file.c: static void parse_cmd_remote_object_info(struct batch_options *opt,\n      \tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n      \tif (results.sizes)\n      \t\tstring_list_append(&data->remote_allowed_atoms, \"objectsize\");\n    @@ fetch-object-info.c\n      #include \"pkt-line.h\"\n      #include \"connect.h\"\n      #include \"oid-array.h\"\n    -@@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n    +@@\n    + static void send_object_info_request(const int fd_out,\n    + \t\t\t\t     const struct string_list *server_options,\n    + \t\t\t\t     struct oid_array *oids,\n    +-\t\t\t\t     unsigned ask_size)\n    ++\t\t\t\t     unsigned ask_size,\n    ++\t\t\t\t     unsigned ask_type)\n    + {\n    + \tstruct strbuf req_buf = STRBUF_INIT;\n    + \n    +@@ fetch-object-info.c: static void send_object_info_request(const int fd_out,\n    + \tif (ask_size)\n    + \t\tpacket_buf_write(&req_buf, \"size\");\n    + \n    ++\tif (ask_type)\n    ++\t\tpacket_buf_write(&req_buf, \"type\");\n    ++\n    + \tif (oids)\n    + \t\tfor (size_t i = 0; i < oids->nr; i++)\n    + \t\t\tpacket_buf_write(&req_buf, \"oid %s\",\n    +@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n    + \t\t       int fd_out)\n    + {\n      \tunsigned ask_size = 0;\n    - \tunsigned ask_type = 0;\n    ++\tunsigned ask_type = 0;\n      \tint size_index = -1;\n     +\tint type_index = -1;\n      \tsize_t wanted;\n    - \tsize_t i;\n      \n    -@@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n    + \tresults->nr = oids->nr;\n    +@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n    + \t\t    server_supports_feature(\"object-info\", \"size\", 0))\n    + \t\t\task_size = 1;\n    + \n    ++\t\tif (results->wants_type &&\n    ++\t\t    server_supports_feature(\"object-info\", \"type\", 0))\n    ++\t\t\task_type = 1;\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, server_options, oids, ask_size);\n    ++\t\tsend_object_info_request(fd_out, server_options, oids, ask_size,\n    ++\t\t\t\t\t ask_type);\n    + \t\tbreak;\n    + \tcase protocol_v1:\n    + \tcase protocol_v0:\n    +@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n    + \tcase protocol_unknown_version:\n    + \t\tBUG(\"unknown protocol version\");\n    + \t}\n    +-\twanted = ask_size;\n    ++\twanted = ask_size + ask_type;\n    + \n    + \tfor (size_t i = 0; i < wanted; i++) {\n    + \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n    +@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n      \t\t\t\tdie(_(\"object-info: duplicate 'size' attribute\"));\n      \t\t\tsize_index = (int)i;\n      \t\t\tCALLOC_ARRAY(results->sizes, results->nr);\n    @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n     +\t\t\ttype_index = (int)i;\n     +\t\t\tCALLOC_ARRAY(results->types, results->nr);\n      \t\t} else {\n    --\t\t\tBUG(\"only size is supported\");\n    -+\t\t\tBUG(\"unexpected object-info option: %s\", reader->line);\n    - \t\t}\n    - \t}\n    - \n    -@@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n    + \t\t\tdie(_(\"object-info: unknown attribute '%s'\"),\n    + \t\t\t    reader->line);\n    +@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n      \t\t\t    object_info_values.items[0].string,\n      \t\t\t    object_info_values.items[size_index + 1].string);\n      \n    @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n      \t\tstring_list_clear(&object_info_values, 0);\n      \t}\n      \n    -@@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n    +@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n      void free_fetch_object_info_results(struct fetch_object_info_results *results)\n      {\n      \tfree(results->sizes);\n    @@ fetch-object-info.h\n      \tuint8_t *unrecognized;\n      \tsize_t nr;\n      \tunsigned wants_size:1;\n    ++\tunsigned wants_type:1;\n    + };\n    + \n    + #define FETCH_OBJECT_INFO_RESULTS_INIT { 0 }\n 7:  8c58680108 =  8:  acfa7cb4b1 serve: advertise type capability\n 8:  fb5032cea5 =  9:  9dc40ba2cb cat-file: unify default format\n\n---\nbase-commit: 5b2471720c93ee30e5764a19f3d3b3ae9ec9712a\nchange-id: 20260724-objecttype-support-ea1ef6941d07\n\n"},{"id":"549592","messageId":"20260804-objecttype-support-v4-1-31511b0231be@gmail.com","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","subject":"[PATCH GSoC v4 1/9] t5701: use test_file_size() to get the size of a file","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T18:42:55Z","receivedAt":"2026-08-04T18:43:01Z","isPatch":true,"body":"The 'basics of object-info' test runs 'wc -c | xargs' twice to get the\nsize of two.t. The pipe to xargs is only there to strip the blanks\nthat some platforms pad the output of wc with.\n\nUse the test_file_size() helper, which outputs the size directly, and\nstore the result in a variable. Because 'git rev-parse two:two.t' is\nalso run multiple times, store its output in a variable as well.\n\nStoring them in variables outside the HERE-document has the added\nbenefit of preserving their exit statuses.\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 t/t5701-git-serve.sh | 11 +++++++----\n 1 file changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 9a575aa098..51d5dd1ae6 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -344,20 +344,23 @@ test_expect_success 'unexpected lines are not allowed in fetch request' '\n test_expect_success 'basics of object-info' '\n \ttest_config transfer.advertiseObjectInfo true &&\n \n+\ttwo_oid=$(git rev-parse two:two.t) &&\n+\ttwo_size=$(test_file_size two.t) &&\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 $(git rev-parse two:two.t)\n-\toid $(git rev-parse two:two.t)\n+\toid $two_oid\n+\toid $two_oid\n \t0000\n \tEOF\n \n \tcat >expect <<-EOF &&\n \tsize\n-\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n-\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n+\t$two_oid $two_size\n+\t$two_oid $two_size\n \t0000\n \tEOF\n \n\n-- \n2.54.0\n\n"},{"id":"549593","messageId":"20260804-objecttype-support-v4-2-31511b0231be@gmail.com","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","subject":"[PATCH GSoC v4 2/9] fetch-object-info: detect malformed server responses","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T18:42:56Z","receivedAt":"2026-08-04T18:43:02Z","isPatch":true,"body":"The loop reading the object-info response stops as soon as the reader\nreturns something other than PACKET_READ_NORMAL, or once it has read as\nmany lines as we requested. Neither end is checked.\n\nA server that answers with fewer objects leaves the end of the result\narrays empty, and the caller trusts that every requested object was\nfilled in. A server that answers with more leaves the extra packets\nunread. On stateless transports check_stateless_delimiter() notices, but\non the others it passes unnoticed.\n\nCheck both limits by extracting the packet_reader_read() from the loop\ncondition, so the loop no longer consumes the last packet (flush). If\nwhile looping the read is different from a PACKET_READ_NORMAL, die()\nmeaning there are fewer objects than expected. After iterating, we only\nexpect a flush, so if the last packet is not a flush, die().\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\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 fetch-object-info.c | 14 ++++++++++----\n 1 file changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex ba7e179c44..c2d4bf9403 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -106,12 +106,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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+\tfor (size_t i = 0; i < args->oids->nr; i++) {\n \t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n \n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n+\t\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n+\n \t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n \n \t\tif (strcmp(object_info_values.items[0].string,\n@@ -150,6 +151,11 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \n \t\tstring_list_clear(&object_info_values, 0);\n \t}\n+\n+\tif (packet_reader_read(reader) != PACKET_READ_FLUSH)\n+\t\tdie(_(\"object-info: expected flush after %\"PRIuMAX\" objects\"),\n+\t\t    (uintmax_t)args->oids->nr);\n+\n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n \n \treturn 0;\n\n-- \n2.54.0\n\n"},{"id":"549594","messageId":"20260804-objecttype-support-v4-3-31511b0231be@gmail.com","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","subject":"[PATCH GSoC v4 3/9] fetch-object-info: pass arguments directly instead of a struct","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T18:42:57Z","receivedAt":"2026-08-04T18:43:04Z","isPatch":true,"body":"struct object_info_args groups three pointers that already live in the\ntransport and are given to fetch_object_info().\nGrouping them into a struct reduces the number of parameters, but it\nsuggests that the three belong together, when they are unrelated and end\nup being accessed as args->* independently.\n\nDrop the struct and pass those parameters directly to\nfetch_object_info() and send_object_info_request(). This should have no\nchange in behavior.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Junio C Hamano <gitster@pobox.com>\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 fetch-object-info.c | 53 +++++++++++++++++++++++++++++++----------------------\n fetch-object-info.h | 17 ++++++++---------\n transport.c         | 11 +++++------\n 3 files changed, 44 insertions(+), 37 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex c2d4bf9403..531610e6f4 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -9,20 +9,24 @@\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+static void send_object_info_request(const int fd_out,\n+\t\t\t\t     const struct string_list *server_options,\n+\t\t\t\t     struct oid_array *oids,\n+\t\t\t\t     struct string_list *object_info_options)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", server_options);\n \n-\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\tif (unsorted_string_list_has_string(object_info_options, \"size\"))\n \t\tpacket_buf_write(&req_buf, \"size\");\n-\telse if (args->object_info_options->nr)\n+\telse if (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+\tif (oids)\n+\t\tfor (size_t i = 0; i < oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\",\n+\t\t\t\t\t oid_to_hex(&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@@ -45,8 +49,12 @@ static int parse_object_size(const char *s, size_t *res)\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+int fetch_object_info(const enum protocol_version version,\n+\t\t      const struct string_list *server_options,\n+\t\t      struct oid_array *oids,\n+\t\t      struct string_list *object_info_options,\n+\t\t      struct packet_reader *reader,\n+\t\t      struct object_info *object_info_data,\n \t\t      const int stateless_rpc, const int fd_out)\n {\n \tint size_index = -1;\n@@ -64,16 +72,17 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tfor (int i = (int)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\t\t\t\t     object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(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\tsend_object_info_request(fd_out, server_options, oids,\n+\t\t\t\t\t object_info_options);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -82,14 +91,14 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\tBUG(\"unknown protocol version\");\n \t}\n \n-\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\tfor (size_t i = 0; i < 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 (!unsorted_string_list_has_string(args->object_info_options, reader->line))\n+\t\tif (!unsorted_string_list_has_string(object_info_options, reader->line))\n \t\t\treturn -1;\n \n \t\tif (!strcmp(reader->line, \"size\")) {\n@@ -98,7 +107,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tfor (size_t j = 0; j < 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@@ -106,19 +115,19 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\t}\n \t}\n \n-\tfor (size_t i = 0; i < args->oids->nr; i++) {\n+\tfor (size_t i = 0; i < oids->nr; i++) {\n \t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n \n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n-\t\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n+\t\t\t    (uintmax_t)oids->nr, (uintmax_t)i);\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\t   oid_to_hex(&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    oid_to_hex(&oids->oid[i]),\n \t\t\t    object_info_values.items[0].string);\n \n \t\t/*\n@@ -138,7 +147,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tif (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@@ -154,7 +163,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \n \tif (packet_reader_read(reader) != PACKET_READ_FLUSH)\n \t\tdie(_(\"object-info: expected flush after %\"PRIuMAX\" objects\"),\n-\t\t    (uintmax_t)args->oids->nr);\n+\t\t    (uintmax_t)oids->nr);\n \n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n \ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 269cebb3f7..316bf917ce 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -4,22 +4,21 @@\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+struct oid_array;\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+ * Modifies 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,\n+int fetch_object_info(enum protocol_version version,\n+\t\t      const struct string_list *server_options,\n+\t\t      struct oid_array *oids,\n+\t\t      struct string_list *object_info_options,\n+\t\t      struct packet_reader *reader,\n+\t\t      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/transport.c b/transport.c\nindex 42b0a2d0bd..2bb5eedc32 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -438,11 +438,6 @@ static int fetch_object_info_via_pack(struct transport *transport)\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 \n \tconnect_setup(transport, 0);\n \tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n@@ -453,7 +448,11 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tdata->version = discover_version(&reader);\n \ttransport->hash_algo = reader.hash_algo;\n \n-\tret = fetch_object_info(data->version, &args, &reader,\n+\tret = fetch_object_info(data->version,\n+\t\t\t\ttransport->server_options,\n+\t\t\t\ttransport->smart_options->object_info_oids,\n+\t\t\t\ttransport->smart_options->object_info_options,\n+\t\t\t\t&reader,\n \t\t\t\tdata->options.object_info_data,\n \t\t\t\ttransport->stateless_rpc, data->fd[1]);\n \n\n-- \n2.54.0\n\n"},{"id":"549595","messageId":"20260804-objecttype-support-v4-5-31511b0231be@gmail.com","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","subject":"[PATCH GSoC v4 5/9] fetch-object-info: die() on the remaining error path","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T18:42:59Z","receivedAt":"2026-08-04T18:43:06Z","isPatch":true,"body":"Every failure in fetch_object_info() dies except one: a short read while\nparsing the attribute lines returns -1. That -1 is then passed through\nfetch_object_info_via_pack() and get_remote_info() up to cat-file, only\nto die() with a generic message.\n\nDie in fetch_object_info() instead, consistently with the rest of its\nerror paths, and make fetch_object_info() void.\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 fetch-object-info.c | 19 +++++++++----------\n fetch-object-info.h | 14 +++++++-------\n transport.c         | 12 ++++++------\n 3 files changed, 22 insertions(+), 23 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 46a1289ff3..6069d6a58b 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -47,13 +47,13 @@ static int parse_object_size(const char *s, size_t *res)\n \treturn 0;\n }\n \n-int fetch_object_info(const enum protocol_version version,\n-\t\t      const struct string_list *server_options,\n-\t\t      struct oid_array *oids,\n-\t\t      struct packet_reader *reader,\n-\t\t      struct fetch_object_info_results *results,\n-\t\t      const int stateless_rpc,\n-\t\t      const int fd_out)\n+void fetch_object_info(enum protocol_version version,\n+\t\t       const struct string_list *server_options,\n+\t\t       struct oid_array *oids,\n+\t\t       struct packet_reader *reader,\n+\t\t       struct fetch_object_info_results *results,\n+\t\t       int stateless_rpc,\n+\t\t       int fd_out)\n {\n \tunsigned ask_size = 0;\n \tint size_index = -1;\n@@ -89,7 +89,8 @@ int fetch_object_info(const enum protocol_version version,\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\tdie(_(\"object-info: expected %\" PRIuMAX \" attributes, got %\" PRIuMAX),\n+\t\t\t    (uintmax_t)wanted, (uintmax_t)i);\n \t\t}\n \n \t\tif (!strcmp(reader->line, \"size\")) {\n@@ -156,8 +157,6 @@ int fetch_object_info(const enum protocol_version version,\n \t\t    (uintmax_t)oids->nr);\n \n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n-\n-\treturn 0;\n }\n \n void free_fetch_object_info_results(struct fetch_object_info_results *results)\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 9f72e91155..97ee5314c9 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -24,13 +24,13 @@ struct oid_array;\n  * attribute is not available.\n  * Release them with free_fetch_object_info_results().\n  */\n-int fetch_object_info(enum protocol_version version,\n-\t\t      const struct string_list *server_options,\n-\t\t      struct oid_array *oids,\n-\t\t      struct packet_reader *reader,\n-\t\t      struct fetch_object_info_results *results,\n-\t\t      int stateless_rpc,\n-\t\t      int fd_out);\n+void fetch_object_info(enum protocol_version version,\n+\t\t       const struct string_list *server_options,\n+\t\t       struct oid_array *oids,\n+\t\t       struct packet_reader *reader,\n+\t\t       struct fetch_object_info_results *results,\n+\t\t       int stateless_rpc,\n+\t\t       int fd_out);\n \n void free_fetch_object_info_results(struct fetch_object_info_results *results);\n \ndiff --git a/transport.c b/transport.c\nindex b0e29124d1..35acdf71a2 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -448,12 +448,12 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tdata->version = discover_version(&reader);\n \ttransport->hash_algo = reader.hash_algo;\n \n-\tret = fetch_object_info(data->version,\n-\t\t\t\ttransport->server_options,\n-\t\t\t\ttransport->smart_options->object_info_oids,\n-\t\t\t\t&reader,\n-\t\t\t\tdata->options.object_info_results,\n-\t\t\t\ttransport->stateless_rpc, data->fd[1]);\n+\tfetch_object_info(data->version,\n+\t\t\t  transport->server_options,\n+\t\t\t  transport->smart_options->object_info_oids,\n+\t\t\t  &reader,\n+\t\t\t  data->options.object_info_results,\n+\t\t\t  transport->stateless_rpc, data->fd[1]);\n \n \tclose(data->fd[0]);\n \tif (data->fd[1] >= 0)\n\n-- \n2.54.0\n\n"},{"id":"549596","messageId":"20260804-objecttype-support-v4-4-31511b0231be@gmail.com","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","subject":"[PATCH GSoC v4 4/9] fetch-object-info: use dedicated struct for the results","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T18:42:58Z","receivedAt":"2026-08-04T18:43:06Z","isPatch":true,"body":"fetch_object_info() collects information about N objects, but it stores\nthe results in an array of object_info. That struct holds the extended\nparameters of read_object_info() (The optional outputs the caller wants\nfilled). Its pointers tell that function where to write the answers for\na single object. object_info is not meant to be the final storage, and\nsince fetch_object_info() does not call read_object_info(), there is no\nreason to use it. Using it means allocating one scalar per object per\nattribute just to have those pointers somewhere to point at.\n\nAdd struct fetch_object_info_results. The caller sets the wants_* flags\nto say what it is interested in, and fetch_object_info() allocates one\narray per attribute. A set wants_* flag means \"asked for\", while a\nnon-NULL array means \"available\". The caller releases the arrays with\nfree_fetch_object_info_results().\n\nThe object_info_options string list is no longer needed. Filtering\nagainst the server's advertisement now sets local ask_* flags, and\nsend_object_info_request() turns those into the v2 protocol option\nstrings. remote_atom_map[] existed only to map those strings back into\natom names, so drop it and build remote_allowed_atoms from the result\narrays.\n\nCurrently for wants_* and ask_* there is only the 'size' variant but a\nsubsequent commit will add '*_type'.\n\nfree_object_info_contents() loses its only caller and is dropped.\n\nDropping the allow-list check makes the final else reachable from the\nwire, so die() instead of BUG(): an unknown attribute is the server's\nerror, not ours.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Junio C Hamano <gitster@pobox.com>\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  | 59 +++++++++-----------------------------\n fetch-object-info.c | 81 ++++++++++++++++++++++++++---------------------------\n fetch-object-info.h | 27 +++++++++++++-----\n object-file.c       | 10 -------\n odb.h               |  3 --\n transport.c         |  3 +-\n transport.h         |  5 ++--\n 7 files changed, 77 insertions(+), 111 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 884b6d5ad3..e1650b2921 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -31,6 +31,7 @@\n #include \"alias.h\"\n #include \"remote.h\"\n #include \"transport.h\"\n+#include \"fetch-object-info.h\"\n \n /*\n  * Maximum length for a remote URL. While no universal standard exists,\n@@ -681,9 +682,8 @@ static void batch_one_object(const char *obj_name,\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-\t\t\t   struct string_list *object_info_options)\n+\t\t\t   struct fetch_object_info_results *results,\n+\t\t\t   struct oid_array *object_info_oids)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n@@ -724,11 +724,9 @@ static int get_remote_info(int argc,\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-\tgtransport->smart_options->object_info_options = object_info_options;\n-\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\tgtransport->smart_options->object_info_results = results;\n \tretval = transport_fetch_object_info(gtransport);\n cleanup:\n \ttransport_disconnect(gtransport);\n@@ -816,21 +814,6 @@ 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@@ -838,9 +821,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tint count;\n \tconst char **argv;\n \tchar *line_to_split;\n-\tstruct object_info *remote_object_info = NULL;\n+\tstruct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;\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@@ -861,26 +843,21 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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+\t\tresults.wants_size = 1;\n \n-\tif (get_remote_info(count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids, &object_info_options))\n+\tif (get_remote_info(count, argv, &results, &object_info_oids))\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+\tif (results.sizes)\n+\t\tstring_list_append(&data->remote_allowed_atoms, \"objectsize\");\n \n \tdata->skip_object_info = 1;\n-\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\tfor (size_t i = 0; i < results.nr; i++) {\n \t\tdata->oid = object_info_oids.oid[i];\n \n-\t\tif (remote_object_info[i].unrecognized) {\n+\t\tif (results.unrecognized[i]) {\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@@ -890,13 +867,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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}\n-\n-\t\tif (remote_object_info[i].typep) {\n-\t\t\tdata->type = *remote_object_info[i].typep;\n-\t\t}\n+\t\tif (results.sizes)\n+\t\t\tdata->size = results.sizes[i];\n \n \t\topt->batch_mode = BATCH_MODE_INFO;\n \t\tdata->is_remote = 1;\n@@ -906,12 +878,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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_fetch_object_info_results(&results);\n \tfree(line_to_split);\n \tfree(argv);\n-\tfree(remote_object_info);\n \toid_array_clear(&object_info_oids);\n }\n \ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 531610e6f4..46a1289ff3 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -12,16 +12,14 @@\n static void send_object_info_request(const int fd_out,\n \t\t\t\t     const struct string_list *server_options,\n \t\t\t\t     struct oid_array *oids,\n-\t\t\t\t     struct string_list *object_info_options)\n+\t\t\t\t     unsigned ask_size)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n \twrite_command_and_capabilities(&req_buf, \"object-info\", server_options);\n \n-\tif (unsorted_string_list_has_string(object_info_options, \"size\"))\n+\tif (ask_size)\n \t\tpacket_buf_write(&req_buf, \"size\");\n-\telse if (object_info_options->nr)\n-\t\tBUG(\"only size should be in object_info_options\");\n \n \tif (oids)\n \t\tfor (size_t i = 0; i < oids->nr; i++)\n@@ -52,37 +50,32 @@ static int parse_object_size(const char *s, size_t *res)\n int fetch_object_info(const enum protocol_version version,\n \t\t      const struct string_list *server_options,\n \t\t      struct oid_array *oids,\n-\t\t      struct string_list *object_info_options,\n \t\t      struct packet_reader *reader,\n-\t\t      struct object_info *object_info_data,\n-\t\t      const int stateless_rpc, const int fd_out)\n+\t\t      struct fetch_object_info_results *results,\n+\t\t      const int stateless_rpc,\n+\t\t      const int fd_out)\n {\n+\tunsigned ask_size = 0;\n \tint size_index = -1;\n+\tsize_t wanted;\n+\n+\tresults->nr = oids->nr;\n+\tCALLOC_ARRAY(results->unrecognized, results->nr);\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\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)object_info_options->nr - 1; i >= 0; i--)\n-\t\t\tif (!server_supports_feature(\"object-info\",\n-\t\t\t\t\t\t     object_info_options->items[i].string, 0))\n-\t\t\t\tunsorted_string_list_delete_item(object_info_options, i, 0);\n+\n+\t\tif (results->wants_size &&\n+\t\t    server_supports_feature(\"object-info\", \"size\", 0))\n+\t\t\task_size = 1;\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, server_options, oids,\n-\t\t\t\t\t object_info_options);\n+\t\tsend_object_info_request(fd_out, server_options, oids, ask_size);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -90,28 +83,25 @@ int fetch_object_info(const enum protocol_version version,\n \tcase protocol_unknown_version:\n \t\tBUG(\"unknown protocol version\");\n \t}\n+\twanted = ask_size;\n \n-\tfor (size_t i = 0; i < object_info_options->nr; i++) {\n+\tfor (size_t i = 0; i < wanted; 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 (!unsorted_string_list_has_string(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\tif (!ask_size)\n+\t\t\t\tdie(_(\"object-info: unrequested 'size' attribute\"));\n+\t\t\tif (results->sizes)\n+\t\t\t\tdie(_(\"object-info: duplicate 'size' attribute\"));\n \t\t\tsize_index = (int)i;\n-\t\t\tfor (size_t j = 0; j < 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\tCALLOC_ARRAY(results->sizes, results->nr);\n \t\t} else {\n-\t\t\tBUG(\"only size is supported\");\n+\t\t\tdie(_(\"object-info: unknown attribute '%s'\"),\n+\t\t\t    reader->line);\n \t\t}\n \t}\n \n@@ -137,24 +127,24 @@ int fetch_object_info(const enum protocol_version version,\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\tresults->unrecognized[i] = 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 * Because we only ask for attributes the server said it\n+\t\t * supports, we expect the answer to have one value per\n+\t\t * requested attribute, plus the OID.\n \t\t */\n-\t\tif (object_info_options->nr + 1 != object_info_values.nr)\n+\t\tif (wanted + 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\tif (results->sizes &&\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\t      &results->sizes[i]))\n+\t\t\tdie(\"object-info: object %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@@ -169,3 +159,10 @@ int fetch_object_info(const enum protocol_version version,\n \n \treturn 0;\n }\n+\n+void free_fetch_object_info_results(struct fetch_object_info_results *results)\n+{\n+\tfree(results->sizes);\n+\tfree(results->unrecognized);\n+\tmemset(results, 0, sizeof(*results));\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 316bf917ce..9f72e91155 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -4,21 +4,34 @@\n #include \"pkt-line.h\"\n #include \"protocol.h\"\n \n-struct object_info;\n+struct fetch_object_info_results {\n+\tsize_t *sizes;\n+\tuint8_t *unrecognized;\n+\tsize_t nr;\n+\tunsigned wants_size:1;\n+};\n+\n+#define FETCH_OBJECT_INFO_RESULTS_INIT { 0 }\n+\n struct oid_array;\n /*\n- * Sends git-cat-file object-info command into the request buf and read the\n+ * Sends git-cat-file object-info command into the request buf and reads the\n  * results from packets.\n  *\n- * Modifies object_info_options, on return it contains only the supported\n- * options by the server.\n+ * The caller sets the wants_* flags in \"results\" to indicate which attributes\n+ * it is interested in. On return, \"results\" holds one array per attribute that\n+ * the server both advertised and answered with. An array left NULL means the\n+ * attribute is not available.\n+ * Release them with free_fetch_object_info_results().\n  */\n int fetch_object_info(enum protocol_version version,\n \t\t      const struct string_list *server_options,\n \t\t      struct oid_array *oids,\n-\t\t      struct string_list *object_info_options,\n \t\t      struct packet_reader *reader,\n-\t\t      struct object_info *object_info_data,\n-\t\t      int stateless_rpc, int fd_out);\n+\t\t      struct fetch_object_info_results *results,\n+\t\t      int stateless_rpc,\n+\t\t      int fd_out);\n+\n+void free_fetch_object_info_results(struct fetch_object_info_results *results);\n \n #endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/object-file.c b/object-file.c\nindex ec35c318bc..66f6c81373 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1340,13 +1340,3 @@ 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 cbc2f9ced4..f4e6c26a0e 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -637,7 +637,4 @@ 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/transport.c b/transport.c\nindex 2bb5eedc32..b0e29124d1 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -451,9 +451,8 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tret = fetch_object_info(data->version,\n \t\t\t\ttransport->server_options,\n \t\t\t\ttransport->smart_options->object_info_oids,\n-\t\t\t\ttransport->smart_options->object_info_options,\n \t\t\t\t&reader,\n-\t\t\t\tdata->options.object_info_data,\n+\t\t\t\tdata->options.object_info_results,\n \t\t\t\ttransport->stateless_rpc, data->fd[1]);\n \n \tclose(data->fd[0]);\ndiff --git a/transport.h b/transport.h\nindex a7869d18e0..6948b65db9 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -7,6 +7,8 @@\n #include \"string-list.h\"\n #include \"connect.h\"\n \n+struct fetch_object_info_results;\n+\n struct git_transport_options {\n \tunsigned thin : 1;\n \tunsigned keep : 1;\n@@ -57,8 +59,7 @@ 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 string_list *object_info_options;\n+\tstruct fetch_object_info_results *object_info_results;\n };\n \n enum transport_family {\n\n-- \n2.54.0\n\n"},{"id":"549597","messageId":"20260804-objecttype-support-v4-6-31511b0231be@gmail.com","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","subject":"[PATCH GSoC v4 6/9] protocol-caps: add type support to object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T18:43:00Z","receivedAt":"2026-08-04T18:43:07Z","isPatch":true,"body":"Teach the server-side object-info handler to accept type as a requested\nfield. When the client includes type in its object-info request, the\nserver returns the requested object type.\n\nWhile touching send_info(), wrap an over-long line and fix the bit field\nstyle of requested_info.size.\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 protocol-caps.c      | 21 ++++++++++++++++++---\n t/t5701-git-serve.sh | 30 ++++++++++++++++++++++++++++++\n 2 files changed, 48 insertions(+), 3 deletions(-)\n\ndiff --git a/protocol-caps.c b/protocol-caps.c\nindex 02261be14d..27e0f85b10 100644\n--- a/protocol-caps.c\n+++ b/protocol-caps.c\n@@ -11,7 +11,8 @@\n #include \"strbuf.h\"\n \n struct requested_info {\n-\tunsigned size : 1;\n+\tunsigned size:1;\n+\tunsigned type:1;\n };\n \n /*\n@@ -73,15 +74,20 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \tif (info->size)\n \t\tpacket_writer_write(writer, \"size\");\n \n+\tif (info->type)\n+\t\tpacket_writer_write(writer, \"type\");\n+\n \tfor_each_string_list_item (item, oid_str_list) {\n \t\tconst char *oid_str = item->string;\n+\t\tenum object_type object_type;\n \t\tstruct object_id oid;\n \t\tsize_t object_size;\n \n \t\tif (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n \t\t\tpacket_writer_error(\n \t\t\t\twriter,\n-\t\t\t\t\"object-info: protocol error, expected to get oid, not '%s'\",\n+\t\t\t\t\"object-info: protocol error, expected to get \"\n+\t\t\t\t\"oid, not '%s'\",\n \t\t\t\toid_str);\n \t\t\tcontinue;\n \t\t}\n@@ -93,7 +99,8 @@ static void send_info(struct repository *r, struct packet_writer *writer,\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\tobject_type = get_object_info(r->objects, &oid, &object_size);\n+\t\tif (object_type <= OBJ_NONE) {\n \t\t\tstrbuf_addstr(&send_buffer, \" \");\n \t\t\tgoto write;\n \t\t}\n@@ -103,6 +110,9 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \t\t\t\t    (uintmax_t)object_size);\n \t\t}\n \n+\t\tif (info->type)\n+\t\t\tstrbuf_addf(&send_buffer, \" %s\", type_name(object_type));\n+\n write:\n \t\tpacket_writer_write(writer, \"%s\", send_buffer.buf);\n \t\tstrbuf_reset(&send_buffer);\n@@ -124,6 +134,11 @@ int cap_object_info(struct repository *r, struct packet_reader *request)\n \t\t\tcontinue;\n \t\t}\n \n+\t\tif (!strcmp(\"type\", request->line)) {\n+\t\t\tinfo.type = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif (parse_oid(request->line, &oid_str_list))\n \t\t\tcontinue;\n \ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 51d5dd1ae6..f57e36a88d 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -369,6 +369,36 @@ test_expect_success 'basics of object-info' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'object-info supports type' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttwo_oid=$(git rev-parse two:two.t) &&\n+\ttwo_size=$(test_file_size two.t) &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\tsize\n+\ttype\n+\toid $two_oid\n+\toid $two_oid\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tsize\n+\ttype\n+\t$two_oid $two_size blob\n+\t$two_oid $two_size blob\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 'bare OID request' '\n \ttest_config transfer.advertiseObjectInfo true &&\n \n\n-- \n2.54.0\n\n"},{"id":"549598","messageId":"20260804-objecttype-support-v4-7-31511b0231be@gmail.com","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","subject":"[PATCH GSoC v4 7/9] fetch-object-info: parse type from server response","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T18:43:01Z","receivedAt":"2026-08-04T18:43:08Z","isPatch":true,"body":"The server can handle type requests but does not advertise the\ncapability yet. Prepare the client to know how to parse the server\nresponse once the server advertises the type capability.\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  |  7 +++++++\n fetch-object-info.c | 38 +++++++++++++++++++++++++++++++++++---\n fetch-object-info.h |  3 +++\n 3 files changed, 45 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex e1650b2921..7a3ae11a70 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -844,6 +844,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \n \tif (data->info.sizep)\n \t\tresults.wants_size = 1;\n+\tif (data->info.typep)\n+\t\tresults.wants_type = 1;\n \n \tif (get_remote_info(count, argv, &results, &object_info_oids))\n \t\tdie(_(\"failed to get object info from the remote: %s\"), argv[0]);\n@@ -852,6 +854,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n \tif (results.sizes)\n \t\tstring_list_append(&data->remote_allowed_atoms, \"objectsize\");\n+\tif (results.types)\n+\t\tstring_list_append(&data->remote_allowed_atoms, \"objecttype\");\n \n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < results.nr; i++) {\n@@ -870,6 +874,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tif (results.sizes)\n \t\t\tdata->size = results.sizes[i];\n \n+\t\tif (results.types)\n+\t\t\tdata->type = results.types[i];\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);\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 6069d6a58b..ad27b1e4ca 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n+#include \"object.h\"\n #include \"pkt-line.h\"\n #include \"connect.h\"\n #include \"oid-array.h\"\n@@ -12,7 +13,8 @@\n static void send_object_info_request(const int fd_out,\n \t\t\t\t     const struct string_list *server_options,\n \t\t\t\t     struct oid_array *oids,\n-\t\t\t\t     unsigned ask_size)\n+\t\t\t\t     unsigned ask_size,\n+\t\t\t\t     unsigned ask_type)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n@@ -21,6 +23,9 @@ static void send_object_info_request(const int fd_out,\n \tif (ask_size)\n \t\tpacket_buf_write(&req_buf, \"size\");\n \n+\tif (ask_type)\n+\t\tpacket_buf_write(&req_buf, \"type\");\n+\n \tif (oids)\n \t\tfor (size_t i = 0; i < oids->nr; i++)\n \t\t\tpacket_buf_write(&req_buf, \"oid %s\",\n@@ -56,7 +61,9 @@ void fetch_object_info(enum protocol_version version,\n \t\t       int fd_out)\n {\n \tunsigned ask_size = 0;\n+\tunsigned ask_type = 0;\n \tint size_index = -1;\n+\tint type_index = -1;\n \tsize_t wanted;\n \n \tresults->nr = oids->nr;\n@@ -71,11 +78,16 @@ void fetch_object_info(enum protocol_version version,\n \t\t    server_supports_feature(\"object-info\", \"size\", 0))\n \t\t\task_size = 1;\n \n+\t\tif (results->wants_type &&\n+\t\t    server_supports_feature(\"object-info\", \"type\", 0))\n+\t\t\task_type = 1;\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, server_options, oids, ask_size);\n+\t\tsend_object_info_request(fd_out, server_options, oids, ask_size,\n+\t\t\t\t\t ask_type);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -83,7 +95,7 @@ void fetch_object_info(enum protocol_version version,\n \tcase protocol_unknown_version:\n \t\tBUG(\"unknown protocol version\");\n \t}\n-\twanted = ask_size;\n+\twanted = ask_size + ask_type;\n \n \tfor (size_t i = 0; i < wanted; i++) {\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n@@ -100,6 +112,13 @@ void fetch_object_info(enum protocol_version version,\n \t\t\t\tdie(_(\"object-info: duplicate 'size' attribute\"));\n \t\t\tsize_index = (int)i;\n \t\t\tCALLOC_ARRAY(results->sizes, results->nr);\n+\t\t} else if (!strcmp(reader->line, \"type\")) {\n+\t\t\tif (!ask_type)\n+\t\t\t\tdie(_(\"object-info: unrequested 'type' attribute\"));\n+\t\t\tif (results->types)\n+\t\t\t\tdie(_(\"object-info: duplicate 'type' attribute\"));\n+\t\t\ttype_index = (int)i;\n+\t\t\tCALLOC_ARRAY(results->types, results->nr);\n \t\t} else {\n \t\t\tdie(_(\"object-info: unknown attribute '%s'\"),\n \t\t\t    reader->line);\n@@ -149,6 +168,18 @@ void fetch_object_info(enum protocol_version version,\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\tif (results->types) {\n+\t\t\tconst char *type_str =\n+\t\t\t\tobject_info_values.items[type_index + 1].string;\n+\t\t\tint type = type_from_string_gently(type_str, -1, 1);\n+\n+\t\t\tif (type < 0)\n+\t\t\t\tdie(_(\"object-info: object %s has invalid type '%s'\"),\n+\t\t\t\t    object_info_values.items[0].string, type_str);\n+\n+\t\t\tresults->types[i] = type;\n+\t\t}\n+\n \t\tstring_list_clear(&object_info_values, 0);\n \t}\n \n@@ -162,6 +193,7 @@ void fetch_object_info(enum protocol_version version,\n void free_fetch_object_info_results(struct fetch_object_info_results *results)\n {\n \tfree(results->sizes);\n+\tfree(results->types);\n \tfree(results->unrecognized);\n \tmemset(results, 0, sizeof(*results));\n }\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 97ee5314c9..10b3641f7c 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -1,14 +1,17 @@\n #ifndef FETCH_OBJECT_INFO_H\n #define FETCH_OBJECT_INFO_H\n \n+#include \"object.h\"\n #include \"pkt-line.h\"\n #include \"protocol.h\"\n \n struct fetch_object_info_results {\n \tsize_t *sizes;\n+\tenum object_type *types;\n \tuint8_t *unrecognized;\n \tsize_t nr;\n \tunsigned wants_size:1;\n+\tunsigned wants_type:1;\n };\n \n #define FETCH_OBJECT_INFO_RESULTS_INIT { 0 }\n\n-- \n2.54.0\n\n"},{"id":"549599","messageId":"20260804-objecttype-support-v4-8-31511b0231be@gmail.com","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","subject":"[PATCH GSoC v4 8/9] serve: advertise type capability","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T18:43:02Z","receivedAt":"2026-08-04T18:43:11Z","isPatch":true,"body":"The server and the client can handle type requests but the client won't\nask for it until the server advertises it.\n\nAdd type to the advertised capabilities so the client knows that it can\nrequest 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 serve.c                                |  4 ++--\n t/t1017-cat-file-remote-object-info.sh | 26 ++++++++++++++++++++++----\n 2 files changed, 24 insertions(+), 6 deletions(-)\n\ndiff --git a/serve.c b/serve.c\nindex 2b07d922b3..2ce513cf2d 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -97,9 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value)\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n-\t/* Currently only size is supported */\n+\t/* Currently only size and type are supported */\n \tif (value && advertise_object_info)\n-\t\tstrbuf_addstr(value, \"size\");\n+\t\tstrbuf_addstr(value, \"size type\");\n \treturn advertise_object_info;\n }\n \ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 116862f9d0..190c45eefc 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -7,6 +7,7 @@ test_description='git cat-file --batch-command with remote-object-info command'\n \n hello_content=\"Hello World\"\n hello_size=$(strlen \"$hello_content\")\n+hello_type=\"blob\"\n hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n \n@@ -19,6 +20,7 @@ unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdi\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+tree_type=\"tree\"\n \n commit_message=\"Initial commit\"\n \n@@ -31,6 +33,7 @@ commit_message=\"Initial commit\"\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+commit_type=\"commit\"\n \n tag_header_without_oid=\"type blob\n tag hellotag\n@@ -44,6 +47,7 @@ $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+tag_type=\"tag\"\n \n set_transport_variables () {\n \thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n@@ -256,14 +260,12 @@ 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+test_expect_success 'objecttype is supported by remote-object-info' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n \n-\t\techo \"\" >expect &&\n+\t\techo \"$hello_type\" >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@@ -271,6 +273,22 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '\n \t)\n '\n \n+test_expect_success 'unsupported placeholders on remote return empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tfmt=\"%(objectmode) %(objectsize:disk) %(rest) %(deltabase)\" &&\n+\n+\t\t# The hardcoded SPs between the atoms are respected.\n+\t\techo \"   \" >expect &&\n+\t\tgit cat-file --batch-command=\"$fmt\" >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 'requesting only objectname echoes back' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n\n-- \n2.54.0\n\n"},{"id":"549600","messageId":"20260804-objecttype-support-v4-9-31511b0231be@gmail.com","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","subject":"[PATCH GSoC v4 9/9] cat-file: unify default format","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T18:43:03Z","receivedAt":"2026-08-04T18:43:12Z","isPatch":true,"body":"%(objecttype) is supported both by the client and by the server.\nChange the temporary default format to the unified version that the\nother commands use.\n\nUpdate documentation to remove %(objecttype) from the caveats of\nremote-object-info and show %(objecttype) support.\n\nNow that type is supported and the default format unified, update the\ntests to expect the new default format.\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        | 17 +++++------\n Documentation/gitprotocol-v2.adoc      | 18 +++++++++---\n builtin/cat-file.c                     |  7 -----\n t/t1017-cat-file-remote-object-info.sh | 52 +++++++++++++++++-----------------\n 4 files changed, 47 insertions(+), 47 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex ac3b528c6f..514bfc0032 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -348,15 +348,12 @@ 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+The command `remote-object-info` only supports the `%(objectname)`,\n+`%(objectsize)` and `%(objecttype)` placeholders. See `CAVEATS` below for more\n+information.\n \n If no format is specified, the default format is `%(objectname)\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+%(objecttype) %(objectsize)`.\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@@ -453,9 +450,9 @@ 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+Note that only `%(objectname)`, `%(objectsize)` and `%(objecttype)` are\n+currently supported by the `remote-object-info` command. Using any other\n+placeholder in 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/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 7bf62014c3..dd52fd8110 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -558,14 +558,17 @@ object-info\n \n `object-info` is the command to retrieve information about one or more objects.\n Its main purpose is to allow a client to make decisions based on this\n-information without having to fully fetch objects. Object size is the only\n-information that is currently supported.\n+information without having to fully fetch objects. Currently only object size\n+and type are supported.\n \n An `object-info` request takes the following arguments:\n \n \tsize\n \tRequests size information to be returned for each listed object id.\n \n+\ttype\n+\tRequests type information to be returned for each listed object id.\n+\n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n \tinformation for. They must be full OIDs.\n@@ -580,11 +583,18 @@ space.\n \tinfo = *PKT-LINE(attr LF)\n \t       *PKT-LINE(obj-info LF)\n \n-\tattr = \"size\"\n+\tattr = \"size\" | \"type\"\n \n \tobj-size = 1*DIGIT\n \n-\tobj-info = obj-id [SP [obj-size]]\n+\tobj-type = \"blob\" | \"tree\" | \"commit\" | \"tag\"\n+\n+\tobj-val = obj-size | obj-type\n+\n+\tobj-info = obj-id [SP [obj-val *(SP obj-val)]]\n+\n+The values in `obj-info` appear in the same order as the corresponding `attr`\n+lines, with exactly one value per requested attribute.\n \n If the server does not recognize the OID, the response will be `<oid> SP`\n regardless of the number of attributes requested.\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 7a3ae11a70..867079a62e 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -823,15 +823,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;\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@@ -883,7 +877,6 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n-\topt->format = saved_format;\n \n \tfree_fetch_object_info_results(&results);\n \tfree(line_to_split);\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 190c45eefc..e2919aa061 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -139,10 +139,10 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\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\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -152,7 +152,7 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\n \t)\n '\n \n-test_expect_success 'remote-object-info does not change the default format of info' '\n+test_expect_success 'remote-object-info and info can be mixed using the unified default format' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n@@ -162,7 +162,7 @@ test_expect_success 'remote-object-info does not change the default format of in\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 \"$hello_oid blob $hello_size\" >>expect &&\n \t\techo \"$local_oid blob $local_size\" >>expect &&\n \n \t\tgit cat-file --batch-command >actual <<-EOF &&\n@@ -209,10 +209,10 @@ test_expect_success 'batch-command -Z remote-object-info git:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -448,10 +448,10 @@ test_expect_success 'batch-command remote-object-info file:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -467,10 +467,10 @@ test_expect_success 'batch-command -Z remote-object-info file:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -618,10 +618,10 @@ test_expect_success 'batch-command remote-object-info http:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -636,10 +636,10 @@ test_expect_success 'batch-command -Z remote-object-info http:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $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-- \n2.54.0\n\n"},{"id":"549613","messageId":"xmqqpkzxtyac.fsf@gitster.g","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-2-31511b0231be@gmail.com","subject":"Re: [PATCH GSoC v4 2/9] fetch-object-info: detect malformed server responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-04T20:40:11Z","receivedAt":"2026-08-04T20:40:15Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> The loop reading the object-info response stops as soon as the reader\n> returns something other than PACKET_READ_NORMAL, or once it has read as\n> many lines as we requested. Neither end is checked.\n>\n> A server that answers with fewer objects leaves the end of the result\n> arrays empty, and the caller trusts that every requested object was\n> filled in. A server that answers with more leaves the extra packets\n> unread. On stateless transports check_stateless_delimiter() notices, but\n> on the others it passes unnoticed.\n>\n> Check both limits by extracting the packet_reader_read() from the loop\n> condition, so the loop no longer consumes the last packet (flush). If\n> while looping the read is different from a PACKET_READ_NORMAL, die()\n> meaning there are fewer objects than expected. After iterating, we only\n> expect a flush, so if the last packet is not a flush, die().\n\nOK.\n\nAfter looking at \"if (i != oids->nr)\" now gone from the end of the\nfunction, but before looking at how the loop terminates its\niterations, I wondered how the \"there are too few\" detection went.\nIt now not just stops iterating when seeing a status other than\nPACKET_READ_NORMAL, but it actively barfs by dying, to detect a\nshort read.  So the only thing we need to check after the loop is if\nwe are truly at the end of the \"list of oids->nr things\".\n\nMakes sense.\n\n> Helped-by: Junio C Hamano <gitster@pobox.com>\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>  fetch-object-info.c | 14 ++++++++++----\n>  1 file changed, 10 insertions(+), 4 deletions(-)\n>\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> index ba7e179c44..c2d4bf9403 100644\n> --- a/fetch-object-info.c\n> +++ b/fetch-object-info.c\n> @@ -106,12 +106,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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> +\tfor (size_t i = 0; i < args->oids->nr; i++) {\n>  \t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n>  \n> +\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n> +\t\t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n> +\t\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n> +\n>  \t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n>  \n>  \t\tif (strcmp(object_info_values.items[0].string,\n> @@ -150,6 +151,11 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n>  \n>  \t\tstring_list_clear(&object_info_values, 0);\n>  \t}\n> +\n> +\tif (packet_reader_read(reader) != PACKET_READ_FLUSH)\n> +\t\tdie(_(\"object-info: expected flush after %\"PRIuMAX\" objects\"),\n> +\t\t    (uintmax_t)args->oids->nr);\n> +\n>  \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n>  \n>  \treturn 0;\n"},{"id":"549615","messageId":"xmqqldalty3i.fsf@gitster.g","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-3-31511b0231be@gmail.com","subject":"Re: [PATCH GSoC v4 3/9] fetch-object-info: pass arguments directly instead of a struct","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-04T20:44:17Z","receivedAt":"2026-08-04T20:44:20Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> struct object_info_args groups three pointers that already live in the\n> transport and are given to fetch_object_info().\n> Grouping them into a struct reduces the number of parameters, but it\n> suggests that the three belong together, when they are unrelated and end\n> up being accessed as args->* independently.\n>\n> Drop the struct and pass those parameters directly to\n> fetch_object_info() and send_object_info_request(). This should have no\n> change in behavior.\n>\n> Helped-by: Jeff King <peff@peff.net>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\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>  fetch-object-info.c | 53 +++++++++++++++++++++++++++++++----------------------\n>  fetch-object-info.h | 17 ++++++++---------\n>  transport.c         | 11 +++++------\n>  3 files changed, 44 insertions(+), 37 deletions(-)\n\nOK, in this iteration, the step is truly about just getting rid of\nthe intermediate \"args\" structure and passing these three things\nseparately, without any other changes like the (half) addition of\n\"type\" support like the previous iteration had.\n\n"},{"id":"549618","messageId":"xmqqh5l9txg6.fsf@gitster.g","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-4-31511b0231be@gmail.com","subject":"Re: [PATCH GSoC v4 4/9] fetch-object-info: use dedicated struct for the results","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-04T20:58:17Z","receivedAt":"2026-08-04T20:58:19Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n>  builtin/cat-file.c  | 59 +++++++++-----------------------------\n>  fetch-object-info.c | 81 ++++++++++++++++++++++++++---------------------------\n>  fetch-object-info.h | 27 +++++++++++++-----\n>  object-file.c       | 10 -------\n>  odb.h               |  3 --\n>  transport.c         |  3 +-\n>  transport.h         |  5 ++--\n>  7 files changed, 77 insertions(+), 111 deletions(-)\n\nWe lost ~30 lines mostly thanks to losing an intermediate structure\nand need to flip pointer members in it to point at heap allocated\nlocations?\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\nHmph, it would not make any difference in the final result, as we\nare losing protocol_placeholder_entry and this list of atoms, but\nI am somewhat confused.   Why we already had \"type\" here, even\nthough we are adding support for it much later in the series (and\nunlike the previous iteration, this iteration did not do the\nask/want_type thing in the previous step)?\n"},{"id":"549624","messageId":"DKGHIZDB5W00.QN7C3PZ77K7B@gmail.com","threadId":"66063","inReplyTo":"xmqqh5l9txg6.fsf@gitster.g","subject":"Re: [PATCH GSoC v4 4/9] fetch-object-info: use dedicated struct for the results","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-04T21:42:04Z","receivedAt":"2026-08-04T21:42:08Z","isPatch":true,"body":"On Tue Aug 4, 2026 at 10:58 PM CEST, Junio C Hamano wrote:\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n>>  builtin/cat-file.c  | 59 +++++++++-----------------------------\n>>  fetch-object-info.c | 81 ++++++++++++++++++++++++++---------------------------\n>>  fetch-object-info.h | 27 +++++++++++++-----\n>>  object-file.c       | 10 -------\n>>  odb.h               |  3 --\n>>  transport.c         |  3 +-\n>>  transport.h         |  5 ++--\n>>  7 files changed, 77 insertions(+), 111 deletions(-)\n>\n> We lost ~30 lines mostly thanks to losing an intermediate structure\n> and need to flip pointer members in it to point at heap allocated\n> locations?\n\nYes, also contributes that the logic that filtered the allow-list is\ndropped, and compared to the previous version, no type logic is in\nthis patch.\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> Hmph, it would not make any difference in the final result, as we\n> are losing protocol_placeholder_entry and this list of atoms, but\n> I am somewhat confused.   Why we already had \"type\" here, even\n> though we are adding support for it much later in the series (and\n> unlike the previous iteration, this iteration did not do the\n> ask/want_type thing in the previous step)?\n\nThe list was designed so even if new placeholder were added, it would be\nidemp. because nothing would happen until a server did advertise that\nattribute; that's why there's no change in behavior.\n\nIt was there because it was interesting for the tests. A known\nplaceholder but unsupported in the server still outputs an empty string\n(this would be the case of: new client vs old server).\n\nThis way we had tested:\n\n  unknown + unsupported as %(deltabase) is.\n  known + unsupported as %(objecttype) was. (that's why it's on the list)\n  knwon + supported as %(objectsize) is.\n\nUsing object_info and the list made sense because object_info already had\na type field, so listing type cost nothing. Now that we have to dedicate\nan array per attribute it would mean carrying a types array that nobody\nfills, so I preferred to drop it here and implement type fully later.\n\nHope this makes sense,\nPablo\n"},{"id":"549837","messageId":"CAOLa=ZT_Zv82cWdhK6rJiAQm05WjdPwfG0p7nLO9pZ-hiX4g3Q@mail.gmail.com","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-3-31511b0231be@gmail.com","subject":"Re: [PATCH GSoC v4 3/9] fetch-object-info: pass arguments directly instead of a struct","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-06T11:21:06Z","receivedAt":"2026-08-06T11:21:08Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> struct object_info_args groups three pointers that already live in the\n> transport and are given to fetch_object_info().\n> Grouping them into a struct reduces the number of parameters, but it\n> suggests that the three belong together, when they are unrelated and end\n> up being accessed as args->* independently.\n>\n\nThis reads much better and explains why we make the change.\n\n> Drop the struct and pass those parameters directly to\n> fetch_object_info() and send_object_info_request(). This should have no\n> change in behavior.\n\n[snip]\n\nThe changes too look in order.\n"},{"id":"549867","messageId":"20260806171714.GA1632126@coredump.intra.peff.net","threadId":"66063","inReplyTo":"20260804-objecttype-support-v4-0-31511b0231be@gmail.com","subject":"Re: [PATCH GSoC v4 0/9] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-08-06T17:17:14Z","receivedAt":"2026-08-06T17:17:17Z","isPatch":true,"body":"On Tue, Aug 04, 2026 at 08:42:54PM +0200, Pablo Sabater wrote:\n\n> Patches 1-5 are preparatory. They don't change what the command does:\n> - [1/9] is a test cleanup.\n> - [2/9] fixes a possible bug in case of a malformed response.\n> - [3/9] and [4/9] refactor how the object data is stored and handled. The\n>   why about this refactor comes from [2].\n\nThanks, I think these refactors in patches 3 and 4 make sense and\naddress the issues raised in the earlier thread. I'd actually take patch\n3 just a step further, as below (which you are welcome to put on top of\nyour series, or work it into the middle, or even take as inspiration and\nrewrite as part of another patch).\n\n-- >8 --\nSubject: transport: drop remote object-info fields from transport struct\n\nA remote object-info request needs three things: the transport for\ncontacting the remote, the list of oids to request, and a place to store\nthe output.\n\nRather than take these as function parameters, we take only the\ntransport object, and expect the caller to have placed the other two\ninto special fields in the transport struct. But this doesn't make much\nsense. The set of oids and results are really only valid for one\nrequest. There is no reason the transport would need to hang on to them\noutside of the single function call.\n\nEven though we save a few lines passing the parameters around through\nthe various vtable functions, the result is harder to understand (for\nexample, who is responsible for cleaning up results, and when shoudl it\nhappen?). It also opens up the possibility of a subtle bug. A caller is\nlikely to point those fields to stack variables which could go out of\nscope, and the transport struct would be left holding invalid pointers.\n\nThis is mostly harmless now, as we disconnect the transport immediately\nafter the sole caller of transport_fetch_object_info(). But conceptually\nwe could keep we could keep the transport open and make multiple fetch\ncalls (and reuse the same connection to the helper, to a remote HTTP\nserver, and so on).\n\nSo let's pull these out of the struct and pass them as function\nparameters. It's a little more verbose, but I think more clearly\nillustrates the intent. I've also tweaked a few function signatures to\nmark the input oid array as const, since it is purely an input to the\nfunction.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nI do think the concept of reusing the transport will become useful\nlater. We limit a single request to 10,000 objects, so it is quite\nconceivable a caller would want to make several. That can mostly come\nlater on top, though I think the design of the remote-object-info\ncommand makes it awkward. Each invocation provides a remote by name,\nwhich is then resolved to a transport. But a given caller is likely\ngoing to provide the same remote over and over again.\n\nWe probably could get away with just caching the last-used transport and\nreusing it when fed the same remote name again. But we could perhaps\nalso change the protocol (which AFAICT is not yet in any released\nversion, so still available for changes) to specify the two\nindependently, like:\n\n   remote https://example.com/foo.git\n   remote-object-info objA objB objC...\n   remote-object-info objX objY objZ\n\nAnd then it is more clear that setting \"remote\" is stateful, and will be\nused for subsequent remote-* commands. But maybe that statefulness is\nsomething we don't want. I dunno.\n\nAnyway, either way I think the cleanup below is worth doing in the short\nterm.\n\n builtin/cat-file.c   |  6 ++----\n fetch-object-info.c  |  4 ++--\n fetch-object-info.h  |  2 +-\n transport-helper.c   |  7 +++++--\n transport-internal.h |  4 +++-\n transport.c          | 14 +++++++++-----\n transport.h          |  7 +++----\n 7 files changed, 25 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 950d9f237f..4f4d791821 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -730,10 +730,8 @@ static int get_remote_info(int argc,\n \t\tgoto cleanup;\n \t}\n \n-\tgtransport->smart_options->object_info_oids = object_info_oids;\n-\n-\tgtransport->smart_options->object_info_results = results;\n-\tretval = transport_fetch_object_info(gtransport);\n+\tretval = transport_fetch_object_info(gtransport, object_info_oids,\n+\t\t\t\t\t     results);\n cleanup:\n \ttransport_disconnect(gtransport);\n \treturn retval;\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex ad27b1e4ca..385462c707 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -12,7 +12,7 @@\n /* Sends object-info command and its arguments into the request buffer. */\n static void send_object_info_request(const int fd_out,\n \t\t\t\t     const struct string_list *server_options,\n-\t\t\t\t     struct oid_array *oids,\n+\t\t\t\t     const struct oid_array *oids,\n \t\t\t\t     unsigned ask_size,\n \t\t\t\t     unsigned ask_type)\n {\n@@ -54,7 +54,7 @@ static int parse_object_size(const char *s, size_t *res)\n \n void fetch_object_info(enum protocol_version version,\n \t\t       const struct string_list *server_options,\n-\t\t       struct oid_array *oids,\n+\t\t       const struct oid_array *oids,\n \t\t       struct packet_reader *reader,\n \t\t       struct fetch_object_info_results *results,\n \t\t       int stateless_rpc,\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 10b3641f7c..2fba96c6f7 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -29,7 +29,7 @@ struct oid_array;\n  */\n void fetch_object_info(enum protocol_version version,\n \t\t       const struct string_list *server_options,\n-\t\t       struct oid_array *oids,\n+\t\t       const struct oid_array *oids,\n \t\t       struct packet_reader *reader,\n \t\t       struct fetch_object_info_results *results,\n \t\t       int stateless_rpc,\ndiff --git a/transport-helper.c b/transport-helper.c\nindex f3cb8f8662..d5a064d386 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -786,11 +786,14 @@ static int fetch_refs(struct transport *transport,\n \treturn -1;\n }\n \n-static int fetch_object_info_helper(struct transport *transport)\n+static int fetch_object_info_helper(struct transport *transport,\n+\t\t\t\t    const struct oid_array *oids,\n+\t\t\t\t    struct fetch_object_info_results *results)\n {\n \tget_helper(transport);\n \tif (process_connect(transport, 0))\n-\t\treturn transport->vtable->fetch_object_info(transport);\n+\t\treturn transport->vtable->fetch_object_info(transport, oids,\n+\t\t\t\t\t\t\t    results);\n \n \tdie(_(\"object-info requires protocol v2\"));\n }\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 60db0bedcd..e7ead5d785 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -51,7 +51,9 @@ struct transport_vtable {\n \t *\n \t * Uses object-info capability of v2 protocol.\n \t */\n-\tint (*fetch_object_info)(struct transport *transport);\n+\tint (*fetch_object_info)(struct transport *transport,\n+\t\t\t\t const struct oid_array *oids,\n+\t\t\t\t struct fetch_object_info_results *results);\n \n \t/**\n \t * Push the objects and refs. Send the necessary objects, and\ndiff --git a/transport.c b/transport.c\nindex 35acdf71a2..25e2c14a7b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -433,7 +433,9 @@ 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+static int fetch_object_info_via_pack(struct transport *transport,\n+\t\t\t\t      const struct oid_array *oids,\n+\t\t\t\t      struct fetch_object_info_results *results)\n {\n \tint ret = 0;\n \tstruct git_transport_data *data = transport->data;\n@@ -450,9 +452,9 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \n \tfetch_object_info(data->version,\n \t\t\t  transport->server_options,\n-\t\t\t  transport->smart_options->object_info_oids,\n+\t\t\t  oids,\n \t\t\t  &reader,\n-\t\t\t  data->options.object_info_results,\n+\t\t\t  results,\n \t\t\t  transport->stateless_rpc, data->fd[1]);\n \n \tclose(data->fd[0]);\n@@ -465,11 +467,13 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \treturn ret;\n }\n \n-int transport_fetch_object_info(struct transport *transport)\n+int transport_fetch_object_info(struct transport *transport,\n+\t\t\t\tconst struct oid_array *oids,\n+\t\t\t\tstruct fetch_object_info_results *results)\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+\treturn transport->vtable->fetch_object_info(transport, oids, results);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\ndiff --git a/transport.h b/transport.h\nindex 6948b65db9..39193d0077 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -57,9 +57,6 @@ 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 fetch_object_info_results *object_info_results;\n };\n \n enum transport_family {\n@@ -317,7 +314,9 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs);\n /*\n  * Fetch the object info from remote\n  */\n-int transport_fetch_object_info(struct transport *transport);\n+int transport_fetch_object_info(struct transport *transport,\n+\t\t\t\tconst struct oid_array *oids,\n+\t\t\t\tstruct fetch_object_info_results *results);\n \n /*\n  * If this flag is set, unlocking will avoid to call non-async-signal-safe\n-- \n2.55.0.819.g4e24f5e379\n\n"},{"id":"549901","messageId":"DKIADCID62IW.1MII8E3AYCI6F@gmail.com","threadId":"66063","inReplyTo":"20260806171714.GA1632126@coredump.intra.peff.net","subject":"Re: [PATCH GSoC v4 0/9] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T00:30:55Z","receivedAt":"2026-08-07T00:30:59Z","isPatch":true,"body":"On Thu Aug 6, 2026 at 7:17 PM CEST, Jeff King wrote:\n> On Tue, Aug 04, 2026 at 08:42:54PM +0200, Pablo Sabater wrote:\n>\n>> Patches 1-5 are preparatory. They don't change what the command does:\n>> - [1/9] is a test cleanup.\n>> - [2/9] fixes a possible bug in case of a malformed response.\n>> - [3/9] and [4/9] refactor how the object data is stored and handled. The\n>>   why about this refactor comes from [2].\n>\n> Thanks, I think these refactors in patches 3 and 4 make sense and\n> address the issues raised in the earlier thread. I'd actually take patch\n> 3 just a step further, as below (which you are welcome to put on top of\n> your series, or work it into the middle, or even take as inspiration and\n> rewrite as part of another patch).\n\nWow, thanks a lot for getting so involved, I think I'll place it as is.\n\n>\n> -- >8 --\n> Subject: transport: drop remote object-info fields from transport struct\n>\n> A remote object-info request needs three things: the transport for\n> contacting the remote, the list of oids to request, and a place to store\n> the output.\n>\n> Rather than take these as function parameters, we take only the\n> transport object, and expect the caller to have placed the other two\n> into special fields in the transport struct. But this doesn't make much\n> sense. The set of oids and results are really only valid for one\n> request. There is no reason the transport would need to hang on to them\n> outside of the single function call.\n>\n> Even though we save a few lines passing the parameters around through\n> the various vtable functions, the result is harder to understand (for\n> example, who is responsible for cleaning up results, and when shoudl it\n> happen?). It also opens up the possibility of a subtle bug. A caller is\n> likely to point those fields to stack variables which could go out of\n> scope, and the transport struct would be left holding invalid pointers.\n>\n> This is mostly harmless now, as we disconnect the transport immediately\n> after the sole caller of transport_fetch_object_info(). But conceptually\n> we could keep we could keep the transport open and make multiple fetch\n> calls (and reuse the same connection to the helper, to a remote HTTP\n> server, and so on).\n>\n> So let's pull these out of the struct and pass them as function\n> parameters. It's a little more verbose, but I think more clearly\n> illustrates the intent. I've also tweaked a few function signatures to\n> mark the input oid array as const, since it is purely an input to the\n> function.\n>\n> Signed-off-by: Jeff King <peff@peff.net>\n> ---\n> I do think the concept of reusing the transport will become useful\n> later. We limit a single request to 10,000 objects, so it is quite\n> conceivable a caller would want to make several. That can mostly come\n> later on top, though I think the design of the remote-object-info\n> command makes it awkward. Each invocation provides a remote by name,\n> which is then resolved to a transport. But a given caller is likely\n> going to provide the same remote over and over again.\n>\n> We probably could get away with just caching the last-used transport and\n> reusing it when fed the same remote name again. But we could perhaps\n> also change the protocol (which AFAICT is not yet in any released\n> version, so still available for changes) to specify the two\n> independently, like:\n>\n>    remote https://example.com/foo.git\n>    remote-object-info objA objB objC...\n>    remote-object-info objX objY objZ\n>\n> And then it is more clear that setting \"remote\" is stateful, and will be\n> used for subsequent remote-* commands. But maybe that statefulness is\n> something we don't want. I dunno.\n\nYeah, I think it is not in any released version yet as the\nps/cat-file-remote-object-info (the one that precedes this series)\nlanded in 'master' the first What's cooking of August [1].\n\nGiven that, I think that it could be a good idea to have both, if a user\nforesees that he's only going to make one 'remote-object-info' command\nhe can write it as it is now:\n\n  remote-object-info <remote> objA objB\n\nBut if a user foresees that he will have to make multiple ones, we can\nmake what you suggested:\n\n>    remote https://example.com/foo.git\n>    remote-object-info objA objB objC...\n>    remote-object-info objX objY objZ\n\nWe would have to make the remote optional, if there's no remote die(),\netc. We would also have to tell apart a remote from an OID in the first\nargument, but full OIDs and remote URLs are not very similar so that\nshould not be hard haha.\n\nI do like the idea, but I see it more as a follow-up series after this\none, as the topic of this series is type support.\nAlso, I'm biased as I have little time before my deadline ends.\n\nI'm happy to keep doing things and there are more things related to the\nobject-info protocol that I'd like to keep working on after finishing\nGSoC.\n\n>\n> Anyway, either way I think the cleanup below is worth doing in the short\n> term.\n>\n>  builtin/cat-file.c   |  6 ++----\n>  fetch-object-info.c  |  4 ++--\n>  fetch-object-info.h  |  2 +-\n>  transport-helper.c   |  7 +++++--\n>  transport-internal.h |  4 +++-\n>  transport.c          | 14 +++++++++-----\n>  transport.h          |  7 +++----\n>  7 files changed, 25 insertions(+), 19 deletions(-)\n>\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 950d9f237f..4f4d791821 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -730,10 +730,8 @@ static int get_remote_info(int argc,\n>  \t\tgoto cleanup;\n>  \t}\n>\n> -\tgtransport->smart_options->object_info_oids = object_info_oids;\n> -\n> -\tgtransport->smart_options->object_info_results = results;\n> -\tretval = transport_fetch_object_info(gtransport);\n> +\tretval = transport_fetch_object_info(gtransport, object_info_oids,\n> +\t\t\t\t\t     results);\n>  cleanup:\n>  \ttransport_disconnect(gtransport);\n>  \treturn retval;\n> diff --git a/fetch-object-info.c b/fetch-object-info.c\n> index ad27b1e4ca..385462c707 100644\n> --- a/fetch-object-info.c\n> +++ b/fetch-object-info.c\n> @@ -12,7 +12,7 @@\n>  /* Sends object-info command and its arguments into the request buffer. */\n>  static void send_object_info_request(const int fd_out,\n>  \t\t\t\t     const struct string_list *server_options,\n> -\t\t\t\t     struct oid_array *oids,\n> +\t\t\t\t     const struct oid_array *oids,\n>  \t\t\t\t     unsigned ask_size,\n>  \t\t\t\t     unsigned ask_type)\n>  {\n> @@ -54,7 +54,7 @@ static int parse_object_size(const char *s, size_t *res)\n>\n>  void fetch_object_info(enum protocol_version version,\n>  \t\t       const struct string_list *server_options,\n> -\t\t       struct oid_array *oids,\n> +\t\t       const struct oid_array *oids,\n>  \t\t       struct packet_reader *reader,\n>  \t\t       struct fetch_object_info_results *results,\n>  \t\t       int stateless_rpc,\n> diff --git a/fetch-object-info.h b/fetch-object-info.h\n> index 10b3641f7c..2fba96c6f7 100644\n> --- a/fetch-object-info.h\n> +++ b/fetch-object-info.h\n> @@ -29,7 +29,7 @@ struct oid_array;\n>   */\n>  void fetch_object_info(enum protocol_version version,\n>  \t\t       const struct string_list *server_options,\n> -\t\t       struct oid_array *oids,\n> +\t\t       const struct oid_array *oids,\n>  \t\t       struct packet_reader *reader,\n>  \t\t       struct fetch_object_info_results *results,\n>  \t\t       int stateless_rpc,\n> diff --git a/transport-helper.c b/transport-helper.c\n> index f3cb8f8662..d5a064d386 100644\n> --- a/transport-helper.c\n> +++ b/transport-helper.c\n> @@ -786,11 +786,14 @@ static int fetch_refs(struct transport *transport,\n>  \treturn -1;\n>  }\n>\n> -static int fetch_object_info_helper(struct transport *transport)\n> +static int fetch_object_info_helper(struct transport *transport,\n> +\t\t\t\t    const struct oid_array *oids,\n> +\t\t\t\t    struct fetch_object_info_results *results)\n>  {\n>  \tget_helper(transport);\n>  \tif (process_connect(transport, 0))\n> -\t\treturn transport->vtable->fetch_object_info(transport);\n> +\t\treturn transport->vtable->fetch_object_info(transport, oids,\n> +\t\t\t\t\t\t\t    results);\n>\n>  \tdie(_(\"object-info requires protocol v2\"));\n>  }\n> diff --git a/transport-internal.h b/transport-internal.h\n> index 60db0bedcd..e7ead5d785 100644\n> --- a/transport-internal.h\n> +++ b/transport-internal.h\n> @@ -51,7 +51,9 @@ struct transport_vtable {\n>  \t *\n>  \t * Uses object-info capability of v2 protocol.\n>  \t */\n> -\tint (*fetch_object_info)(struct transport *transport);\n> +\tint (*fetch_object_info)(struct transport *transport,\n> +\t\t\t\t const struct oid_array *oids,\n> +\t\t\t\t struct fetch_object_info_results *results);\n>\n>  \t/**\n>  \t * Push the objects and refs. Send the necessary objects, and\n> diff --git a/transport.c b/transport.c\n> index 35acdf71a2..25e2c14a7b 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -433,7 +433,9 @@ 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> +static int fetch_object_info_via_pack(struct transport *transport,\n> +\t\t\t\t      const struct oid_array *oids,\n> +\t\t\t\t      struct fetch_object_info_results *results)\n>  {\n>  \tint ret = 0;\n>  \tstruct git_transport_data *data = transport->data;\n> @@ -450,9 +452,9 @@ static int fetch_object_info_via_pack(struct transport *transport)\n>\n>  \tfetch_object_info(data->version,\n>  \t\t\t  transport->server_options,\n> -\t\t\t  transport->smart_options->object_info_oids,\n> +\t\t\t  oids,\n>  \t\t\t  &reader,\n> -\t\t\t  data->options.object_info_results,\n> +\t\t\t  results,\n>  \t\t\t  transport->stateless_rpc, data->fd[1]);\n>\n>  \tclose(data->fd[0]);\n> @@ -465,11 +467,13 @@ static int fetch_object_info_via_pack(struct transport *transport)\n>  \treturn ret;\n>  }\n>\n> -int transport_fetch_object_info(struct transport *transport)\n> +int transport_fetch_object_info(struct transport *transport,\n> +\t\t\t\tconst struct oid_array *oids,\n> +\t\t\t\tstruct fetch_object_info_results *results)\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> +\treturn transport->vtable->fetch_object_info(transport, oids, results);\n>  }\n>\n>  static int fetch_refs_via_pack(struct transport *transport,\n> diff --git a/transport.h b/transport.h\n> index 6948b65db9..39193d0077 100644\n> --- a/transport.h\n> +++ b/transport.h\n> @@ -57,9 +57,6 @@ 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 fetch_object_info_results *object_info_results;\n>  };\n>\n>  enum transport_family {\n> @@ -317,7 +314,9 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs);\n>  /*\n>   * Fetch the object info from remote\n>   */\n> -int transport_fetch_object_info(struct transport *transport);\n> +int transport_fetch_object_info(struct transport *transport,\n> +\t\t\t\tconst struct oid_array *oids,\n> +\t\t\t\tstruct fetch_object_info_results *results);\n>\n>  /*\n>   * If this flag is set, unlocking will avoid to call non-async-signal-safe\n\nI see everything all right.\n\nThere's two typos on the patch's commit message:\n- s/shoudl/should/\n- a duplicated \"we could keep\"\n\nI will fix them, so if you see anything changed in your patch it's just\nthat. If I end up changing anything else, I'll let you know.\n\n[1]: https://lore.kernel.org/git/xmqqldanxbq9.fsf@gitster.g/T/#t\n\nThanks, a lot,\nPablo\n\n"},{"id":"550057","messageId":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"[PATCH GSoC v5 00/10] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:06:55Z","receivedAt":"2026-08-07T22:07:12Z","isPatch":true,"body":"Neither the client nor the server knows how to handle type. This series\ncontinues the work for git cat-file --batch-command extending\nremote-object-info to support \"%(objecttype)\" end to end. It is based on\nits predecessor series \"cat-file: add remote-object-info to\nbatch-command\" [1] merged into master.\n\nType is the last property that is identical on the server and on the\nclient once fetched.\n\nWhether to support more metadata such as: - objectsize:disk\n- deltabase\n- objectmode (needs context)\n\nshould be discussed, unlike size and type, the rest depend on how things\nare packed on the server and on what the client already has, so they\ncannot reliably match local once fetched. IMO they are not worth\nsupporting, I can't find a use case for them.\n\nTo add a new placeholder, follow the steps in this series and add its\nwants_* flag to struct fetch_object_info_results.\n\nPatches 1-6 are preparatory. They don't change what the command does:\n- [1/10] is a test cleanup.\n- [2/10] fixes a possible bug in case of a malformed response.\n- [3/10] and [4/10] refactor how the object data is stored and handled.\n  The why about this refactor comes from [2].\n- [5/10] drops the last error return left in fetch_object_info().\n- [6/10] drops the remote object-info fields from the transport struct.\n\nPatches 7-10 are the actual objecttype support:\n- [7/10] teaches the server to answer type.\n- [8/10] teaches the client to parse it.\n- [9/10] advertises the capability so the client can start asking it.\n- [10/10] unifies the default format.\n\nNote that *_type appears from [8/10]. Before this series, \"type\" is\nalready listed in the client's allow-list, but the prep patches drop\nthat list, so the client knows nothing about type until [8/10] adds it\nback.\n\nGithub CI: https://github.com/pabloosabaterr/git/actions/runs/30938909345\n\n[1]: https://lore.kernel.org/git/20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com/\n[2]: https://lore.kernel.org/git/xmqqzez67yg1.fsf@gitster.g/\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nChanges in v5:\n- New [6/10] by Jeff King: drop the remote object-info fields from the\n  transport struct and pass them as function parameters instead.\n- [5/10]: keep the const qualifiers on parameters of fetch_object_info(),\n  matching how the signature was.\n- Link to v4: https://patch.msgid.link/20260804-objecttype-support-v4-0-31511b0231be@gmail.com\n\nChanges in v4:\n- Rebased onto master now that ps/cat-file-remote-object-info was merged.\n- Dropped the Based-on trailer.\n- Reworded [1/9] so it shows that we also benefit from preserving the\n  exit status.\n- Changed [2/9] to also check if a response returns more packets.\n- New [5/9]: fetch_object_info() dies on its last error return and\n  becomes void.\n- Moved the introduction of ask_type, wants_type to [7/9] instead of\n  [4/9].\n- Link to v3: https://lore.kernel.org/git/20260803-objecttype-support-v3-0-7176fecf7950@gmail.com/\n\nChanges in v3:\n- 1/8 (was 2/6): reword the commit message to explain that \"wc -c | xargs\"\n  only strips the padding some platforms add.\n- New 2/8: die if the server sends fewer object-info lines than\n  requested OIDs. Preexisting bug.\n- New 3/8: drop struct object_info_args and pass its members directly to\n  fetch_object_info().\n- New 4/8: replace struct object_info with struct\n  fetch_object_info_results to hold the results. This also drops the\n  object_info_options string list, remote_atom_map[] and\n  free_object_info_contents().\n- Dropped 1/6, which replaced the hardcoded \"size\" in\n  send_object_info_request() with a loop over object_info_options, as\n  that list no longer reaches it.\n- 6/8: follows the new result struct, so it only adds the type array.\n- 7/8: quote $hello_type in the test.\n- Link to v2: https://lore.kernel.org/git/20260731-objecttype-support-v2-0-af577461ed57@gmail.com/\n\nChanges in v2:\n- Changed t5701 test to use test_file_size.\n- Changed the for loop to use for_each_string_list_item().\n- Moved \"fetch-object-info: request all supported options dynamically\"\n  patch to be the first one as it is prep patch.\n- Typos and nits.\n- Restored the test mixing info and remote-object-info, adapted to the\n  unified format.\n- Link to v1: https://lore.kernel.org/git/20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com/\n\n---\nJeff King (1):\n      transport: drop remote object-info fields from transport struct\n\nPablo Sabater (9):\n      t5701: use test_file_size() to get the size of a file\n      fetch-object-info: detect malformed server responses\n      fetch-object-info: pass arguments directly instead of a struct\n      fetch-object-info: use dedicated struct for the results\n      fetch-object-info: die() on the remaining error path\n      protocol-caps: add type support to object-info\n      fetch-object-info: parse type from server response\n      serve: advertise type capability\n      cat-file: unify default format\n\n Documentation/git-cat-file.adoc        |  17 ++--\n Documentation/gitprotocol-v2.adoc      |  18 +++-\n builtin/cat-file.c                     |  73 +++++-----------\n fetch-object-info.c                    | 149 +++++++++++++++++++++------------\n fetch-object-info.h                    |  37 +++++---\n object-file.c                          |  10 ---\n odb.h                                  |   3 -\n protocol-caps.c                        |  21 ++++-\n serve.c                                |   4 +-\n t/t1017-cat-file-remote-object-info.sh |  78 ++++++++++-------\n t/t5701-git-serve.sh                   |  41 ++++++++-\n transport-helper.c                     |   7 +-\n transport-internal.h                   |   4 +-\n transport.c                            |  24 +++---\n transport.h                            |  10 +--\n 15 files changed, 294 insertions(+), 202 deletions(-)\n\nRange-diff versus v4:\n\n 1:  ef807d2fa2 =  1:  e5f8dd814c t5701: use test_file_size() to get the size of a file\n 2:  c3c3e37c36 =  2:  4c108f4a59 fetch-object-info: detect malformed server responses\n 3:  193f71d224 =  3:  54fe4136de fetch-object-info: pass arguments directly instead of a struct\n 4:  ee82d07710 =  4:  f286159201 fetch-object-info: use dedicated struct for the results\n 5:  b5c09feca9 !  5:  dbf5832d05 fetch-object-info: die() on the remaining error path\n    @@ fetch-object-info.c: static int parse_object_size(const char *s, size_t *res)\n     -\t\t      struct fetch_object_info_results *results,\n     -\t\t      const int stateless_rpc,\n     -\t\t      const int fd_out)\n    -+void fetch_object_info(enum protocol_version version,\n    ++void fetch_object_info(const enum protocol_version version,\n     +\t\t       const struct string_list *server_options,\n     +\t\t       struct oid_array *oids,\n     +\t\t       struct packet_reader *reader,\n     +\t\t       struct fetch_object_info_results *results,\n    -+\t\t       int stateless_rpc,\n    -+\t\t       int fd_out)\n    ++\t\t       const int stateless_rpc,\n    ++\t\t       const int fd_out)\n      {\n      \tunsigned ask_size = 0;\n      \tint size_index = -1;\n -:  ---------- >  6:  f89a6a8c1d transport: drop remote object-info fields from transport struct\n 6:  2c95d76b46 =  7:  56a9cdbde6 protocol-caps: add type support to object-info\n 7:  8c6b342c3a !  8:  1fd7392db9 fetch-object-info: parse type from server response\n    @@ fetch-object-info.c\n     @@\n      static void send_object_info_request(const int fd_out,\n      \t\t\t\t     const struct string_list *server_options,\n    - \t\t\t\t     struct oid_array *oids,\n    + \t\t\t\t     const struct oid_array *oids,\n     -\t\t\t\t     unsigned ask_size)\n     +\t\t\t\t     unsigned ask_size,\n     +\t\t\t\t     unsigned ask_type)\n    @@ fetch-object-info.c: static void send_object_info_request(const int fd_out,\n      \tif (oids)\n      \t\tfor (size_t i = 0; i < oids->nr; i++)\n      \t\t\tpacket_buf_write(&req_buf, \"oid %s\",\n    -@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n    - \t\t       int fd_out)\n    +@@ fetch-object-info.c: void fetch_object_info(const enum protocol_version version,\n    + \t\t       const int fd_out)\n      {\n      \tunsigned ask_size = 0;\n     +\tunsigned ask_type = 0;\n    @@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n      \tsize_t wanted;\n      \n      \tresults->nr = oids->nr;\n    -@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n    +@@ fetch-object-info.c: void fetch_object_info(const enum protocol_version version,\n      \t\t    server_supports_feature(\"object-info\", \"size\", 0))\n      \t\t\task_size = 1;\n      \n    @@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n      \t\tbreak;\n      \tcase protocol_v1:\n      \tcase protocol_v0:\n    -@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n    +@@ fetch-object-info.c: void fetch_object_info(const enum protocol_version version,\n      \tcase protocol_unknown_version:\n      \t\tBUG(\"unknown protocol version\");\n      \t}\n    @@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n      \n      \tfor (size_t i = 0; i < wanted; i++) {\n      \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n    -@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n    +@@ fetch-object-info.c: void fetch_object_info(const enum protocol_version version,\n      \t\t\t\tdie(_(\"object-info: duplicate 'size' attribute\"));\n      \t\t\tsize_index = (int)i;\n      \t\t\tCALLOC_ARRAY(results->sizes, results->nr);\n    @@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n      \t\t} else {\n      \t\t\tdie(_(\"object-info: unknown attribute '%s'\"),\n      \t\t\t    reader->line);\n    -@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n    +@@ fetch-object-info.c: void fetch_object_info(const enum protocol_version version,\n      \t\t\t    object_info_values.items[0].string,\n      \t\t\t    object_info_values.items[size_index + 1].string);\n      \n    @@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n      \t\tstring_list_clear(&object_info_values, 0);\n      \t}\n      \n    -@@ fetch-object-info.c: void fetch_object_info(enum protocol_version version,\n    +@@ fetch-object-info.c: void fetch_object_info(const enum protocol_version version,\n      void free_fetch_object_info_results(struct fetch_object_info_results *results)\n      {\n      \tfree(results->sizes);\n 8:  0964658eb6 =  9:  76f52c455b serve: advertise type capability\n 9:  77a193bf5c = 10:  e5cebc08f0 cat-file: unify default format\n\n---\nbase-commit: 5b2471720c93ee30e5764a19f3d3b3ae9ec9712a\nchange-id: 20260724-objecttype-support-ea1ef6941d07\n\n"},{"id":"550058","messageId":"20260808-objecttype-support-v5-1-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"[PATCH GSoC v5 01/10] t5701: use test_file_size() to get the size of a file","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:06:56Z","receivedAt":"2026-08-07T22:07:13Z","isPatch":true,"body":"The 'basics of object-info' test runs 'wc -c | xargs' twice to get the\nsize of two.t. The pipe to xargs is only there to strip the blanks\nthat some platforms pad the output of wc with.\n\nUse the test_file_size() helper, which outputs the size directly, and\nstore the result in a variable. Because 'git rev-parse two:two.t' is\nalso run multiple times, store its output in a variable as well.\n\nStoring them in variables outside the HERE-document has the added\nbenefit of preserving their exit statuses.\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 t/t5701-git-serve.sh | 11 +++++++----\n 1 file changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 9a575aa098..51d5dd1ae6 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -344,20 +344,23 @@ test_expect_success 'unexpected lines are not allowed in fetch request' '\n test_expect_success 'basics of object-info' '\n \ttest_config transfer.advertiseObjectInfo true &&\n \n+\ttwo_oid=$(git rev-parse two:two.t) &&\n+\ttwo_size=$(test_file_size two.t) &&\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 $(git rev-parse two:two.t)\n-\toid $(git rev-parse two:two.t)\n+\toid $two_oid\n+\toid $two_oid\n \t0000\n \tEOF\n \n \tcat >expect <<-EOF &&\n \tsize\n-\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n-\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n+\t$two_oid $two_size\n+\t$two_oid $two_size\n \t0000\n \tEOF\n \n\n-- \n2.54.0\n\n"},{"id":"550059","messageId":"20260808-objecttype-support-v5-2-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"[PATCH GSoC v5 02/10] fetch-object-info: detect malformed server responses","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:06:57Z","receivedAt":"2026-08-07T22:07:16Z","isPatch":true,"body":"The loop reading the object-info response stops as soon as the reader\nreturns something other than PACKET_READ_NORMAL, or once it has read as\nmany lines as we requested. Neither end is checked.\n\nA server that answers with fewer objects leaves the end of the result\narrays empty, and the caller trusts that every requested object was\nfilled in. A server that answers with more leaves the extra packets\nunread. On stateless transports check_stateless_delimiter() notices, but\non the others it passes unnoticed.\n\nCheck both limits by extracting the packet_reader_read() from the loop\ncondition, so the loop no longer consumes the last packet (flush). If\nwhile looping the read is different from a PACKET_READ_NORMAL, die()\nmeaning there are fewer objects than expected. After iterating, we only\nexpect a flush, so if the last packet is not a flush, die().\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\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 fetch-object-info.c | 14 ++++++++++----\n 1 file changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex ba7e179c44..c2d4bf9403 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -106,12 +106,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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+\tfor (size_t i = 0; i < args->oids->nr; i++) {\n \t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n \n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n+\t\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n+\n \t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n \n \t\tif (strcmp(object_info_values.items[0].string,\n@@ -150,6 +151,11 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \n \t\tstring_list_clear(&object_info_values, 0);\n \t}\n+\n+\tif (packet_reader_read(reader) != PACKET_READ_FLUSH)\n+\t\tdie(_(\"object-info: expected flush after %\"PRIuMAX\" objects\"),\n+\t\t    (uintmax_t)args->oids->nr);\n+\n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n \n \treturn 0;\n\n-- \n2.54.0\n\n"},{"id":"550060","messageId":"20260808-objecttype-support-v5-3-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"[PATCH GSoC v5 03/10] fetch-object-info: pass arguments directly instead of a struct","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:06:58Z","receivedAt":"2026-08-07T22:07:18Z","isPatch":true,"body":"struct object_info_args groups three pointers that already live in the\ntransport and are given to fetch_object_info().\nGrouping them into a struct reduces the number of parameters, but it\nsuggests that the three belong together, when they are unrelated and end\nup being accessed as args->* independently.\n\nDrop the struct and pass those parameters directly to\nfetch_object_info() and send_object_info_request(). This should have no\nchange in behavior.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Junio C Hamano <gitster@pobox.com>\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 fetch-object-info.c | 53 +++++++++++++++++++++++++++++++----------------------\n fetch-object-info.h | 17 ++++++++---------\n transport.c         | 11 +++++------\n 3 files changed, 44 insertions(+), 37 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex c2d4bf9403..531610e6f4 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -9,20 +9,24 @@\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+static void send_object_info_request(const int fd_out,\n+\t\t\t\t     const struct string_list *server_options,\n+\t\t\t\t     struct oid_array *oids,\n+\t\t\t\t     struct string_list *object_info_options)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", server_options);\n \n-\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\tif (unsorted_string_list_has_string(object_info_options, \"size\"))\n \t\tpacket_buf_write(&req_buf, \"size\");\n-\telse if (args->object_info_options->nr)\n+\telse if (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+\tif (oids)\n+\t\tfor (size_t i = 0; i < oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\",\n+\t\t\t\t\t oid_to_hex(&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@@ -45,8 +49,12 @@ static int parse_object_size(const char *s, size_t *res)\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+int fetch_object_info(const enum protocol_version version,\n+\t\t      const struct string_list *server_options,\n+\t\t      struct oid_array *oids,\n+\t\t      struct string_list *object_info_options,\n+\t\t      struct packet_reader *reader,\n+\t\t      struct object_info *object_info_data,\n \t\t      const int stateless_rpc, const int fd_out)\n {\n \tint size_index = -1;\n@@ -64,16 +72,17 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tfor (int i = (int)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\t\t\t\t     object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(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\tsend_object_info_request(fd_out, server_options, oids,\n+\t\t\t\t\t object_info_options);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -82,14 +91,14 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\tBUG(\"unknown protocol version\");\n \t}\n \n-\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\tfor (size_t i = 0; i < 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 (!unsorted_string_list_has_string(args->object_info_options, reader->line))\n+\t\tif (!unsorted_string_list_has_string(object_info_options, reader->line))\n \t\t\treturn -1;\n \n \t\tif (!strcmp(reader->line, \"size\")) {\n@@ -98,7 +107,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tfor (size_t j = 0; j < 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@@ -106,19 +115,19 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\t}\n \t}\n \n-\tfor (size_t i = 0; i < args->oids->nr; i++) {\n+\tfor (size_t i = 0; i < oids->nr; i++) {\n \t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n \n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n-\t\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n+\t\t\t    (uintmax_t)oids->nr, (uintmax_t)i);\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\t   oid_to_hex(&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    oid_to_hex(&oids->oid[i]),\n \t\t\t    object_info_values.items[0].string);\n \n \t\t/*\n@@ -138,7 +147,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tif (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@@ -154,7 +163,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \n \tif (packet_reader_read(reader) != PACKET_READ_FLUSH)\n \t\tdie(_(\"object-info: expected flush after %\"PRIuMAX\" objects\"),\n-\t\t    (uintmax_t)args->oids->nr);\n+\t\t    (uintmax_t)oids->nr);\n \n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n \ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 269cebb3f7..316bf917ce 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -4,22 +4,21 @@\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+struct oid_array;\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+ * Modifies 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,\n+int fetch_object_info(enum protocol_version version,\n+\t\t      const struct string_list *server_options,\n+\t\t      struct oid_array *oids,\n+\t\t      struct string_list *object_info_options,\n+\t\t      struct packet_reader *reader,\n+\t\t      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/transport.c b/transport.c\nindex 42b0a2d0bd..2bb5eedc32 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -438,11 +438,6 @@ static int fetch_object_info_via_pack(struct transport *transport)\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 \n \tconnect_setup(transport, 0);\n \tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n@@ -453,7 +448,11 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tdata->version = discover_version(&reader);\n \ttransport->hash_algo = reader.hash_algo;\n \n-\tret = fetch_object_info(data->version, &args, &reader,\n+\tret = fetch_object_info(data->version,\n+\t\t\t\ttransport->server_options,\n+\t\t\t\ttransport->smart_options->object_info_oids,\n+\t\t\t\ttransport->smart_options->object_info_options,\n+\t\t\t\t&reader,\n \t\t\t\tdata->options.object_info_data,\n \t\t\t\ttransport->stateless_rpc, data->fd[1]);\n \n\n-- \n2.54.0\n\n"},{"id":"550061","messageId":"20260808-objecttype-support-v5-4-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"[PATCH GSoC v5 04/10] fetch-object-info: use dedicated struct for the results","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:06:59Z","receivedAt":"2026-08-07T22:07:20Z","isPatch":true,"body":"fetch_object_info() collects information about N objects, but it stores\nthe results in an array of object_info. That struct holds the extended\nparameters of read_object_info() (The optional outputs the caller wants\nfilled). Its pointers tell that function where to write the answers for\na single object. object_info is not meant to be the final storage, and\nsince fetch_object_info() does not call read_object_info(), there is no\nreason to use it. Using it means allocating one scalar per object per\nattribute just to have those pointers somewhere to point at.\n\nAdd struct fetch_object_info_results. The caller sets the wants_* flags\nto say what it is interested in, and fetch_object_info() allocates one\narray per attribute. A set wants_* flag means \"asked for\", while a\nnon-NULL array means \"available\". The caller releases the arrays with\nfree_fetch_object_info_results().\n\nThe object_info_options string list is no longer needed. Filtering\nagainst the server's advertisement now sets local ask_* flags, and\nsend_object_info_request() turns those into the v2 protocol option\nstrings. remote_atom_map[] existed only to map those strings back into\natom names, so drop it and build remote_allowed_atoms from the result\narrays.\n\nCurrently for wants_* and ask_* there is only the 'size' variant but a\nsubsequent commit will add '*_type'.\n\nfree_object_info_contents() loses its only caller and is dropped.\n\nDropping the allow-list check makes the final else reachable from the\nwire, so die() instead of BUG(): an unknown attribute is the server's\nerror, not ours.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Junio C Hamano <gitster@pobox.com>\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  | 59 +++++++++-----------------------------\n fetch-object-info.c | 81 ++++++++++++++++++++++++++---------------------------\n fetch-object-info.h | 27 +++++++++++++-----\n object-file.c       | 10 -------\n odb.h               |  3 --\n transport.c         |  3 +-\n transport.h         |  5 ++--\n 7 files changed, 77 insertions(+), 111 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 884b6d5ad3..e1650b2921 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -31,6 +31,7 @@\n #include \"alias.h\"\n #include \"remote.h\"\n #include \"transport.h\"\n+#include \"fetch-object-info.h\"\n \n /*\n  * Maximum length for a remote URL. While no universal standard exists,\n@@ -681,9 +682,8 @@ static void batch_one_object(const char *obj_name,\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-\t\t\t   struct string_list *object_info_options)\n+\t\t\t   struct fetch_object_info_results *results,\n+\t\t\t   struct oid_array *object_info_oids)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n@@ -724,11 +724,9 @@ static int get_remote_info(int argc,\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-\tgtransport->smart_options->object_info_options = object_info_options;\n-\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\tgtransport->smart_options->object_info_results = results;\n \tretval = transport_fetch_object_info(gtransport);\n cleanup:\n \ttransport_disconnect(gtransport);\n@@ -816,21 +814,6 @@ 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@@ -838,9 +821,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tint count;\n \tconst char **argv;\n \tchar *line_to_split;\n-\tstruct object_info *remote_object_info = NULL;\n+\tstruct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;\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@@ -861,26 +843,21 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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+\t\tresults.wants_size = 1;\n \n-\tif (get_remote_info(count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids, &object_info_options))\n+\tif (get_remote_info(count, argv, &results, &object_info_oids))\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+\tif (results.sizes)\n+\t\tstring_list_append(&data->remote_allowed_atoms, \"objectsize\");\n \n \tdata->skip_object_info = 1;\n-\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\tfor (size_t i = 0; i < results.nr; i++) {\n \t\tdata->oid = object_info_oids.oid[i];\n \n-\t\tif (remote_object_info[i].unrecognized) {\n+\t\tif (results.unrecognized[i]) {\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@@ -890,13 +867,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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}\n-\n-\t\tif (remote_object_info[i].typep) {\n-\t\t\tdata->type = *remote_object_info[i].typep;\n-\t\t}\n+\t\tif (results.sizes)\n+\t\t\tdata->size = results.sizes[i];\n \n \t\topt->batch_mode = BATCH_MODE_INFO;\n \t\tdata->is_remote = 1;\n@@ -906,12 +878,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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_fetch_object_info_results(&results);\n \tfree(line_to_split);\n \tfree(argv);\n-\tfree(remote_object_info);\n \toid_array_clear(&object_info_oids);\n }\n \ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 531610e6f4..46a1289ff3 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -12,16 +12,14 @@\n static void send_object_info_request(const int fd_out,\n \t\t\t\t     const struct string_list *server_options,\n \t\t\t\t     struct oid_array *oids,\n-\t\t\t\t     struct string_list *object_info_options)\n+\t\t\t\t     unsigned ask_size)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n \twrite_command_and_capabilities(&req_buf, \"object-info\", server_options);\n \n-\tif (unsorted_string_list_has_string(object_info_options, \"size\"))\n+\tif (ask_size)\n \t\tpacket_buf_write(&req_buf, \"size\");\n-\telse if (object_info_options->nr)\n-\t\tBUG(\"only size should be in object_info_options\");\n \n \tif (oids)\n \t\tfor (size_t i = 0; i < oids->nr; i++)\n@@ -52,37 +50,32 @@ static int parse_object_size(const char *s, size_t *res)\n int fetch_object_info(const enum protocol_version version,\n \t\t      const struct string_list *server_options,\n \t\t      struct oid_array *oids,\n-\t\t      struct string_list *object_info_options,\n \t\t      struct packet_reader *reader,\n-\t\t      struct object_info *object_info_data,\n-\t\t      const int stateless_rpc, const int fd_out)\n+\t\t      struct fetch_object_info_results *results,\n+\t\t      const int stateless_rpc,\n+\t\t      const int fd_out)\n {\n+\tunsigned ask_size = 0;\n \tint size_index = -1;\n+\tsize_t wanted;\n+\n+\tresults->nr = oids->nr;\n+\tCALLOC_ARRAY(results->unrecognized, results->nr);\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\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)object_info_options->nr - 1; i >= 0; i--)\n-\t\t\tif (!server_supports_feature(\"object-info\",\n-\t\t\t\t\t\t     object_info_options->items[i].string, 0))\n-\t\t\t\tunsorted_string_list_delete_item(object_info_options, i, 0);\n+\n+\t\tif (results->wants_size &&\n+\t\t    server_supports_feature(\"object-info\", \"size\", 0))\n+\t\t\task_size = 1;\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, server_options, oids,\n-\t\t\t\t\t object_info_options);\n+\t\tsend_object_info_request(fd_out, server_options, oids, ask_size);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -90,28 +83,25 @@ int fetch_object_info(const enum protocol_version version,\n \tcase protocol_unknown_version:\n \t\tBUG(\"unknown protocol version\");\n \t}\n+\twanted = ask_size;\n \n-\tfor (size_t i = 0; i < object_info_options->nr; i++) {\n+\tfor (size_t i = 0; i < wanted; 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 (!unsorted_string_list_has_string(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\tif (!ask_size)\n+\t\t\t\tdie(_(\"object-info: unrequested 'size' attribute\"));\n+\t\t\tif (results->sizes)\n+\t\t\t\tdie(_(\"object-info: duplicate 'size' attribute\"));\n \t\t\tsize_index = (int)i;\n-\t\t\tfor (size_t j = 0; j < 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\tCALLOC_ARRAY(results->sizes, results->nr);\n \t\t} else {\n-\t\t\tBUG(\"only size is supported\");\n+\t\t\tdie(_(\"object-info: unknown attribute '%s'\"),\n+\t\t\t    reader->line);\n \t\t}\n \t}\n \n@@ -137,24 +127,24 @@ int fetch_object_info(const enum protocol_version version,\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\tresults->unrecognized[i] = 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 * Because we only ask for attributes the server said it\n+\t\t * supports, we expect the answer to have one value per\n+\t\t * requested attribute, plus the OID.\n \t\t */\n-\t\tif (object_info_options->nr + 1 != object_info_values.nr)\n+\t\tif (wanted + 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\tif (results->sizes &&\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\t      &results->sizes[i]))\n+\t\t\tdie(\"object-info: object %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@@ -169,3 +159,10 @@ int fetch_object_info(const enum protocol_version version,\n \n \treturn 0;\n }\n+\n+void free_fetch_object_info_results(struct fetch_object_info_results *results)\n+{\n+\tfree(results->sizes);\n+\tfree(results->unrecognized);\n+\tmemset(results, 0, sizeof(*results));\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 316bf917ce..9f72e91155 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -4,21 +4,34 @@\n #include \"pkt-line.h\"\n #include \"protocol.h\"\n \n-struct object_info;\n+struct fetch_object_info_results {\n+\tsize_t *sizes;\n+\tuint8_t *unrecognized;\n+\tsize_t nr;\n+\tunsigned wants_size:1;\n+};\n+\n+#define FETCH_OBJECT_INFO_RESULTS_INIT { 0 }\n+\n struct oid_array;\n /*\n- * Sends git-cat-file object-info command into the request buf and read the\n+ * Sends git-cat-file object-info command into the request buf and reads the\n  * results from packets.\n  *\n- * Modifies object_info_options, on return it contains only the supported\n- * options by the server.\n+ * The caller sets the wants_* flags in \"results\" to indicate which attributes\n+ * it is interested in. On return, \"results\" holds one array per attribute that\n+ * the server both advertised and answered with. An array left NULL means the\n+ * attribute is not available.\n+ * Release them with free_fetch_object_info_results().\n  */\n int fetch_object_info(enum protocol_version version,\n \t\t      const struct string_list *server_options,\n \t\t      struct oid_array *oids,\n-\t\t      struct string_list *object_info_options,\n \t\t      struct packet_reader *reader,\n-\t\t      struct object_info *object_info_data,\n-\t\t      int stateless_rpc, int fd_out);\n+\t\t      struct fetch_object_info_results *results,\n+\t\t      int stateless_rpc,\n+\t\t      int fd_out);\n+\n+void free_fetch_object_info_results(struct fetch_object_info_results *results);\n \n #endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/object-file.c b/object-file.c\nindex ec35c318bc..66f6c81373 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1340,13 +1340,3 @@ 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 cbc2f9ced4..f4e6c26a0e 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -637,7 +637,4 @@ 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/transport.c b/transport.c\nindex 2bb5eedc32..b0e29124d1 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -451,9 +451,8 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tret = fetch_object_info(data->version,\n \t\t\t\ttransport->server_options,\n \t\t\t\ttransport->smart_options->object_info_oids,\n-\t\t\t\ttransport->smart_options->object_info_options,\n \t\t\t\t&reader,\n-\t\t\t\tdata->options.object_info_data,\n+\t\t\t\tdata->options.object_info_results,\n \t\t\t\ttransport->stateless_rpc, data->fd[1]);\n \n \tclose(data->fd[0]);\ndiff --git a/transport.h b/transport.h\nindex a7869d18e0..6948b65db9 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -7,6 +7,8 @@\n #include \"string-list.h\"\n #include \"connect.h\"\n \n+struct fetch_object_info_results;\n+\n struct git_transport_options {\n \tunsigned thin : 1;\n \tunsigned keep : 1;\n@@ -57,8 +59,7 @@ 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 string_list *object_info_options;\n+\tstruct fetch_object_info_results *object_info_results;\n };\n \n enum transport_family {\n\n-- \n2.54.0\n\n"},{"id":"550062","messageId":"20260808-objecttype-support-v5-5-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"[PATCH GSoC v5 05/10] fetch-object-info: die() on the remaining error path","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:07:00Z","receivedAt":"2026-08-07T22:07:22Z","isPatch":true,"body":"Every failure in fetch_object_info() dies except one: a short read while\nparsing the attribute lines returns -1. That -1 is then passed through\nfetch_object_info_via_pack() and get_remote_info() up to cat-file, only\nto die() with a generic message.\n\nDie in fetch_object_info() instead, consistently with the rest of its\nerror paths, and make fetch_object_info() void.\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 fetch-object-info.c | 19 +++++++++----------\n fetch-object-info.h | 14 +++++++-------\n transport.c         | 12 ++++++------\n 3 files changed, 22 insertions(+), 23 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 46a1289ff3..7a337a6857 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -47,13 +47,13 @@ static int parse_object_size(const char *s, size_t *res)\n \treturn 0;\n }\n \n-int fetch_object_info(const enum protocol_version version,\n-\t\t      const struct string_list *server_options,\n-\t\t      struct oid_array *oids,\n-\t\t      struct packet_reader *reader,\n-\t\t      struct fetch_object_info_results *results,\n-\t\t      const int stateless_rpc,\n-\t\t      const int fd_out)\n+void fetch_object_info(const enum protocol_version version,\n+\t\t       const struct string_list *server_options,\n+\t\t       struct oid_array *oids,\n+\t\t       struct packet_reader *reader,\n+\t\t       struct fetch_object_info_results *results,\n+\t\t       const int stateless_rpc,\n+\t\t       const int fd_out)\n {\n \tunsigned ask_size = 0;\n \tint size_index = -1;\n@@ -89,7 +89,8 @@ int fetch_object_info(const enum protocol_version version,\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\tdie(_(\"object-info: expected %\" PRIuMAX \" attributes, got %\" PRIuMAX),\n+\t\t\t    (uintmax_t)wanted, (uintmax_t)i);\n \t\t}\n \n \t\tif (!strcmp(reader->line, \"size\")) {\n@@ -156,8 +157,6 @@ int fetch_object_info(const enum protocol_version version,\n \t\t    (uintmax_t)oids->nr);\n \n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n-\n-\treturn 0;\n }\n \n void free_fetch_object_info_results(struct fetch_object_info_results *results)\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 9f72e91155..97ee5314c9 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -24,13 +24,13 @@ struct oid_array;\n  * attribute is not available.\n  * Release them with free_fetch_object_info_results().\n  */\n-int fetch_object_info(enum protocol_version version,\n-\t\t      const struct string_list *server_options,\n-\t\t      struct oid_array *oids,\n-\t\t      struct packet_reader *reader,\n-\t\t      struct fetch_object_info_results *results,\n-\t\t      int stateless_rpc,\n-\t\t      int fd_out);\n+void fetch_object_info(enum protocol_version version,\n+\t\t       const struct string_list *server_options,\n+\t\t       struct oid_array *oids,\n+\t\t       struct packet_reader *reader,\n+\t\t       struct fetch_object_info_results *results,\n+\t\t       int stateless_rpc,\n+\t\t       int fd_out);\n \n void free_fetch_object_info_results(struct fetch_object_info_results *results);\n \ndiff --git a/transport.c b/transport.c\nindex b0e29124d1..35acdf71a2 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -448,12 +448,12 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tdata->version = discover_version(&reader);\n \ttransport->hash_algo = reader.hash_algo;\n \n-\tret = fetch_object_info(data->version,\n-\t\t\t\ttransport->server_options,\n-\t\t\t\ttransport->smart_options->object_info_oids,\n-\t\t\t\t&reader,\n-\t\t\t\tdata->options.object_info_results,\n-\t\t\t\ttransport->stateless_rpc, data->fd[1]);\n+\tfetch_object_info(data->version,\n+\t\t\t  transport->server_options,\n+\t\t\t  transport->smart_options->object_info_oids,\n+\t\t\t  &reader,\n+\t\t\t  data->options.object_info_results,\n+\t\t\t  transport->stateless_rpc, data->fd[1]);\n \n \tclose(data->fd[0]);\n \tif (data->fd[1] >= 0)\n\n-- \n2.54.0\n\n"},{"id":"550063","messageId":"20260808-objecttype-support-v5-6-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"[PATCH GSoC v5 06/10] transport: drop remote object-info fields from transport struct","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:07:01Z","receivedAt":"2026-08-07T22:07:23Z","isPatch":true,"body":"From: Jeff King <peff@peff.net>\n\nA remote object-info request needs three things: the transport for\ncontacting the remote, the list of oids to request, and a place to store\nthe output.\n\nRather than take these as function parameters, we take only the\ntransport object, and expect the caller to have placed the other two\ninto special fields in the transport struct. But this doesn't make much\nsense. The set of oids and results are really only valid for one\nrequest. There is no reason the transport would need to hang on to them\noutside of the single function call.\n\nEven though we save a few lines passing the parameters around through\nthe various vtable functions, the result is harder to understand (for\nexample, who is responsible for cleaning up results, and when should it\nhappen?). It also opens up the possibility of a subtle bug. A caller is\nlikely to point those fields to stack variables which could go out of\nscope, and the transport struct would be left holding invalid pointers.\n\nThis is mostly harmless now, as we disconnect the transport immediately\nafter the sole caller of transport_fetch_object_info(). But conceptually\nwe could keep the transport open and make multiple fetch calls (and\nreuse the same connection to the helper, to a remote HTTP server, and so\non).\n\nSo let's pull these out of the struct and pass them as function\nparameters. It's a little more verbose, but I think more clearly\nillustrates the intent. I've also tweaked a few function signatures to\nmark the input oid array as const, since it is purely an input to the\nfunction.\n\nSigned-off-by: Jeff King <peff@peff.net>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c   |  6 ++----\n fetch-object-info.c  |  4 ++--\n fetch-object-info.h  |  2 +-\n transport-helper.c   |  7 +++++--\n transport-internal.h |  4 +++-\n transport.c          | 14 +++++++++-----\n transport.h          |  7 +++----\n 7 files changed, 25 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex e1650b2921..8dcad2f5eb 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -724,10 +724,8 @@ static int get_remote_info(int argc,\n \t\tgoto cleanup;\n \t}\n \n-\tgtransport->smart_options->object_info_oids = object_info_oids;\n-\n-\tgtransport->smart_options->object_info_results = results;\n-\tretval = transport_fetch_object_info(gtransport);\n+\tretval = transport_fetch_object_info(gtransport, object_info_oids,\n+\t\t\t\t\t     results);\n cleanup:\n \ttransport_disconnect(gtransport);\n \treturn retval;\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 7a337a6857..f269a5d8a8 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -11,7 +11,7 @@\n /* Sends object-info command and its arguments into the request buffer. */\n static void send_object_info_request(const int fd_out,\n \t\t\t\t     const struct string_list *server_options,\n-\t\t\t\t     struct oid_array *oids,\n+\t\t\t\t     const struct oid_array *oids,\n \t\t\t\t     unsigned ask_size)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n@@ -49,7 +49,7 @@ static int parse_object_size(const char *s, size_t *res)\n \n void fetch_object_info(const enum protocol_version version,\n \t\t       const struct string_list *server_options,\n-\t\t       struct oid_array *oids,\n+\t\t       const struct oid_array *oids,\n \t\t       struct packet_reader *reader,\n \t\t       struct fetch_object_info_results *results,\n \t\t       const int stateless_rpc,\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 97ee5314c9..10cf9f5f63 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -26,7 +26,7 @@ struct oid_array;\n  */\n void fetch_object_info(enum protocol_version version,\n \t\t       const struct string_list *server_options,\n-\t\t       struct oid_array *oids,\n+\t\t       const struct oid_array *oids,\n \t\t       struct packet_reader *reader,\n \t\t       struct fetch_object_info_results *results,\n \t\t       int stateless_rpc,\ndiff --git a/transport-helper.c b/transport-helper.c\nindex b109fbd8c3..e729a097de 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -784,11 +784,14 @@ static int fetch_refs(struct transport *transport,\n \treturn -1;\n }\n \n-static int fetch_object_info_helper(struct transport *transport)\n+static int fetch_object_info_helper(struct transport *transport,\n+\t\t\t\t    const struct oid_array *oids,\n+\t\t\t\t    struct fetch_object_info_results *results)\n {\n \tget_helper(transport);\n \tif (process_connect(transport, 0))\n-\t\treturn transport->vtable->fetch_object_info(transport);\n+\t\treturn transport->vtable->fetch_object_info(transport, oids,\n+\t\t\t\t\t\t\t    results);\n \n \tdie(_(\"object-info requires protocol v2\"));\n }\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 60db0bedcd..e7ead5d785 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -51,7 +51,9 @@ struct transport_vtable {\n \t *\n \t * Uses object-info capability of v2 protocol.\n \t */\n-\tint (*fetch_object_info)(struct transport *transport);\n+\tint (*fetch_object_info)(struct transport *transport,\n+\t\t\t\t const struct oid_array *oids,\n+\t\t\t\t struct fetch_object_info_results *results);\n \n \t/**\n \t * Push the objects and refs. Send the necessary objects, and\ndiff --git a/transport.c b/transport.c\nindex 35acdf71a2..25e2c14a7b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -433,7 +433,9 @@ 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+static int fetch_object_info_via_pack(struct transport *transport,\n+\t\t\t\t      const struct oid_array *oids,\n+\t\t\t\t      struct fetch_object_info_results *results)\n {\n \tint ret = 0;\n \tstruct git_transport_data *data = transport->data;\n@@ -450,9 +452,9 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \n \tfetch_object_info(data->version,\n \t\t\t  transport->server_options,\n-\t\t\t  transport->smart_options->object_info_oids,\n+\t\t\t  oids,\n \t\t\t  &reader,\n-\t\t\t  data->options.object_info_results,\n+\t\t\t  results,\n \t\t\t  transport->stateless_rpc, data->fd[1]);\n \n \tclose(data->fd[0]);\n@@ -465,11 +467,13 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \treturn ret;\n }\n \n-int transport_fetch_object_info(struct transport *transport)\n+int transport_fetch_object_info(struct transport *transport,\n+\t\t\t\tconst struct oid_array *oids,\n+\t\t\t\tstruct fetch_object_info_results *results)\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+\treturn transport->vtable->fetch_object_info(transport, oids, results);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\ndiff --git a/transport.h b/transport.h\nindex 6948b65db9..39193d0077 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -57,9 +57,6 @@ 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 fetch_object_info_results *object_info_results;\n };\n \n enum transport_family {\n@@ -317,7 +314,9 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs);\n /*\n  * Fetch the object info from remote\n  */\n-int transport_fetch_object_info(struct transport *transport);\n+int transport_fetch_object_info(struct transport *transport,\n+\t\t\t\tconst struct oid_array *oids,\n+\t\t\t\tstruct fetch_object_info_results *results);\n \n /*\n  * If this flag is set, unlocking will avoid to call non-async-signal-safe\n\n-- \n2.54.0\n\n"},{"id":"550064","messageId":"20260808-objecttype-support-v5-7-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"[PATCH GSoC v5 07/10] protocol-caps: add type support to object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:07:02Z","receivedAt":"2026-08-07T22:07:26Z","isPatch":true,"body":"Teach the server-side object-info handler to accept type as a requested\nfield. When the client includes type in its object-info request, the\nserver returns the requested object type.\n\nWhile touching send_info(), wrap an over-long line and fix the bit field\nstyle of requested_info.size.\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 protocol-caps.c      | 21 ++++++++++++++++++---\n t/t5701-git-serve.sh | 30 ++++++++++++++++++++++++++++++\n 2 files changed, 48 insertions(+), 3 deletions(-)\n\ndiff --git a/protocol-caps.c b/protocol-caps.c\nindex 02261be14d..27e0f85b10 100644\n--- a/protocol-caps.c\n+++ b/protocol-caps.c\n@@ -11,7 +11,8 @@\n #include \"strbuf.h\"\n \n struct requested_info {\n-\tunsigned size : 1;\n+\tunsigned size:1;\n+\tunsigned type:1;\n };\n \n /*\n@@ -73,15 +74,20 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \tif (info->size)\n \t\tpacket_writer_write(writer, \"size\");\n \n+\tif (info->type)\n+\t\tpacket_writer_write(writer, \"type\");\n+\n \tfor_each_string_list_item (item, oid_str_list) {\n \t\tconst char *oid_str = item->string;\n+\t\tenum object_type object_type;\n \t\tstruct object_id oid;\n \t\tsize_t object_size;\n \n \t\tif (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n \t\t\tpacket_writer_error(\n \t\t\t\twriter,\n-\t\t\t\t\"object-info: protocol error, expected to get oid, not '%s'\",\n+\t\t\t\t\"object-info: protocol error, expected to get \"\n+\t\t\t\t\"oid, not '%s'\",\n \t\t\t\toid_str);\n \t\t\tcontinue;\n \t\t}\n@@ -93,7 +99,8 @@ static void send_info(struct repository *r, struct packet_writer *writer,\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\tobject_type = get_object_info(r->objects, &oid, &object_size);\n+\t\tif (object_type <= OBJ_NONE) {\n \t\t\tstrbuf_addstr(&send_buffer, \" \");\n \t\t\tgoto write;\n \t\t}\n@@ -103,6 +110,9 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \t\t\t\t    (uintmax_t)object_size);\n \t\t}\n \n+\t\tif (info->type)\n+\t\t\tstrbuf_addf(&send_buffer, \" %s\", type_name(object_type));\n+\n write:\n \t\tpacket_writer_write(writer, \"%s\", send_buffer.buf);\n \t\tstrbuf_reset(&send_buffer);\n@@ -124,6 +134,11 @@ int cap_object_info(struct repository *r, struct packet_reader *request)\n \t\t\tcontinue;\n \t\t}\n \n+\t\tif (!strcmp(\"type\", request->line)) {\n+\t\t\tinfo.type = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif (parse_oid(request->line, &oid_str_list))\n \t\t\tcontinue;\n \ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 51d5dd1ae6..f57e36a88d 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -369,6 +369,36 @@ test_expect_success 'basics of object-info' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'object-info supports type' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttwo_oid=$(git rev-parse two:two.t) &&\n+\ttwo_size=$(test_file_size two.t) &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\tsize\n+\ttype\n+\toid $two_oid\n+\toid $two_oid\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tsize\n+\ttype\n+\t$two_oid $two_size blob\n+\t$two_oid $two_size blob\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 'bare OID request' '\n \ttest_config transfer.advertiseObjectInfo true &&\n \n\n-- \n2.54.0\n\n"},{"id":"550065","messageId":"20260808-objecttype-support-v5-8-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"[PATCH GSoC v5 08/10] fetch-object-info: parse type from server response","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:07:03Z","receivedAt":"2026-08-07T22:07:28Z","isPatch":true,"body":"The server can handle type requests but does not advertise the\ncapability yet. Prepare the client to know how to parse the server\nresponse once the server advertises the type capability.\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  |  7 +++++++\n fetch-object-info.c | 38 +++++++++++++++++++++++++++++++++++---\n fetch-object-info.h |  3 +++\n 3 files changed, 45 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 8dcad2f5eb..8502020083 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -842,6 +842,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \n \tif (data->info.sizep)\n \t\tresults.wants_size = 1;\n+\tif (data->info.typep)\n+\t\tresults.wants_type = 1;\n \n \tif (get_remote_info(count, argv, &results, &object_info_oids))\n \t\tdie(_(\"failed to get object info from the remote: %s\"), argv[0]);\n@@ -850,6 +852,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n \tif (results.sizes)\n \t\tstring_list_append(&data->remote_allowed_atoms, \"objectsize\");\n+\tif (results.types)\n+\t\tstring_list_append(&data->remote_allowed_atoms, \"objecttype\");\n \n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < results.nr; i++) {\n@@ -868,6 +872,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tif (results.sizes)\n \t\t\tdata->size = results.sizes[i];\n \n+\t\tif (results.types)\n+\t\t\tdata->type = results.types[i];\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);\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex f269a5d8a8..ba0540853e 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n+#include \"object.h\"\n #include \"pkt-line.h\"\n #include \"connect.h\"\n #include \"oid-array.h\"\n@@ -12,7 +13,8 @@\n static void send_object_info_request(const int fd_out,\n \t\t\t\t     const struct string_list *server_options,\n \t\t\t\t     const struct oid_array *oids,\n-\t\t\t\t     unsigned ask_size)\n+\t\t\t\t     unsigned ask_size,\n+\t\t\t\t     unsigned ask_type)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n@@ -21,6 +23,9 @@ static void send_object_info_request(const int fd_out,\n \tif (ask_size)\n \t\tpacket_buf_write(&req_buf, \"size\");\n \n+\tif (ask_type)\n+\t\tpacket_buf_write(&req_buf, \"type\");\n+\n \tif (oids)\n \t\tfor (size_t i = 0; i < oids->nr; i++)\n \t\t\tpacket_buf_write(&req_buf, \"oid %s\",\n@@ -56,7 +61,9 @@ void fetch_object_info(const enum protocol_version version,\n \t\t       const int fd_out)\n {\n \tunsigned ask_size = 0;\n+\tunsigned ask_type = 0;\n \tint size_index = -1;\n+\tint type_index = -1;\n \tsize_t wanted;\n \n \tresults->nr = oids->nr;\n@@ -71,11 +78,16 @@ void fetch_object_info(const enum protocol_version version,\n \t\t    server_supports_feature(\"object-info\", \"size\", 0))\n \t\t\task_size = 1;\n \n+\t\tif (results->wants_type &&\n+\t\t    server_supports_feature(\"object-info\", \"type\", 0))\n+\t\t\task_type = 1;\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, server_options, oids, ask_size);\n+\t\tsend_object_info_request(fd_out, server_options, oids, ask_size,\n+\t\t\t\t\t ask_type);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -83,7 +95,7 @@ void fetch_object_info(const enum protocol_version version,\n \tcase protocol_unknown_version:\n \t\tBUG(\"unknown protocol version\");\n \t}\n-\twanted = ask_size;\n+\twanted = ask_size + ask_type;\n \n \tfor (size_t i = 0; i < wanted; i++) {\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n@@ -100,6 +112,13 @@ void fetch_object_info(const enum protocol_version version,\n \t\t\t\tdie(_(\"object-info: duplicate 'size' attribute\"));\n \t\t\tsize_index = (int)i;\n \t\t\tCALLOC_ARRAY(results->sizes, results->nr);\n+\t\t} else if (!strcmp(reader->line, \"type\")) {\n+\t\t\tif (!ask_type)\n+\t\t\t\tdie(_(\"object-info: unrequested 'type' attribute\"));\n+\t\t\tif (results->types)\n+\t\t\t\tdie(_(\"object-info: duplicate 'type' attribute\"));\n+\t\t\ttype_index = (int)i;\n+\t\t\tCALLOC_ARRAY(results->types, results->nr);\n \t\t} else {\n \t\t\tdie(_(\"object-info: unknown attribute '%s'\"),\n \t\t\t    reader->line);\n@@ -149,6 +168,18 @@ void fetch_object_info(const enum protocol_version version,\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\tif (results->types) {\n+\t\t\tconst char *type_str =\n+\t\t\t\tobject_info_values.items[type_index + 1].string;\n+\t\t\tint type = type_from_string_gently(type_str, -1, 1);\n+\n+\t\t\tif (type < 0)\n+\t\t\t\tdie(_(\"object-info: object %s has invalid type '%s'\"),\n+\t\t\t\t    object_info_values.items[0].string, type_str);\n+\n+\t\t\tresults->types[i] = type;\n+\t\t}\n+\n \t\tstring_list_clear(&object_info_values, 0);\n \t}\n \n@@ -162,6 +193,7 @@ void fetch_object_info(const enum protocol_version version,\n void free_fetch_object_info_results(struct fetch_object_info_results *results)\n {\n \tfree(results->sizes);\n+\tfree(results->types);\n \tfree(results->unrecognized);\n \tmemset(results, 0, sizeof(*results));\n }\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 10cf9f5f63..2fba96c6f7 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -1,14 +1,17 @@\n #ifndef FETCH_OBJECT_INFO_H\n #define FETCH_OBJECT_INFO_H\n \n+#include \"object.h\"\n #include \"pkt-line.h\"\n #include \"protocol.h\"\n \n struct fetch_object_info_results {\n \tsize_t *sizes;\n+\tenum object_type *types;\n \tuint8_t *unrecognized;\n \tsize_t nr;\n \tunsigned wants_size:1;\n+\tunsigned wants_type:1;\n };\n \n #define FETCH_OBJECT_INFO_RESULTS_INIT { 0 }\n\n-- \n2.54.0\n\n"},{"id":"550066","messageId":"20260808-objecttype-support-v5-9-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"[PATCH GSoC v5 09/10] serve: advertise type capability","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:07:04Z","receivedAt":"2026-08-07T22:07:30Z","isPatch":true,"body":"The server and the client can handle type requests but the client won't\nask for it until the server advertises it.\n\nAdd type to the advertised capabilities so the client knows that it can\nrequest 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 serve.c                                |  4 ++--\n t/t1017-cat-file-remote-object-info.sh | 26 ++++++++++++++++++++++----\n 2 files changed, 24 insertions(+), 6 deletions(-)\n\ndiff --git a/serve.c b/serve.c\nindex 2b07d922b3..2ce513cf2d 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -97,9 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value)\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n-\t/* Currently only size is supported */\n+\t/* Currently only size and type are supported */\n \tif (value && advertise_object_info)\n-\t\tstrbuf_addstr(value, \"size\");\n+\t\tstrbuf_addstr(value, \"size type\");\n \treturn advertise_object_info;\n }\n \ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 116862f9d0..190c45eefc 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -7,6 +7,7 @@ test_description='git cat-file --batch-command with remote-object-info command'\n \n hello_content=\"Hello World\"\n hello_size=$(strlen \"$hello_content\")\n+hello_type=\"blob\"\n hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n \n@@ -19,6 +20,7 @@ unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdi\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+tree_type=\"tree\"\n \n commit_message=\"Initial commit\"\n \n@@ -31,6 +33,7 @@ commit_message=\"Initial commit\"\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+commit_type=\"commit\"\n \n tag_header_without_oid=\"type blob\n tag hellotag\n@@ -44,6 +47,7 @@ $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+tag_type=\"tag\"\n \n set_transport_variables () {\n \thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n@@ -256,14 +260,12 @@ 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+test_expect_success 'objecttype is supported by remote-object-info' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n \n-\t\techo \"\" >expect &&\n+\t\techo \"$hello_type\" >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@@ -271,6 +273,22 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '\n \t)\n '\n \n+test_expect_success 'unsupported placeholders on remote return empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tfmt=\"%(objectmode) %(objectsize:disk) %(rest) %(deltabase)\" &&\n+\n+\t\t# The hardcoded SPs between the atoms are respected.\n+\t\techo \"   \" >expect &&\n+\t\tgit cat-file --batch-command=\"$fmt\" >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 'requesting only objectname echoes back' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n\n-- \n2.54.0\n\n"},{"id":"550067","messageId":"20260808-objecttype-support-v5-10-86f22bec04b2@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"[PATCH GSoC v5 10/10] cat-file: unify default format","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T22:07:05Z","receivedAt":"2026-08-07T22:07:32Z","isPatch":true,"body":"%(objecttype) is supported both by the client and by the server.\nChange the temporary default format to the unified version that the\nother commands use.\n\nUpdate documentation to remove %(objecttype) from the caveats of\nremote-object-info and show %(objecttype) support.\n\nNow that type is supported and the default format unified, update the\ntests to expect the new default format.\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        | 17 +++++------\n Documentation/gitprotocol-v2.adoc      | 18 +++++++++---\n builtin/cat-file.c                     |  7 -----\n t/t1017-cat-file-remote-object-info.sh | 52 +++++++++++++++++-----------------\n 4 files changed, 47 insertions(+), 47 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex ac3b528c6f..514bfc0032 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -348,15 +348,12 @@ 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+The command `remote-object-info` only supports the `%(objectname)`,\n+`%(objectsize)` and `%(objecttype)` placeholders. See `CAVEATS` below for more\n+information.\n \n If no format is specified, the default format is `%(objectname)\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+%(objecttype) %(objectsize)`.\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@@ -453,9 +450,9 @@ 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+Note that only `%(objectname)`, `%(objectsize)` and `%(objecttype)` are\n+currently supported by the `remote-object-info` command. Using any other\n+placeholder in 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/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 7bf62014c3..dd52fd8110 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -558,14 +558,17 @@ object-info\n \n `object-info` is the command to retrieve information about one or more objects.\n Its main purpose is to allow a client to make decisions based on this\n-information without having to fully fetch objects. Object size is the only\n-information that is currently supported.\n+information without having to fully fetch objects. Currently only object size\n+and type are supported.\n \n An `object-info` request takes the following arguments:\n \n \tsize\n \tRequests size information to be returned for each listed object id.\n \n+\ttype\n+\tRequests type information to be returned for each listed object id.\n+\n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n \tinformation for. They must be full OIDs.\n@@ -580,11 +583,18 @@ space.\n \tinfo = *PKT-LINE(attr LF)\n \t       *PKT-LINE(obj-info LF)\n \n-\tattr = \"size\"\n+\tattr = \"size\" | \"type\"\n \n \tobj-size = 1*DIGIT\n \n-\tobj-info = obj-id [SP [obj-size]]\n+\tobj-type = \"blob\" | \"tree\" | \"commit\" | \"tag\"\n+\n+\tobj-val = obj-size | obj-type\n+\n+\tobj-info = obj-id [SP [obj-val *(SP obj-val)]]\n+\n+The values in `obj-info` appear in the same order as the corresponding `attr`\n+lines, with exactly one value per requested attribute.\n \n If the server does not recognize the OID, the response will be `<oid> SP`\n regardless of the number of attributes requested.\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 8502020083..011acdec09 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -821,15 +821,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;\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@@ -881,7 +875,6 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n-\topt->format = saved_format;\n \n \tfree_fetch_object_info_results(&results);\n \tfree(line_to_split);\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 190c45eefc..e2919aa061 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -139,10 +139,10 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\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\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -152,7 +152,7 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\n \t)\n '\n \n-test_expect_success 'remote-object-info does not change the default format of info' '\n+test_expect_success 'remote-object-info and info can be mixed using the unified default format' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n@@ -162,7 +162,7 @@ test_expect_success 'remote-object-info does not change the default format of in\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 \"$hello_oid blob $hello_size\" >>expect &&\n \t\techo \"$local_oid blob $local_size\" >>expect &&\n \n \t\tgit cat-file --batch-command >actual <<-EOF &&\n@@ -209,10 +209,10 @@ test_expect_success 'batch-command -Z remote-object-info git:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -448,10 +448,10 @@ test_expect_success 'batch-command remote-object-info file:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -467,10 +467,10 @@ test_expect_success 'batch-command -Z remote-object-info file:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -618,10 +618,10 @@ test_expect_success 'batch-command remote-object-info http:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -636,10 +636,10 @@ test_expect_success 'batch-command -Z remote-object-info http:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $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-- \n2.54.0\n\n"},{"id":"550070","messageId":"DKJ3BWNUXGK0.H44F57AAI1U7@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com","subject":"Re: [PATCH GSoC v5 00/10] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-07T23:12:34Z","receivedAt":"2026-08-07T23:12:38Z","isPatch":true,"body":"I just realised I posted the wrong CI, and this v5 fails static analysis.\n\nI’ll send v6 with the fix.\n\nApologies,\nPablo\n"},{"id":"550071","messageId":"20260807231900.GA2293966@coredump.intra.peff.net","threadId":"66063","inReplyTo":"DKIADCID62IW.1MII8E3AYCI6F@gmail.com","subject":"Re: [PATCH GSoC v4 0/9] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-08-07T23:19:00Z","receivedAt":"2026-08-07T23:19:08Z","isPatch":true,"body":"On Fri, Aug 07, 2026 at 02:30:55AM +0200, Pablo Sabater wrote:\n\n> Given that, I think that it could be a good idea to have both, if a user\n> foresees that he's only going to make one 'remote-object-info' command\n> he can write it as it is now:\n> \n>   remote-object-info <remote> objA objB\n> \n> But if a user foresees that he will have to make multiple ones, we can\n> make what you suggested:\n> \n> >    remote https://example.com/foo.git\n> >    remote-object-info objA objB objC...\n> >    remote-object-info objX objY objZ\n> \n> We would have to make the remote optional, if there's no remote die(),\n> etc. We would also have to tell apart a remote from an OID in the first\n> argument, but full OIDs and remote URLs are not very similar so that\n> should not be hard haha.\n\nHmm, I dunno. Making it optional just seems to introduce more\ncomplications. The value of a separate \"remote\" is that it makes things\nsimpler (IMHO).\n\nSo maybe it is best to leave it as-is:\n\n  remote-object-info <remote> <objs...>\n\nand then let cat-file handle it under the hood as an optimization,\nleaving the transport intact between calls, in expectation of another\npossible request.\n\nIt does mean that the remote may sit \"open\" if no other call arrives.\nIn practice that's probably not a big deal. In most cases we'd either\nmake more requests or hang up the batch-command session soon after. And\nfor stateless transports, the connection naturally times out (e.g., even\na persistent HTTP connection will hang up after a few seconds). So it\nwould only be things like git-over-ssh where we keep the ssh session\nopen waiting for more commands. That seems OK to me.\n\n> I do like the idea, but I see it more as a follow-up series after this\n> one, as the topic of this series is type support.\n> Also, I'm biased as I have little time before my deadline ends.\n\nI think we can leave it in the state your series has it, and then later\ncome back and do the caching optimization. If we're not changing the\nprotocol, then those changes are invisible to the caller, so there's no\nrush.\n\n-Peff\n"},{"id":"550072","messageId":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com","subject":"[PATCH GSoC v6 00/10] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:15Z","receivedAt":"2026-08-08T00:02:24Z","isPatch":true,"body":"Neither the client nor the server knows how to handle type. This series\ncontinues the work for git cat-file --batch-command extending\nremote-object-info to support \"%(objecttype)\" end to end. It is based on\nits predecessor series \"cat-file: add remote-object-info to\nbatch-command\" [1] merged into master.\n\nType is the last property that is identical on the server and on the\nclient once fetched.\n\nWhether to support more metadata such as: - objectsize:disk\n- deltabase\n- objectmode (needs context)\n\nshould be discussed, unlike size and type, the rest depend on how things\nare packed on the server and on what the client already has, so they\ncannot reliably match local once fetched. IMO they are not worth\nsupporting, I can't find a use case for them.\n\nTo add a new placeholder, follow the steps in this series and add its\nwants_* flag to struct fetch_object_info_results.\n\nPatches 1-6 are preparatory. They don't change what the command does:\n- [1/10] is a test cleanup.\n- [2/10] fixes a possible bug in case of a malformed response.\n- [3/10] and [4/10] refactor how the object data is stored and handled.\n  The why about this refactor comes from [2].\n- [5/10] drops the last error return left in fetch_object_info().\n- [6/10] drops the remote object-info fields from the transport struct.\n\nPatches 7-10 are the actual objecttype support:\n- [7/10] teaches the server to answer type.\n- [8/10] teaches the client to parse it.\n- [9/10] advertises the capability so the client can start asking it.\n- [10/10] unifies the default format.\n\nNote that *_type appears from [8/10]. Before this series, \"type\" is\nalready listed in the client's allow-list, but the prep patches drop\nthat list, so the client knows nothing about type until [8/10] adds it\nback.\n\nI apologise for the spam as I've sent v5 and v6 very close together.\n\nGithub CI v6: https://github.com/pabloosabaterr/git/actions/runs/31227956862\n\nSorry for the inconveniences.\n\n[1]: https://lore.kernel.org/git/20260724-ps-eric-work-rebase-v21-0-ba67f024fdff@gmail.com/\n[2]: https://lore.kernel.org/git/xmqqzez67yg1.fsf@gitster.g/\n\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\nChanges in v6:\n- Fixed static analysis CI fail with two forward declarations.\n- Fixed s/%\"PRIuMAX\"/%\" PRIuMAX \"/\n- Link to v5: https://lore.kernel.org/git/20260808-objecttype-support-v5-0-86f22bec04b2@gmail.com\n\nChanges in v5:\n- New [6/10] by Jeff King: drop the remote object-info fields from the\n  transport struct and pass them as function parameters instead.\n- [5/10]: keep the const qualifiers on parameters of fetch_object_info(),\n  matching how the signature was.\n- Link to v4: https://lore.kernel.org/git/20260804-objecttype-support-v4-0-31511b0231be@gmail.com/\n\nChanges in v4:\n- Rebased onto master now that ps/cat-file-remote-object-info was merged.\n- Dropped the Based-on trailer.\n- Reworded [1/9] so it shows that we also benefit from preserving the\n  exit status.\n- Changed [2/9] to also check if a response returns more packets.\n- New [5/9]: fetch_object_info() dies on its last error return and\n  becomes void.\n- Moved the introduction of ask_type, wants_type to [7/9] instead of\n  [4/9].\n- Link to v3: https://lore.kernel.org/git/20260803-objecttype-support-v3-0-7176fecf7950@gmail.com/\n\nChanges in v3:\n- 1/8 (was 2/6): reword the commit message to explain that \"wc -c | xargs\"\n  only strips the padding some platforms add.\n- New 2/8: die if the server sends fewer object-info lines than\n  requested OIDs. Preexisting bug.\n- New 3/8: drop struct object_info_args and pass its members directly to\n  fetch_object_info().\n- New 4/8: replace struct object_info with struct\n  fetch_object_info_results to hold the results. This also drops the\n  object_info_options string list, remote_atom_map[] and\n  free_object_info_contents().\n- Dropped 1/6, which replaced the hardcoded \"size\" in\n  send_object_info_request() with a loop over object_info_options, as\n  that list no longer reaches it.\n- 6/8: follows the new result struct, so it only adds the type array.\n- 7/8: quote $hello_type in the test.\n- Link to v2: https://lore.kernel.org/git/20260731-objecttype-support-v2-0-af577461ed57@gmail.com/\n\nChanges in v2:\n- Changed t5701 test to use test_file_size.\n- Changed the for loop to use for_each_string_list_item().\n- Moved \"fetch-object-info: request all supported options dynamically\"\n  patch to be the first one as it is prep patch.\n- Typos and nits.\n- Restored the test mixing info and remote-object-info, adapted to the\n  unified format.\n- Link to v1: https://lore.kernel.org/git/20260725-objecttype-support-v1-0-2d4ca3bbabf1@gmail.com/\n\n---\nJeff King (1):\n      transport: drop remote object-info fields from transport struct\n\nPablo Sabater (9):\n      t5701: use test_file_size() to get the size of a file\n      fetch-object-info: detect malformed server responses\n      fetch-object-info: pass arguments directly instead of a struct\n      fetch-object-info: use dedicated struct for the results\n      fetch-object-info: die() on the remaining error path\n      protocol-caps: add type support to object-info\n      fetch-object-info: parse type from server response\n      serve: advertise type capability\n      cat-file: unify default format\n\n Documentation/git-cat-file.adoc        |  17 ++--\n Documentation/gitprotocol-v2.adoc      |  18 +++-\n builtin/cat-file.c                     |  73 +++++-----------\n fetch-object-info.c                    | 149 +++++++++++++++++++++------------\n fetch-object-info.h                    |  37 +++++---\n object-file.c                          |  10 ---\n odb.h                                  |   3 -\n protocol-caps.c                        |  21 ++++-\n serve.c                                |   4 +-\n t/t1017-cat-file-remote-object-info.sh |  78 ++++++++++-------\n t/t5701-git-serve.sh                   |  41 ++++++++-\n transport-helper.c                     |   7 +-\n transport-internal.h                   |   6 +-\n transport.c                            |  24 +++---\n transport.h                            |  10 +--\n 15 files changed, 296 insertions(+), 202 deletions(-)\n\nRange-diff versus v5:\n\n 1:  77b048574a =  1:  99112fd453 t5701: use test_file_size() to get the size of a file\n 2:  892e149f11 !  2:  e18da979aa fetch-object-info: detect malformed server responses\n    @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n      \t}\n     +\n     +\tif (packet_reader_read(reader) != PACKET_READ_FLUSH)\n    -+\t\tdie(_(\"object-info: expected flush after %\"PRIuMAX\" objects\"),\n    ++\t\tdie(_(\"object-info: expected flush after %\" PRIuMAX \" objects\"),\n     +\t\t    (uintmax_t)args->oids->nr);\n     +\n      \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n 3:  05f4d26560 !  3:  8b4f6913c1 fetch-object-info: pass arguments directly instead of a struct\n    @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version,\n     @@ fetch-object-info.c: int fetch_object_info(const enum protocol_version version, struct object_info_ar\n      \n      \tif (packet_reader_read(reader) != PACKET_READ_FLUSH)\n    - \t\tdie(_(\"object-info: expected flush after %\"PRIuMAX\" objects\"),\n    + \t\tdie(_(\"object-info: expected flush after %\" PRIuMAX \" objects\"),\n     -\t\t    (uintmax_t)args->oids->nr);\n     +\t\t    (uintmax_t)oids->nr);\n      \n 4:  832639ae1a =  4:  a81e1e3c1d fetch-object-info: use dedicated struct for the results\n 5:  ffac30cef5 =  5:  a79f9957b4 fetch-object-info: die() on the remaining error path\n 6:  748adf35fb !  6:  f14f3b0a2a transport: drop remote object-info fields from transport struct\n    @@ transport-helper.c: static int fetch_refs(struct transport *transport,\n      }\n     \n      ## transport-internal.h ##\n    +@@ transport-internal.h: struct ref;\n    + struct transport;\n    + struct strvec;\n    + struct transport_ls_refs_options;\n    ++struct oid_array;\n    ++struct fetch_object_info_results;\n    + \n    + struct transport_vtable {\n    + \t/**\n     @@ transport-internal.h: struct transport_vtable {\n      \t *\n      \t * Uses object-info capability of v2 protocol.\n 7:  c7ab410aef =  7:  c51d8ae137 protocol-caps: add type support to object-info\n 8:  59da95f82c =  8:  1a9b320f36 fetch-object-info: parse type from server response\n 9:  b249e9e16c =  9:  394bdd0885 serve: advertise type capability\n10:  f8221fc1de = 10:  595b80c28d cat-file: unify default format\n\n---\nbase-commit: 5b2471720c93ee30e5764a19f3d3b3ae9ec9712a\nchange-id: 20260724-objecttype-support-ea1ef6941d07\n\n"},{"id":"550073","messageId":"20260808-objecttype-support-v6-1-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"[PATCH GSoC v6 01/10] t5701: use test_file_size() to get the size of a file","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:16Z","receivedAt":"2026-08-08T00:02:26Z","isPatch":true,"body":"The 'basics of object-info' test runs 'wc -c | xargs' twice to get the\nsize of two.t. The pipe to xargs is only there to strip the blanks\nthat some platforms pad the output of wc with.\n\nUse the test_file_size() helper, which outputs the size directly, and\nstore the result in a variable. Because 'git rev-parse two:two.t' is\nalso run multiple times, store its output in a variable as well.\n\nStoring them in variables outside the HERE-document has the added\nbenefit of preserving their exit statuses.\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 t/t5701-git-serve.sh | 11 +++++++----\n 1 file changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 9a575aa098..51d5dd1ae6 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -344,20 +344,23 @@ test_expect_success 'unexpected lines are not allowed in fetch request' '\n test_expect_success 'basics of object-info' '\n \ttest_config transfer.advertiseObjectInfo true &&\n \n+\ttwo_oid=$(git rev-parse two:two.t) &&\n+\ttwo_size=$(test_file_size two.t) &&\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 $(git rev-parse two:two.t)\n-\toid $(git rev-parse two:two.t)\n+\toid $two_oid\n+\toid $two_oid\n \t0000\n \tEOF\n \n \tcat >expect <<-EOF &&\n \tsize\n-\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n-\t$(git rev-parse two:two.t) $(wc -c <two.t | xargs)\n+\t$two_oid $two_size\n+\t$two_oid $two_size\n \t0000\n \tEOF\n \n\n-- \n2.54.0\n\n"},{"id":"550074","messageId":"20260808-objecttype-support-v6-2-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"[PATCH GSoC v6 02/10] fetch-object-info: detect malformed server responses","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:17Z","receivedAt":"2026-08-08T00:02:28Z","isPatch":true,"body":"The loop reading the object-info response stops as soon as the reader\nreturns something other than PACKET_READ_NORMAL, or once it has read as\nmany lines as we requested. Neither end is checked.\n\nA server that answers with fewer objects leaves the end of the result\narrays empty, and the caller trusts that every requested object was\nfilled in. A server that answers with more leaves the extra packets\nunread. On stateless transports check_stateless_delimiter() notices, but\non the others it passes unnoticed.\n\nCheck both limits by extracting the packet_reader_read() from the loop\ncondition, so the loop no longer consumes the last packet (flush). If\nwhile looping the read is different from a PACKET_READ_NORMAL, die()\nmeaning there are fewer objects than expected. After iterating, we only\nexpect a flush, so if the last packet is not a flush, die().\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\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 fetch-object-info.c | 14 ++++++++++----\n 1 file changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex ba7e179c44..287f668a3c 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -106,12 +106,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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+\tfor (size_t i = 0; i < args->oids->nr; i++) {\n \t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n \n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n+\t\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n+\n \t\tstring_list_split(&object_info_values, reader->line, \" \", -1);\n \n \t\tif (strcmp(object_info_values.items[0].string,\n@@ -150,6 +151,11 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \n \t\tstring_list_clear(&object_info_values, 0);\n \t}\n+\n+\tif (packet_reader_read(reader) != PACKET_READ_FLUSH)\n+\t\tdie(_(\"object-info: expected flush after %\" PRIuMAX \" objects\"),\n+\t\t    (uintmax_t)args->oids->nr);\n+\n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n \n \treturn 0;\n\n-- \n2.54.0\n\n"},{"id":"550075","messageId":"20260808-objecttype-support-v6-3-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"[PATCH GSoC v6 03/10] fetch-object-info: pass arguments directly instead of a struct","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:18Z","receivedAt":"2026-08-08T00:02:29Z","isPatch":true,"body":"struct object_info_args groups three pointers that already live in the\ntransport and are given to fetch_object_info().\nGrouping them into a struct reduces the number of parameters, but it\nsuggests that the three belong together, when they are unrelated and end\nup being accessed as args->* independently.\n\nDrop the struct and pass those parameters directly to\nfetch_object_info() and send_object_info_request(). This should have no\nchange in behavior.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Junio C Hamano <gitster@pobox.com>\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 fetch-object-info.c | 53 +++++++++++++++++++++++++++++++----------------------\n fetch-object-info.h | 17 ++++++++---------\n transport.c         | 11 +++++------\n 3 files changed, 44 insertions(+), 37 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 287f668a3c..53eec88cf0 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -9,20 +9,24 @@\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+static void send_object_info_request(const int fd_out,\n+\t\t\t\t     const struct string_list *server_options,\n+\t\t\t\t     struct oid_array *oids,\n+\t\t\t\t     struct string_list *object_info_options)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n-\twrite_command_and_capabilities(&req_buf, \"object-info\", args->server_options);\n+\twrite_command_and_capabilities(&req_buf, \"object-info\", server_options);\n \n-\tif (unsorted_string_list_has_string(args->object_info_options, \"size\"))\n+\tif (unsorted_string_list_has_string(object_info_options, \"size\"))\n \t\tpacket_buf_write(&req_buf, \"size\");\n-\telse if (args->object_info_options->nr)\n+\telse if (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+\tif (oids)\n+\t\tfor (size_t i = 0; i < oids->nr; i++)\n+\t\t\tpacket_buf_write(&req_buf, \"oid %s\",\n+\t\t\t\t\t oid_to_hex(&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@@ -45,8 +49,12 @@ static int parse_object_size(const char *s, size_t *res)\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+int fetch_object_info(const enum protocol_version version,\n+\t\t      const struct string_list *server_options,\n+\t\t      struct oid_array *oids,\n+\t\t      struct string_list *object_info_options,\n+\t\t      struct packet_reader *reader,\n+\t\t      struct object_info *object_info_data,\n \t\t      const int stateless_rpc, const int fd_out)\n {\n \tint size_index = -1;\n@@ -64,16 +72,17 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tfor (int i = (int)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\t\t\t\t     object_info_options->items[i].string, 0))\n+\t\t\t\tunsorted_string_list_delete_item(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\tsend_object_info_request(fd_out, server_options, oids,\n+\t\t\t\t\t object_info_options);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -82,14 +91,14 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\tBUG(\"unknown protocol version\");\n \t}\n \n-\tfor (size_t i = 0; i < args->object_info_options->nr; i++) {\n+\tfor (size_t i = 0; i < 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 (!unsorted_string_list_has_string(args->object_info_options, reader->line))\n+\t\tif (!unsorted_string_list_has_string(object_info_options, reader->line))\n \t\t\treturn -1;\n \n \t\tif (!strcmp(reader->line, \"size\")) {\n@@ -98,7 +107,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tfor (size_t j = 0; j < 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@@ -106,19 +115,19 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \t\t}\n \t}\n \n-\tfor (size_t i = 0; i < args->oids->nr; i++) {\n+\tfor (size_t i = 0; i < oids->nr; i++) {\n \t\tstruct string_list object_info_values = STRING_LIST_INIT_DUP;\n \n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tdie(_(\"object-info: expected %\" PRIuMAX \" objects, got %\" PRIuMAX),\n-\t\t\t    (uintmax_t)args->oids->nr, (uintmax_t)i);\n+\t\t\t    (uintmax_t)oids->nr, (uintmax_t)i);\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\t   oid_to_hex(&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    oid_to_hex(&oids->oid[i]),\n \t\t\t    object_info_values.items[0].string);\n \n \t\t/*\n@@ -138,7 +147,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\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\tif (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@@ -154,7 +163,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar\n \n \tif (packet_reader_read(reader) != PACKET_READ_FLUSH)\n \t\tdie(_(\"object-info: expected flush after %\" PRIuMAX \" objects\"),\n-\t\t    (uintmax_t)args->oids->nr);\n+\t\t    (uintmax_t)oids->nr);\n \n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n \ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 269cebb3f7..316bf917ce 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -4,22 +4,21 @@\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+struct oid_array;\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+ * Modifies 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,\n+int fetch_object_info(enum protocol_version version,\n+\t\t      const struct string_list *server_options,\n+\t\t      struct oid_array *oids,\n+\t\t      struct string_list *object_info_options,\n+\t\t      struct packet_reader *reader,\n+\t\t      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/transport.c b/transport.c\nindex 42b0a2d0bd..2bb5eedc32 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -438,11 +438,6 @@ static int fetch_object_info_via_pack(struct transport *transport)\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 \n \tconnect_setup(transport, 0);\n \tpacket_reader_init(&reader, data->fd[0], NULL, 0,\n@@ -453,7 +448,11 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tdata->version = discover_version(&reader);\n \ttransport->hash_algo = reader.hash_algo;\n \n-\tret = fetch_object_info(data->version, &args, &reader,\n+\tret = fetch_object_info(data->version,\n+\t\t\t\ttransport->server_options,\n+\t\t\t\ttransport->smart_options->object_info_oids,\n+\t\t\t\ttransport->smart_options->object_info_options,\n+\t\t\t\t&reader,\n \t\t\t\tdata->options.object_info_data,\n \t\t\t\ttransport->stateless_rpc, data->fd[1]);\n \n\n-- \n2.54.0\n\n"},{"id":"550076","messageId":"20260808-objecttype-support-v6-4-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"[PATCH GSoC v6 04/10] fetch-object-info: use dedicated struct for the results","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:19Z","receivedAt":"2026-08-08T00:02:31Z","isPatch":true,"body":"fetch_object_info() collects information about N objects, but it stores\nthe results in an array of object_info. That struct holds the extended\nparameters of read_object_info() (The optional outputs the caller wants\nfilled). Its pointers tell that function where to write the answers for\na single object. object_info is not meant to be the final storage, and\nsince fetch_object_info() does not call read_object_info(), there is no\nreason to use it. Using it means allocating one scalar per object per\nattribute just to have those pointers somewhere to point at.\n\nAdd struct fetch_object_info_results. The caller sets the wants_* flags\nto say what it is interested in, and fetch_object_info() allocates one\narray per attribute. A set wants_* flag means \"asked for\", while a\nnon-NULL array means \"available\". The caller releases the arrays with\nfree_fetch_object_info_results().\n\nThe object_info_options string list is no longer needed. Filtering\nagainst the server's advertisement now sets local ask_* flags, and\nsend_object_info_request() turns those into the v2 protocol option\nstrings. remote_atom_map[] existed only to map those strings back into\natom names, so drop it and build remote_allowed_atoms from the result\narrays.\n\nCurrently for wants_* and ask_* there is only the 'size' variant but a\nsubsequent commit will add '*_type'.\n\nfree_object_info_contents() loses its only caller and is dropped.\n\nDropping the allow-list check makes the final else reachable from the\nwire, so die() instead of BUG(): an unknown attribute is the server's\nerror, not ours.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Junio C Hamano <gitster@pobox.com>\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  | 59 +++++++++-----------------------------\n fetch-object-info.c | 81 ++++++++++++++++++++++++++---------------------------\n fetch-object-info.h | 27 +++++++++++++-----\n object-file.c       | 10 -------\n odb.h               |  3 --\n transport.c         |  3 +-\n transport.h         |  5 ++--\n 7 files changed, 77 insertions(+), 111 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 884b6d5ad3..e1650b2921 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -31,6 +31,7 @@\n #include \"alias.h\"\n #include \"remote.h\"\n #include \"transport.h\"\n+#include \"fetch-object-info.h\"\n \n /*\n  * Maximum length for a remote URL. While no universal standard exists,\n@@ -681,9 +682,8 @@ static void batch_one_object(const char *obj_name,\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-\t\t\t   struct string_list *object_info_options)\n+\t\t\t   struct fetch_object_info_results *results,\n+\t\t\t   struct oid_array *object_info_oids)\n {\n \tint retval = 0;\n \tstruct remote *remote = NULL;\n@@ -724,11 +724,9 @@ static int get_remote_info(int argc,\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-\tgtransport->smart_options->object_info_options = object_info_options;\n-\tgtransport->smart_options->object_info_data = *remote_object_info;\n+\tgtransport->smart_options->object_info_results = results;\n \tretval = transport_fetch_object_info(gtransport);\n cleanup:\n \ttransport_disconnect(gtransport);\n@@ -816,21 +814,6 @@ 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@@ -838,9 +821,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tint count;\n \tconst char **argv;\n \tchar *line_to_split;\n-\tstruct object_info *remote_object_info = NULL;\n+\tstruct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;\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@@ -861,26 +843,21 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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+\t\tresults.wants_size = 1;\n \n-\tif (get_remote_info(count, argv, &remote_object_info,\n-\t\t\t    &object_info_oids, &object_info_options))\n+\tif (get_remote_info(count, argv, &results, &object_info_oids))\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+\tif (results.sizes)\n+\t\tstring_list_append(&data->remote_allowed_atoms, \"objectsize\");\n \n \tdata->skip_object_info = 1;\n-\tfor (size_t i = 0; i < object_info_oids.nr; i++) {\n+\tfor (size_t i = 0; i < results.nr; i++) {\n \t\tdata->oid = object_info_oids.oid[i];\n \n-\t\tif (remote_object_info[i].unrecognized) {\n+\t\tif (results.unrecognized[i]) {\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@@ -890,13 +867,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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}\n-\n-\t\tif (remote_object_info[i].typep) {\n-\t\t\tdata->type = *remote_object_info[i].typep;\n-\t\t}\n+\t\tif (results.sizes)\n+\t\t\tdata->size = results.sizes[i];\n \n \t\topt->batch_mode = BATCH_MODE_INFO;\n \t\tdata->is_remote = 1;\n@@ -906,12 +878,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\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_fetch_object_info_results(&results);\n \tfree(line_to_split);\n \tfree(argv);\n-\tfree(remote_object_info);\n \toid_array_clear(&object_info_oids);\n }\n \ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 53eec88cf0..5f53dbd6b9 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -12,16 +12,14 @@\n static void send_object_info_request(const int fd_out,\n \t\t\t\t     const struct string_list *server_options,\n \t\t\t\t     struct oid_array *oids,\n-\t\t\t\t     struct string_list *object_info_options)\n+\t\t\t\t     unsigned ask_size)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n \twrite_command_and_capabilities(&req_buf, \"object-info\", server_options);\n \n-\tif (unsorted_string_list_has_string(object_info_options, \"size\"))\n+\tif (ask_size)\n \t\tpacket_buf_write(&req_buf, \"size\");\n-\telse if (object_info_options->nr)\n-\t\tBUG(\"only size should be in object_info_options\");\n \n \tif (oids)\n \t\tfor (size_t i = 0; i < oids->nr; i++)\n@@ -52,37 +50,32 @@ static int parse_object_size(const char *s, size_t *res)\n int fetch_object_info(const enum protocol_version version,\n \t\t      const struct string_list *server_options,\n \t\t      struct oid_array *oids,\n-\t\t      struct string_list *object_info_options,\n \t\t      struct packet_reader *reader,\n-\t\t      struct object_info *object_info_data,\n-\t\t      const int stateless_rpc, const int fd_out)\n+\t\t      struct fetch_object_info_results *results,\n+\t\t      const int stateless_rpc,\n+\t\t      const int fd_out)\n {\n+\tunsigned ask_size = 0;\n \tint size_index = -1;\n+\tsize_t wanted;\n+\n+\tresults->nr = oids->nr;\n+\tCALLOC_ARRAY(results->unrecognized, results->nr);\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\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)object_info_options->nr - 1; i >= 0; i--)\n-\t\t\tif (!server_supports_feature(\"object-info\",\n-\t\t\t\t\t\t     object_info_options->items[i].string, 0))\n-\t\t\t\tunsorted_string_list_delete_item(object_info_options, i, 0);\n+\n+\t\tif (results->wants_size &&\n+\t\t    server_supports_feature(\"object-info\", \"size\", 0))\n+\t\t\task_size = 1;\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, server_options, oids,\n-\t\t\t\t\t object_info_options);\n+\t\tsend_object_info_request(fd_out, server_options, oids, ask_size);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -90,28 +83,25 @@ int fetch_object_info(const enum protocol_version version,\n \tcase protocol_unknown_version:\n \t\tBUG(\"unknown protocol version\");\n \t}\n+\twanted = ask_size;\n \n-\tfor (size_t i = 0; i < object_info_options->nr; i++) {\n+\tfor (size_t i = 0; i < wanted; 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 (!unsorted_string_list_has_string(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\tif (!ask_size)\n+\t\t\t\tdie(_(\"object-info: unrequested 'size' attribute\"));\n+\t\t\tif (results->sizes)\n+\t\t\t\tdie(_(\"object-info: duplicate 'size' attribute\"));\n \t\t\tsize_index = (int)i;\n-\t\t\tfor (size_t j = 0; j < 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\tCALLOC_ARRAY(results->sizes, results->nr);\n \t\t} else {\n-\t\t\tBUG(\"only size is supported\");\n+\t\t\tdie(_(\"object-info: unknown attribute '%s'\"),\n+\t\t\t    reader->line);\n \t\t}\n \t}\n \n@@ -137,24 +127,24 @@ int fetch_object_info(const enum protocol_version version,\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\tresults->unrecognized[i] = 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 * Because we only ask for attributes the server said it\n+\t\t * supports, we expect the answer to have one value per\n+\t\t * requested attribute, plus the OID.\n \t\t */\n-\t\tif (object_info_options->nr + 1 != object_info_values.nr)\n+\t\tif (wanted + 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\tif (results->sizes &&\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\t      &results->sizes[i]))\n+\t\t\tdie(\"object-info: object %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@@ -169,3 +159,10 @@ int fetch_object_info(const enum protocol_version version,\n \n \treturn 0;\n }\n+\n+void free_fetch_object_info_results(struct fetch_object_info_results *results)\n+{\n+\tfree(results->sizes);\n+\tfree(results->unrecognized);\n+\tmemset(results, 0, sizeof(*results));\n+}\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 316bf917ce..9f72e91155 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -4,21 +4,34 @@\n #include \"pkt-line.h\"\n #include \"protocol.h\"\n \n-struct object_info;\n+struct fetch_object_info_results {\n+\tsize_t *sizes;\n+\tuint8_t *unrecognized;\n+\tsize_t nr;\n+\tunsigned wants_size:1;\n+};\n+\n+#define FETCH_OBJECT_INFO_RESULTS_INIT { 0 }\n+\n struct oid_array;\n /*\n- * Sends git-cat-file object-info command into the request buf and read the\n+ * Sends git-cat-file object-info command into the request buf and reads the\n  * results from packets.\n  *\n- * Modifies object_info_options, on return it contains only the supported\n- * options by the server.\n+ * The caller sets the wants_* flags in \"results\" to indicate which attributes\n+ * it is interested in. On return, \"results\" holds one array per attribute that\n+ * the server both advertised and answered with. An array left NULL means the\n+ * attribute is not available.\n+ * Release them with free_fetch_object_info_results().\n  */\n int fetch_object_info(enum protocol_version version,\n \t\t      const struct string_list *server_options,\n \t\t      struct oid_array *oids,\n-\t\t      struct string_list *object_info_options,\n \t\t      struct packet_reader *reader,\n-\t\t      struct object_info *object_info_data,\n-\t\t      int stateless_rpc, int fd_out);\n+\t\t      struct fetch_object_info_results *results,\n+\t\t      int stateless_rpc,\n+\t\t      int fd_out);\n+\n+void free_fetch_object_info_results(struct fetch_object_info_results *results);\n \n #endif /* FETCH_OBJECT_INFO_H */\ndiff --git a/object-file.c b/object-file.c\nindex ec35c318bc..66f6c81373 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1340,13 +1340,3 @@ 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 cbc2f9ced4..f4e6c26a0e 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -637,7 +637,4 @@ 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/transport.c b/transport.c\nindex 2bb5eedc32..b0e29124d1 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -451,9 +451,8 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tret = fetch_object_info(data->version,\n \t\t\t\ttransport->server_options,\n \t\t\t\ttransport->smart_options->object_info_oids,\n-\t\t\t\ttransport->smart_options->object_info_options,\n \t\t\t\t&reader,\n-\t\t\t\tdata->options.object_info_data,\n+\t\t\t\tdata->options.object_info_results,\n \t\t\t\ttransport->stateless_rpc, data->fd[1]);\n \n \tclose(data->fd[0]);\ndiff --git a/transport.h b/transport.h\nindex a7869d18e0..6948b65db9 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -7,6 +7,8 @@\n #include \"string-list.h\"\n #include \"connect.h\"\n \n+struct fetch_object_info_results;\n+\n struct git_transport_options {\n \tunsigned thin : 1;\n \tunsigned keep : 1;\n@@ -57,8 +59,7 @@ 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 string_list *object_info_options;\n+\tstruct fetch_object_info_results *object_info_results;\n };\n \n enum transport_family {\n\n-- \n2.54.0\n\n"},{"id":"550077","messageId":"20260808-objecttype-support-v6-5-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"[PATCH GSoC v6 05/10] fetch-object-info: die() on the remaining error path","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:20Z","receivedAt":"2026-08-08T00:02:32Z","isPatch":true,"body":"Every failure in fetch_object_info() dies except one: a short read while\nparsing the attribute lines returns -1. That -1 is then passed through\nfetch_object_info_via_pack() and get_remote_info() up to cat-file, only\nto die() with a generic message.\n\nDie in fetch_object_info() instead, consistently with the rest of its\nerror paths, and make fetch_object_info() void.\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 fetch-object-info.c | 19 +++++++++----------\n fetch-object-info.h | 14 +++++++-------\n transport.c         | 12 ++++++------\n 3 files changed, 22 insertions(+), 23 deletions(-)\n\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 5f53dbd6b9..4db879c2dc 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -47,13 +47,13 @@ static int parse_object_size(const char *s, size_t *res)\n \treturn 0;\n }\n \n-int fetch_object_info(const enum protocol_version version,\n-\t\t      const struct string_list *server_options,\n-\t\t      struct oid_array *oids,\n-\t\t      struct packet_reader *reader,\n-\t\t      struct fetch_object_info_results *results,\n-\t\t      const int stateless_rpc,\n-\t\t      const int fd_out)\n+void fetch_object_info(const enum protocol_version version,\n+\t\t       const struct string_list *server_options,\n+\t\t       struct oid_array *oids,\n+\t\t       struct packet_reader *reader,\n+\t\t       struct fetch_object_info_results *results,\n+\t\t       const int stateless_rpc,\n+\t\t       const int fd_out)\n {\n \tunsigned ask_size = 0;\n \tint size_index = -1;\n@@ -89,7 +89,8 @@ int fetch_object_info(const enum protocol_version version,\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\tdie(_(\"object-info: expected %\" PRIuMAX \" attributes, got %\" PRIuMAX),\n+\t\t\t    (uintmax_t)wanted, (uintmax_t)i);\n \t\t}\n \n \t\tif (!strcmp(reader->line, \"size\")) {\n@@ -156,8 +157,6 @@ int fetch_object_info(const enum protocol_version version,\n \t\t    (uintmax_t)oids->nr);\n \n \tcheck_stateless_delimiter(stateless_rpc, reader, \"stateless delimiter expected\");\n-\n-\treturn 0;\n }\n \n void free_fetch_object_info_results(struct fetch_object_info_results *results)\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 9f72e91155..97ee5314c9 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -24,13 +24,13 @@ struct oid_array;\n  * attribute is not available.\n  * Release them with free_fetch_object_info_results().\n  */\n-int fetch_object_info(enum protocol_version version,\n-\t\t      const struct string_list *server_options,\n-\t\t      struct oid_array *oids,\n-\t\t      struct packet_reader *reader,\n-\t\t      struct fetch_object_info_results *results,\n-\t\t      int stateless_rpc,\n-\t\t      int fd_out);\n+void fetch_object_info(enum protocol_version version,\n+\t\t       const struct string_list *server_options,\n+\t\t       struct oid_array *oids,\n+\t\t       struct packet_reader *reader,\n+\t\t       struct fetch_object_info_results *results,\n+\t\t       int stateless_rpc,\n+\t\t       int fd_out);\n \n void free_fetch_object_info_results(struct fetch_object_info_results *results);\n \ndiff --git a/transport.c b/transport.c\nindex b0e29124d1..35acdf71a2 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -448,12 +448,12 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \tdata->version = discover_version(&reader);\n \ttransport->hash_algo = reader.hash_algo;\n \n-\tret = fetch_object_info(data->version,\n-\t\t\t\ttransport->server_options,\n-\t\t\t\ttransport->smart_options->object_info_oids,\n-\t\t\t\t&reader,\n-\t\t\t\tdata->options.object_info_results,\n-\t\t\t\ttransport->stateless_rpc, data->fd[1]);\n+\tfetch_object_info(data->version,\n+\t\t\t  transport->server_options,\n+\t\t\t  transport->smart_options->object_info_oids,\n+\t\t\t  &reader,\n+\t\t\t  data->options.object_info_results,\n+\t\t\t  transport->stateless_rpc, data->fd[1]);\n \n \tclose(data->fd[0]);\n \tif (data->fd[1] >= 0)\n\n-- \n2.54.0\n\n"},{"id":"550078","messageId":"20260808-objecttype-support-v6-6-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"[PATCH GSoC v6 06/10] transport: drop remote object-info fields from transport struct","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:21Z","receivedAt":"2026-08-08T00:02:34Z","isPatch":true,"body":"From: Jeff King <peff@peff.net>\n\nA remote object-info request needs three things: the transport for\ncontacting the remote, the list of oids to request, and a place to store\nthe output.\n\nRather than take these as function parameters, we take only the\ntransport object, and expect the caller to have placed the other two\ninto special fields in the transport struct. But this doesn't make much\nsense. The set of oids and results are really only valid for one\nrequest. There is no reason the transport would need to hang on to them\noutside of the single function call.\n\nEven though we save a few lines passing the parameters around through\nthe various vtable functions, the result is harder to understand (for\nexample, who is responsible for cleaning up results, and when should it\nhappen?). It also opens up the possibility of a subtle bug. A caller is\nlikely to point those fields to stack variables which could go out of\nscope, and the transport struct would be left holding invalid pointers.\n\nThis is mostly harmless now, as we disconnect the transport immediately\nafter the sole caller of transport_fetch_object_info(). But conceptually\nwe could keep the transport open and make multiple fetch calls (and\nreuse the same connection to the helper, to a remote HTTP server, and so\non).\n\nSo let's pull these out of the struct and pass them as function\nparameters. It's a little more verbose, but I think more clearly\nillustrates the intent. I've also tweaked a few function signatures to\nmark the input oid array as const, since it is purely an input to the\nfunction.\n\nSigned-off-by: Jeff King <peff@peff.net>\nSigned-off-by: Pablo Sabater <pabloosabaterr@gmail.com>\n---\n builtin/cat-file.c   |  6 ++----\n fetch-object-info.c  |  4 ++--\n fetch-object-info.h  |  2 +-\n transport-helper.c   |  7 +++++--\n transport-internal.h |  6 +++++-\n transport.c          | 14 +++++++++-----\n transport.h          |  7 +++----\n 7 files changed, 27 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex e1650b2921..8dcad2f5eb 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -724,10 +724,8 @@ static int get_remote_info(int argc,\n \t\tgoto cleanup;\n \t}\n \n-\tgtransport->smart_options->object_info_oids = object_info_oids;\n-\n-\tgtransport->smart_options->object_info_results = results;\n-\tretval = transport_fetch_object_info(gtransport);\n+\tretval = transport_fetch_object_info(gtransport, object_info_oids,\n+\t\t\t\t\t     results);\n cleanup:\n \ttransport_disconnect(gtransport);\n \treturn retval;\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex 4db879c2dc..fe26bf4bbc 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -11,7 +11,7 @@\n /* Sends object-info command and its arguments into the request buffer. */\n static void send_object_info_request(const int fd_out,\n \t\t\t\t     const struct string_list *server_options,\n-\t\t\t\t     struct oid_array *oids,\n+\t\t\t\t     const struct oid_array *oids,\n \t\t\t\t     unsigned ask_size)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n@@ -49,7 +49,7 @@ static int parse_object_size(const char *s, size_t *res)\n \n void fetch_object_info(const enum protocol_version version,\n \t\t       const struct string_list *server_options,\n-\t\t       struct oid_array *oids,\n+\t\t       const struct oid_array *oids,\n \t\t       struct packet_reader *reader,\n \t\t       struct fetch_object_info_results *results,\n \t\t       const int stateless_rpc,\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 97ee5314c9..10cf9f5f63 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -26,7 +26,7 @@ struct oid_array;\n  */\n void fetch_object_info(enum protocol_version version,\n \t\t       const struct string_list *server_options,\n-\t\t       struct oid_array *oids,\n+\t\t       const struct oid_array *oids,\n \t\t       struct packet_reader *reader,\n \t\t       struct fetch_object_info_results *results,\n \t\t       int stateless_rpc,\ndiff --git a/transport-helper.c b/transport-helper.c\nindex b109fbd8c3..e729a097de 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -784,11 +784,14 @@ static int fetch_refs(struct transport *transport,\n \treturn -1;\n }\n \n-static int fetch_object_info_helper(struct transport *transport)\n+static int fetch_object_info_helper(struct transport *transport,\n+\t\t\t\t    const struct oid_array *oids,\n+\t\t\t\t    struct fetch_object_info_results *results)\n {\n \tget_helper(transport);\n \tif (process_connect(transport, 0))\n-\t\treturn transport->vtable->fetch_object_info(transport);\n+\t\treturn transport->vtable->fetch_object_info(transport, oids,\n+\t\t\t\t\t\t\t    results);\n \n \tdie(_(\"object-info requires protocol v2\"));\n }\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 60db0bedcd..a10b27cc81 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -7,6 +7,8 @@ struct ref;\n struct transport;\n struct strvec;\n struct transport_ls_refs_options;\n+struct oid_array;\n+struct fetch_object_info_results;\n \n struct transport_vtable {\n \t/**\n@@ -51,7 +53,9 @@ struct transport_vtable {\n \t *\n \t * Uses object-info capability of v2 protocol.\n \t */\n-\tint (*fetch_object_info)(struct transport *transport);\n+\tint (*fetch_object_info)(struct transport *transport,\n+\t\t\t\t const struct oid_array *oids,\n+\t\t\t\t struct fetch_object_info_results *results);\n \n \t/**\n \t * Push the objects and refs. Send the necessary objects, and\ndiff --git a/transport.c b/transport.c\nindex 35acdf71a2..25e2c14a7b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -433,7 +433,9 @@ 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+static int fetch_object_info_via_pack(struct transport *transport,\n+\t\t\t\t      const struct oid_array *oids,\n+\t\t\t\t      struct fetch_object_info_results *results)\n {\n \tint ret = 0;\n \tstruct git_transport_data *data = transport->data;\n@@ -450,9 +452,9 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \n \tfetch_object_info(data->version,\n \t\t\t  transport->server_options,\n-\t\t\t  transport->smart_options->object_info_oids,\n+\t\t\t  oids,\n \t\t\t  &reader,\n-\t\t\t  data->options.object_info_results,\n+\t\t\t  results,\n \t\t\t  transport->stateless_rpc, data->fd[1]);\n \n \tclose(data->fd[0]);\n@@ -465,11 +467,13 @@ static int fetch_object_info_via_pack(struct transport *transport)\n \treturn ret;\n }\n \n-int transport_fetch_object_info(struct transport *transport)\n+int transport_fetch_object_info(struct transport *transport,\n+\t\t\t\tconst struct oid_array *oids,\n+\t\t\t\tstruct fetch_object_info_results *results)\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+\treturn transport->vtable->fetch_object_info(transport, oids, results);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\ndiff --git a/transport.h b/transport.h\nindex 6948b65db9..39193d0077 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -57,9 +57,6 @@ 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 fetch_object_info_results *object_info_results;\n };\n \n enum transport_family {\n@@ -317,7 +314,9 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs);\n /*\n  * Fetch the object info from remote\n  */\n-int transport_fetch_object_info(struct transport *transport);\n+int transport_fetch_object_info(struct transport *transport,\n+\t\t\t\tconst struct oid_array *oids,\n+\t\t\t\tstruct fetch_object_info_results *results);\n \n /*\n  * If this flag is set, unlocking will avoid to call non-async-signal-safe\n\n-- \n2.54.0\n\n"},{"id":"550079","messageId":"20260808-objecttype-support-v6-7-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"[PATCH GSoC v6 07/10] protocol-caps: add type support to object-info","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:22Z","receivedAt":"2026-08-08T00:02:35Z","isPatch":true,"body":"Teach the server-side object-info handler to accept type as a requested\nfield. When the client includes type in its object-info request, the\nserver returns the requested object type.\n\nWhile touching send_info(), wrap an over-long line and fix the bit field\nstyle of requested_info.size.\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 protocol-caps.c      | 21 ++++++++++++++++++---\n t/t5701-git-serve.sh | 30 ++++++++++++++++++++++++++++++\n 2 files changed, 48 insertions(+), 3 deletions(-)\n\ndiff --git a/protocol-caps.c b/protocol-caps.c\nindex 02261be14d..27e0f85b10 100644\n--- a/protocol-caps.c\n+++ b/protocol-caps.c\n@@ -11,7 +11,8 @@\n #include \"strbuf.h\"\n \n struct requested_info {\n-\tunsigned size : 1;\n+\tunsigned size:1;\n+\tunsigned type:1;\n };\n \n /*\n@@ -73,15 +74,20 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \tif (info->size)\n \t\tpacket_writer_write(writer, \"size\");\n \n+\tif (info->type)\n+\t\tpacket_writer_write(writer, \"type\");\n+\n \tfor_each_string_list_item (item, oid_str_list) {\n \t\tconst char *oid_str = item->string;\n+\t\tenum object_type object_type;\n \t\tstruct object_id oid;\n \t\tsize_t object_size;\n \n \t\tif (get_oid_hex_algop(oid_str, &oid, r->hash_algo) < 0) {\n \t\t\tpacket_writer_error(\n \t\t\t\twriter,\n-\t\t\t\t\"object-info: protocol error, expected to get oid, not '%s'\",\n+\t\t\t\t\"object-info: protocol error, expected to get \"\n+\t\t\t\t\"oid, not '%s'\",\n \t\t\t\toid_str);\n \t\t\tcontinue;\n \t\t}\n@@ -93,7 +99,8 @@ static void send_info(struct repository *r, struct packet_writer *writer,\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\tobject_type = get_object_info(r->objects, &oid, &object_size);\n+\t\tif (object_type <= OBJ_NONE) {\n \t\t\tstrbuf_addstr(&send_buffer, \" \");\n \t\t\tgoto write;\n \t\t}\n@@ -103,6 +110,9 @@ static void send_info(struct repository *r, struct packet_writer *writer,\n \t\t\t\t    (uintmax_t)object_size);\n \t\t}\n \n+\t\tif (info->type)\n+\t\t\tstrbuf_addf(&send_buffer, \" %s\", type_name(object_type));\n+\n write:\n \t\tpacket_writer_write(writer, \"%s\", send_buffer.buf);\n \t\tstrbuf_reset(&send_buffer);\n@@ -124,6 +134,11 @@ int cap_object_info(struct repository *r, struct packet_reader *request)\n \t\t\tcontinue;\n \t\t}\n \n+\t\tif (!strcmp(\"type\", request->line)) {\n+\t\t\tinfo.type = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif (parse_oid(request->line, &oid_str_list))\n \t\t\tcontinue;\n \ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 51d5dd1ae6..f57e36a88d 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -369,6 +369,36 @@ test_expect_success 'basics of object-info' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'object-info supports type' '\n+\ttest_config transfer.advertiseObjectInfo true &&\n+\n+\ttwo_oid=$(git rev-parse two:two.t) &&\n+\ttwo_size=$(test_file_size two.t) &&\n+\n+\ttest-tool pkt-line pack >in <<-EOF &&\n+\tcommand=object-info\n+\tobject-format=$(test_oid algo)\n+\t0001\n+\tsize\n+\ttype\n+\toid $two_oid\n+\toid $two_oid\n+\t0000\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tsize\n+\ttype\n+\t$two_oid $two_size blob\n+\t$two_oid $two_size blob\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 'bare OID request' '\n \ttest_config transfer.advertiseObjectInfo true &&\n \n\n-- \n2.54.0\n\n"},{"id":"550080","messageId":"20260808-objecttype-support-v6-8-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"[PATCH GSoC v6 08/10] fetch-object-info: parse type from server response","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:23Z","receivedAt":"2026-08-08T00:02:37Z","isPatch":true,"body":"The server can handle type requests but does not advertise the\ncapability yet. Prepare the client to know how to parse the server\nresponse once the server advertises the type capability.\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  |  7 +++++++\n fetch-object-info.c | 38 +++++++++++++++++++++++++++++++++++---\n fetch-object-info.h |  3 +++\n 3 files changed, 45 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 8dcad2f5eb..8502020083 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -842,6 +842,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \n \tif (data->info.sizep)\n \t\tresults.wants_size = 1;\n+\tif (data->info.typep)\n+\t\tresults.wants_type = 1;\n \n \tif (get_remote_info(count, argv, &results, &object_info_oids))\n \t\tdie(_(\"failed to get object info from the remote: %s\"), argv[0]);\n@@ -850,6 +852,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tstring_list_append(&data->remote_allowed_atoms, \"objectname\");\n \tif (results.sizes)\n \t\tstring_list_append(&data->remote_allowed_atoms, \"objectsize\");\n+\tif (results.types)\n+\t\tstring_list_append(&data->remote_allowed_atoms, \"objecttype\");\n \n \tdata->skip_object_info = 1;\n \tfor (size_t i = 0; i < results.nr; i++) {\n@@ -868,6 +872,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tif (results.sizes)\n \t\t\tdata->size = results.sizes[i];\n \n+\t\tif (results.types)\n+\t\t\tdata->type = results.types[i];\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);\ndiff --git a/fetch-object-info.c b/fetch-object-info.c\nindex fe26bf4bbc..0a58308f9b 100644\n--- a/fetch-object-info.c\n+++ b/fetch-object-info.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n+#include \"object.h\"\n #include \"pkt-line.h\"\n #include \"connect.h\"\n #include \"oid-array.h\"\n@@ -12,7 +13,8 @@\n static void send_object_info_request(const int fd_out,\n \t\t\t\t     const struct string_list *server_options,\n \t\t\t\t     const struct oid_array *oids,\n-\t\t\t\t     unsigned ask_size)\n+\t\t\t\t     unsigned ask_size,\n+\t\t\t\t     unsigned ask_type)\n {\n \tstruct strbuf req_buf = STRBUF_INIT;\n \n@@ -21,6 +23,9 @@ static void send_object_info_request(const int fd_out,\n \tif (ask_size)\n \t\tpacket_buf_write(&req_buf, \"size\");\n \n+\tif (ask_type)\n+\t\tpacket_buf_write(&req_buf, \"type\");\n+\n \tif (oids)\n \t\tfor (size_t i = 0; i < oids->nr; i++)\n \t\t\tpacket_buf_write(&req_buf, \"oid %s\",\n@@ -56,7 +61,9 @@ void fetch_object_info(const enum protocol_version version,\n \t\t       const int fd_out)\n {\n \tunsigned ask_size = 0;\n+\tunsigned ask_type = 0;\n \tint size_index = -1;\n+\tint type_index = -1;\n \tsize_t wanted;\n \n \tresults->nr = oids->nr;\n@@ -71,11 +78,16 @@ void fetch_object_info(const enum protocol_version version,\n \t\t    server_supports_feature(\"object-info\", \"size\", 0))\n \t\t\task_size = 1;\n \n+\t\tif (results->wants_type &&\n+\t\t    server_supports_feature(\"object-info\", \"type\", 0))\n+\t\t\task_type = 1;\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, server_options, oids, ask_size);\n+\t\tsend_object_info_request(fd_out, server_options, oids, ask_size,\n+\t\t\t\t\t ask_type);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -83,7 +95,7 @@ void fetch_object_info(const enum protocol_version version,\n \tcase protocol_unknown_version:\n \t\tBUG(\"unknown protocol version\");\n \t}\n-\twanted = ask_size;\n+\twanted = ask_size + ask_type;\n \n \tfor (size_t i = 0; i < wanted; i++) {\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n@@ -100,6 +112,13 @@ void fetch_object_info(const enum protocol_version version,\n \t\t\t\tdie(_(\"object-info: duplicate 'size' attribute\"));\n \t\t\tsize_index = (int)i;\n \t\t\tCALLOC_ARRAY(results->sizes, results->nr);\n+\t\t} else if (!strcmp(reader->line, \"type\")) {\n+\t\t\tif (!ask_type)\n+\t\t\t\tdie(_(\"object-info: unrequested 'type' attribute\"));\n+\t\t\tif (results->types)\n+\t\t\t\tdie(_(\"object-info: duplicate 'type' attribute\"));\n+\t\t\ttype_index = (int)i;\n+\t\t\tCALLOC_ARRAY(results->types, results->nr);\n \t\t} else {\n \t\t\tdie(_(\"object-info: unknown attribute '%s'\"),\n \t\t\t    reader->line);\n@@ -149,6 +168,18 @@ void fetch_object_info(const enum protocol_version version,\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\tif (results->types) {\n+\t\t\tconst char *type_str =\n+\t\t\t\tobject_info_values.items[type_index + 1].string;\n+\t\t\tint type = type_from_string_gently(type_str, -1, 1);\n+\n+\t\t\tif (type < 0)\n+\t\t\t\tdie(_(\"object-info: object %s has invalid type '%s'\"),\n+\t\t\t\t    object_info_values.items[0].string, type_str);\n+\n+\t\t\tresults->types[i] = type;\n+\t\t}\n+\n \t\tstring_list_clear(&object_info_values, 0);\n \t}\n \n@@ -162,6 +193,7 @@ void fetch_object_info(const enum protocol_version version,\n void free_fetch_object_info_results(struct fetch_object_info_results *results)\n {\n \tfree(results->sizes);\n+\tfree(results->types);\n \tfree(results->unrecognized);\n \tmemset(results, 0, sizeof(*results));\n }\ndiff --git a/fetch-object-info.h b/fetch-object-info.h\nindex 10cf9f5f63..2fba96c6f7 100644\n--- a/fetch-object-info.h\n+++ b/fetch-object-info.h\n@@ -1,14 +1,17 @@\n #ifndef FETCH_OBJECT_INFO_H\n #define FETCH_OBJECT_INFO_H\n \n+#include \"object.h\"\n #include \"pkt-line.h\"\n #include \"protocol.h\"\n \n struct fetch_object_info_results {\n \tsize_t *sizes;\n+\tenum object_type *types;\n \tuint8_t *unrecognized;\n \tsize_t nr;\n \tunsigned wants_size:1;\n+\tunsigned wants_type:1;\n };\n \n #define FETCH_OBJECT_INFO_RESULTS_INIT { 0 }\n\n-- \n2.54.0\n\n"},{"id":"550081","messageId":"20260808-objecttype-support-v6-9-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"[PATCH GSoC v6 09/10] serve: advertise type capability","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:24Z","receivedAt":"2026-08-08T00:02:38Z","isPatch":true,"body":"The server and the client can handle type requests but the client won't\nask for it until the server advertises it.\n\nAdd type to the advertised capabilities so the client knows that it can\nrequest 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 serve.c                                |  4 ++--\n t/t1017-cat-file-remote-object-info.sh | 26 ++++++++++++++++++++++----\n 2 files changed, 24 insertions(+), 6 deletions(-)\n\ndiff --git a/serve.c b/serve.c\nindex 2b07d922b3..2ce513cf2d 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -97,9 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value)\n \t\t/* disabled by default */\n \t\tadvertise_object_info = 0;\n \t}\n-\t/* Currently only size is supported */\n+\t/* Currently only size and type are supported */\n \tif (value && advertise_object_info)\n-\t\tstrbuf_addstr(value, \"size\");\n+\t\tstrbuf_addstr(value, \"size type\");\n \treturn advertise_object_info;\n }\n \ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 116862f9d0..190c45eefc 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -7,6 +7,7 @@ test_description='git cat-file --batch-command with remote-object-info command'\n \n hello_content=\"Hello World\"\n hello_size=$(strlen \"$hello_content\")\n+hello_type=\"blob\"\n hello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n hello_short_oid=$(git rev-parse --short \"$hello_oid\")\n \n@@ -19,6 +20,7 @@ unstored_oid=$(echo_without_newline \"$unstored_content\" | git hash-object --stdi\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+tree_type=\"tree\"\n \n commit_message=\"Initial commit\"\n \n@@ -31,6 +33,7 @@ commit_message=\"Initial commit\"\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+commit_type=\"commit\"\n \n tag_header_without_oid=\"type blob\n tag hellotag\n@@ -44,6 +47,7 @@ $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+tag_type=\"tag\"\n \n set_transport_variables () {\n \thello_oid=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n@@ -256,14 +260,12 @@ 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+test_expect_success 'objecttype is supported by remote-object-info' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n \n-\t\techo \"\" >expect &&\n+\t\techo \"$hello_type\" >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@@ -271,6 +273,22 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '\n \t)\n '\n \n+test_expect_success 'unsupported placeholders on remote return empty string' '\n+\t(\n+\t\tset_transport_variables \"$daemon_parent\" &&\n+\t\tcd \"$daemon_parent/daemon_client_empty\" &&\n+\n+\t\tfmt=\"%(objectmode) %(objectsize:disk) %(rest) %(deltabase)\" &&\n+\n+\t\t# The hardcoded SPs between the atoms are respected.\n+\t\techo \"   \" >expect &&\n+\t\tgit cat-file --batch-command=\"$fmt\" >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 'requesting only objectname echoes back' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n\n-- \n2.54.0\n\n"},{"id":"550082","messageId":"20260808-objecttype-support-v6-10-e5cdaf27a49c@gmail.com","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"[PATCH GSoC v6 10/10] cat-file: unify default format","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-08-08T00:02:25Z","receivedAt":"2026-08-08T00:02:39Z","isPatch":true,"body":"%(objecttype) is supported both by the client and by the server.\nChange the temporary default format to the unified version that the\nother commands use.\n\nUpdate documentation to remove %(objecttype) from the caveats of\nremote-object-info and show %(objecttype) support.\n\nNow that type is supported and the default format unified, update the\ntests to expect the new default format.\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        | 17 +++++------\n Documentation/gitprotocol-v2.adoc      | 18 +++++++++---\n builtin/cat-file.c                     |  7 -----\n t/t1017-cat-file-remote-object-info.sh | 52 +++++++++++++++++-----------------\n 4 files changed, 47 insertions(+), 47 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc\nindex ac3b528c6f..514bfc0032 100644\n--- a/Documentation/git-cat-file.adoc\n+++ b/Documentation/git-cat-file.adoc\n@@ -348,15 +348,12 @@ 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+The command `remote-object-info` only supports the `%(objectname)`,\n+`%(objectsize)` and `%(objecttype)` placeholders. See `CAVEATS` below for more\n+information.\n \n If no format is specified, the default format is `%(objectname)\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+%(objecttype) %(objectsize)`.\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@@ -453,9 +450,9 @@ 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+Note that only `%(objectname)`, `%(objectsize)` and `%(objecttype)` are\n+currently supported by the `remote-object-info` command. Using any other\n+placeholder in 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/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc\nindex 7bf62014c3..dd52fd8110 100644\n--- a/Documentation/gitprotocol-v2.adoc\n+++ b/Documentation/gitprotocol-v2.adoc\n@@ -558,14 +558,17 @@ object-info\n \n `object-info` is the command to retrieve information about one or more objects.\n Its main purpose is to allow a client to make decisions based on this\n-information without having to fully fetch objects. Object size is the only\n-information that is currently supported.\n+information without having to fully fetch objects. Currently only object size\n+and type are supported.\n \n An `object-info` request takes the following arguments:\n \n \tsize\n \tRequests size information to be returned for each listed object id.\n \n+\ttype\n+\tRequests type information to be returned for each listed object id.\n+\n \toid <oid>\n \tIndicates to the server an object which the client wants to obtain\n \tinformation for. They must be full OIDs.\n@@ -580,11 +583,18 @@ space.\n \tinfo = *PKT-LINE(attr LF)\n \t       *PKT-LINE(obj-info LF)\n \n-\tattr = \"size\"\n+\tattr = \"size\" | \"type\"\n \n \tobj-size = 1*DIGIT\n \n-\tobj-info = obj-id [SP [obj-size]]\n+\tobj-type = \"blob\" | \"tree\" | \"commit\" | \"tag\"\n+\n+\tobj-val = obj-size | obj-type\n+\n+\tobj-info = obj-id [SP [obj-val *(SP obj-val)]]\n+\n+The values in `obj-info` appear in the same order as the corresponding `attr`\n+lines, with exactly one value per requested attribute.\n \n If the server does not recognize the OID, the response will be `<oid> SP`\n regardless of the number of attributes requested.\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 8502020083..011acdec09 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -821,15 +821,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \tchar *line_to_split;\n \tstruct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;\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@@ -881,7 +875,6 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,\n \t\tdata->is_remote = 0;\n \t}\n \tdata->skip_object_info = 0;\n-\topt->format = saved_format;\n \n \tfree_fetch_object_info_results(&results);\n \tfree(line_to_split);\ndiff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh\nindex 190c45eefc..e2919aa061 100755\n--- a/t/t1017-cat-file-remote-object-info.sh\n+++ b/t/t1017-cat-file-remote-object-info.sh\n@@ -139,10 +139,10 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\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\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -152,7 +152,7 @@ test_expect_success 'batch-command remote-object-info git:// default filter' '\n \t)\n '\n \n-test_expect_success 'remote-object-info does not change the default format of info' '\n+test_expect_success 'remote-object-info and info can be mixed using the unified default format' '\n \t(\n \t\tset_transport_variables \"$daemon_parent\" &&\n \t\tcd \"$daemon_parent/daemon_client_empty\" &&\n@@ -162,7 +162,7 @@ test_expect_success 'remote-object-info does not change the default format of in\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 \"$hello_oid blob $hello_size\" >>expect &&\n \t\techo \"$local_oid blob $local_size\" >>expect &&\n \n \t\tgit cat-file --batch-command >actual <<-EOF &&\n@@ -209,10 +209,10 @@ test_expect_success 'batch-command -Z remote-object-info git:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -448,10 +448,10 @@ test_expect_success 'batch-command remote-object-info file:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -467,10 +467,10 @@ test_expect_success 'batch-command -Z remote-object-info file:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $tag_size\" >>expect &&\n \n \t\tprintf \"%s\\0\" \"$hello_oid missing\" >>expect &&\n \t\tprintf \"%s\\0\" \"$tree_oid missing\" >>expect &&\n@@ -618,10 +618,10 @@ test_expect_success 'batch-command remote-object-info http:// default filter' '\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+\t\techo \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\techo \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\techo \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\techo \"$tag_oid $tag_type $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@@ -636,10 +636,10 @@ test_expect_success 'batch-command -Z remote-object-info http:// default filter'\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+\t\tprintf \"%s\\0\" \"$hello_oid $hello_type $hello_size\" >expect &&\n+\t\tprintf \"%s\\0\" \"$tree_oid $tree_type $tree_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$commit_oid $commit_type $commit_size\" >>expect &&\n+\t\tprintf \"%s\\0\" \"$tag_oid $tag_type $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-- \n2.54.0\n\n"},{"id":"550089","messageId":"20260808074157.GA2915582@coredump.intra.peff.net","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-0-e5cdaf27a49c@gmail.com","subject":"Re: [PATCH GSoC v6 00/10] cat-file: extend remote-object-info to support %(objecttype)","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-08-08T07:41:57Z","receivedAt":"2026-08-08T07:41:58Z","isPatch":true,"body":"On Sat, Aug 08, 2026 at 02:02:15AM +0200, Pablo Sabater wrote:\n\n> Changes in v6:\n> - Fixed static analysis CI fail with two forward declarations.\n\nSorry, these were my fault. The forward declarations are obviously the\nright solution.\n\n-Peff\n"},{"id":"550096","messageId":"xmqqmruwbn21.fsf@gitster.g","threadId":"66063","inReplyTo":"20260808-objecttype-support-v6-6-e5cdaf27a49c@gmail.com","subject":"Re: [PATCH GSoC v6 06/10] transport: drop remote object-info fields from transport struct","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-08T16:21:26Z","receivedAt":"2026-08-08T16:21:29Z","isPatch":true,"body":"Pablo Sabater <pabloosabaterr@gmail.com> writes:\n\n> Rather than take these as function parameters, we take only the\n> transport object, and expect the caller to have placed the other two\n> into special fields in the transport struct. But this doesn't make much\n> sense. The set of oids and results are really only valid for one\n> request. There is no reason the transport would need to hang on to them\n> outside of the single function call.\n\nThanks for injecting some sanity into the mix.\n\nWith this fixed, are we happy with the entire series by now?\n\nThanks.\n"},{"id":"550105","messageId":"CA+J6zkR5ZkUc8c=xiXgKiAYmbgcoyGfwpgm6aaG0Gog8OVmOjw@mail.gmail.com","threadId":"66063","inReplyTo":"xmqqmruwbn21.fsf@gitster.g","subject":"Re: [PATCH GSoC v6 06/10] transport: drop remote object-info fields from transport struct","fromName":"Chandra Pratap","fromEmail":"chandrapratap3519@gmail.com","sentAt":"2026-08-08T18:59:38Z","receivedAt":"2026-08-08T19:00:05Z","isPatch":true,"body":"On Sat, 8 Aug 2026 at 21:51, Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>\n> > Rather than take these as function parameters, we take only the\n> > transport object, and expect the caller to have placed the other two\n> > into special fields in the transport struct. But this doesn't make much\n> > sense. The set of oids and results are really only valid for one\n> > request. There is no reason the transport would need to hang on to them\n> > outside of the single function call.\n>\n> Thanks for injecting some sanity into the mix.\n>\n> With this fixed, are we happy with the entire series by now?\n\nJust went through the series and everything looks good to me, but I've had\nthe same opinion for the last couple of iterations as well.\n\nThanks,\nChandra.\n"},{"id":"550244","messageId":"CAOLa=ZTrf_WHiRHTjBGAus+YbRsUkbR3dzsW=fgCK0jit6fYzQ@mail.gmail.com","threadId":"66063","inReplyTo":"CA+J6zkR5ZkUc8c=xiXgKiAYmbgcoyGfwpgm6aaG0Gog8OVmOjw@mail.gmail.com","subject":"Re: [PATCH GSoC v6 06/10] transport: drop remote object-info fields from transport struct","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-11T07:43:12Z","receivedAt":"2026-08-11T07:43:14Z","isPatch":true,"body":"Chandra Pratap <chandrapratap3519@gmail.com> writes:\n\n> On Sat, 8 Aug 2026 at 21:51, Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Pablo Sabater <pabloosabaterr@gmail.com> writes:\n>>\n>> > Rather than take these as function parameters, we take only the\n>> > transport object, and expect the caller to have placed the other two\n>> > into special fields in the transport struct. But this doesn't make much\n>> > sense. The set of oids and results are really only valid for one\n>> > request. There is no reason the transport would need to hang on to them\n>> > outside of the single function call.\n>>\n>> Thanks for injecting some sanity into the mix.\n>>\n>> With this fixed, are we happy with the entire series by now?\n>\n> Just went through the series and everything looks good to me, but I've had\n> the same opinion for the last couple of iterations as well.\n>\n> Thanks,\n> Chandra.\n\nSame. This version looks good to me too :)\n"}]}