fetch-object-info: use dedicated struct for the results

fetch_object_info() collects information about N objects, but it stores the results in an array of object_info. That struct holds the extended parameters of read_object_info() (The optional outputs the caller wants filled). Its pointers tell that function where to write the answers for a single object. object_info is not meant to be the final storage, and since fetch_object_info() does not call read_object_info(), there is no reason to use it. Using it means allocating one scalar per object per attribute just to have those pointers somewhere to point at. Add struct fetch_object_info_results. The caller sets the wants_* flags to say what it is interested in, and fetch_object_info() allocates one array per attribute. A set wants_* flag means "asked for", while a non-NULL array means "available". The caller releases the arrays with free_fetch_object_info_results(). The object_info_options string list is no longer needed. Filtering against the server's advertisement now sets local ask_* flags, and send_object_info_request() turns those into the v2 protocol option strings. remote_atom_map[] existed only to map those strings back into atom names, so drop it and build remote_allowed_atoms from the result arrays. free_object_info_contents() loses its only caller and is dropped. Helped-by: Jeff King <peff@peff.net> Helped-by: Junio C Hamano <gitster@pobox.com> Mentored-by: Karthik Nayak <karthik.188@gmail.com> Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Pablo Sabater committed Aug 3, 2026 at 16:39 UTC 3bc51ab99815757aec5a1ccd0d2397d32c217be5
7 files changed +88 -110
builtin/cat-file.c
+15 -44
@@ -31,6 +31,7 @@
31 #include "alias.h"
32 #include "remote.h"
33 #include "transport.h"
34 +#include "fetch-object-info.h"
35
36 /*
37 * Maximum length for a remote URL. While no universal standard exists,
@@ -681,9 +682,8 @@ out:
682
683 static int get_remote_info(int argc,
684 const char **argv,
684 - struct object_info **remote_object_info,
685 - struct oid_array *object_info_oids,
686 - struct string_list *object_info_options)
685 + struct fetch_object_info_results *results,
686 + struct oid_array *object_info_oids)
687 {
688 int retval = 0;
689 struct remote *remote = NULL;
@@ -724,11 +724,9 @@ static int get_remote_info(int argc,
724 goto cleanup;
725 }
726
727 - CALLOC_ARRAY(*remote_object_info, object_info_oids->nr);
727 gtransport->smart_options->object_info_oids = object_info_oids;
728
730 - gtransport->smart_options->object_info_options = object_info_options;
731 - gtransport->smart_options->object_info_data = *remote_object_info;
729 + gtransport->smart_options->object_info_results = results;
730 retval = transport_fetch_object_info(gtransport);
731 cleanup:
732 transport_disconnect(gtransport);
@@ -816,21 +814,6 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,
814 load_mailmap();
815 }
816
819 -struct protocol_placeholder_entry {
820 - const char *option;
821 - const char *atom;
822 -};
823 -
824 -static const struct protocol_placeholder_entry remote_atom_map[] = {
825 - {"size", "objectsize"},
826 - {"type", "objecttype"},
827 - /*
828 - * Add new protocol options here. Even if the server doesn't support
829 - * them the allow_list will drop them if the server doesn't advertise
830 - * them.
831 - */
832 -};
833 -
817 static void parse_cmd_remote_object_info(struct batch_options *opt,
818 const char *line, struct strbuf *output,
819 struct expand_data *data)
@@ -838,9 +821,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
821 int count;
822 const char **argv;
823 char *line_to_split;
841 - struct object_info *remote_object_info = NULL;
824 + struct fetch_object_info_results results = FETCH_OBJECT_INFO_RESULTS_INIT;
825 struct oid_array object_info_oids = OID_ARRAY_INIT;
843 - struct string_list object_info_options = STRING_LIST_INIT_NODUP;
826 const char *saved_format = opt->format;
827
828 if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)
@@ -861,26 +843,23 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
843 MAX_ALLOWED_OBJ_LIMIT);
844
845 if (data->info.sizep)
864 - string_list_append(&object_info_options, "size");
846 + results.wants_size = 1;
847 if (data->info.typep)
866 - string_list_append(&object_info_options, "type");
848 + results.wants_type = 1;
849
868 - if (get_remote_info(count, argv, &remote_object_info,
869 - &object_info_oids, &object_info_options))
850 + if (get_remote_info(count, argv, &results, &object_info_oids))
851 die(_("failed to get object info from the remote: %s"), argv[0]);
852
853 string_list_clear(&data->remote_allowed_atoms, 0);
854 string_list_append(&data->remote_allowed_atoms, "objectname");
874 - for (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)
875 - if (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))
876 - string_list_append(&data->remote_allowed_atoms,
877 - remote_atom_map[i].atom);
855 + if (results.sizes)
856 + string_list_append(&data->remote_allowed_atoms, "objectsize");
857
858 data->skip_object_info = 1;
880 - for (size_t i = 0; i < object_info_oids.nr; i++) {
859 + for (size_t i = 0; i < results.nr; i++) {
860 data->oid = object_info_oids.oid[i];
861
883 - if (remote_object_info[i].unrecognized) {
862 + if (results.unrecognized[i]) {
863 report_object_status(opt, oid_to_hex(&data->oid),
864 &data->oid, "missing");
865 continue;
@@ -890,13 +869,8 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
869 * When reaching here, it means remote-object-info can retrieve
870 * information from server without downloading them.
871 */
893 - if (remote_object_info[i].sizep) {
894 - data->size = *remote_object_info[i].sizep;
895 - }
896 -
897 - if (remote_object_info[i].typep) {
898 - data->type = *remote_object_info[i].typep;
899 - }
872 + if (results.sizes)
873 + data->size = results.sizes[i];
874
875 opt->batch_mode = BATCH_MODE_INFO;
876 data->is_remote = 1;
@@ -906,12 +880,9 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
880 data->skip_object_info = 0;
881 opt->format = saved_format;
882
909 - for (size_t i = 0; i < object_info_oids.nr; i++)
910 - free_object_info_contents(&remote_object_info[i]);
911 - string_list_clear(&object_info_options, 0);
883 + free_fetch_object_info_results(&results);
884 free(line_to_split);
885 free(argv);
914 - free(remote_object_info);
886 oid_array_clear(&object_info_oids);
887 }
888
fetch-object-info.c
+48 -42
@@ -12,16 +12,18 @@
12 static void send_object_info_request(const int fd_out,
13 const struct string_list *server_options,
14 struct oid_array *oids,
15 - struct string_list *object_info_options)
15 + unsigned ask_size,
16 + unsigned ask_type)
17 {
18 struct strbuf req_buf = STRBUF_INIT;
19
20 write_command_and_capabilities(&req_buf, "object-info", server_options);
21
21 - if (unsorted_string_list_has_string(object_info_options, "size"))
22 + if (ask_size)
23 packet_buf_write(&req_buf, "size");
23 - else if (object_info_options->nr)
24 - BUG("only size should be in object_info_options");
24 +
25 + if (ask_type)
26 + packet_buf_write(&req_buf, "type");
27
28 if (oids)
29 for (size_t i = 0; i < oids->nr; i++)
@@ -52,38 +54,39 @@ static int parse_object_size(const char *s, size_t *res)
54 int fetch_object_info(const enum protocol_version version,
55 const struct string_list *server_options,
56 struct oid_array *oids,
55 - struct string_list *object_info_options,
57 struct packet_reader *reader,
57 - struct object_info *object_info_data,
58 - const int stateless_rpc, const int fd_out)
58 + struct fetch_object_info_results *results,
59 + const int stateless_rpc,
60 + const int fd_out)
61 {
60 - size_t i;
62 + unsigned ask_size = 0;
63 + unsigned ask_type = 0;
64 int size_index = -1;
65 + size_t wanted;
66 + size_t i;
67 +
68 + results->nr = oids->nr;
69 + CALLOC_ARRAY(results->unrecognized, results->nr);
70
71 switch (version) {
72 case protocol_v2:
73 if (!server_supports_v2("object-info"))
74 die(_("object-info capability is not enabled on the server"));
67 - /*
68 - * When removing an element from the list it gets swapped by the
69 - * last element, iterate backwards to prevent elements skipping
70 - * evaluation.
71 - *
72 - * object_info_options->nr can be safely casted without overflow
73 - * because the number of options is a small known number (the
74 - * supported placeholders which currently are size and type).
75 - */
76 - for (int i = (int)object_info_options->nr - 1; i >= 0; i--)
77 - if (!server_supports_feature("object-info",
78 - object_info_options->items[i].string, 0))
79 - unsorted_string_list_delete_item(object_info_options, i, 0);
75 +
76 + if (results->wants_size &&
77 + server_supports_feature("object-info", "size", 0))
78 + ask_size = 1;
79 +
80 + if (results->wants_type &&
81 + server_supports_feature("object-info", "type", 0))
82 + ask_type = 1;
83
84 /*
85 * Even if no options are left, we still send the oid so we get
86 * at least an existence check.
87 */
85 - send_object_info_request(fd_out, server_options, oids,
86 - object_info_options);
88 + send_object_info_request(fd_out, server_options, oids, ask_size,
89 + ask_type);
90 break;
91 case protocol_v1:
92 case protocol_v0:
@@ -91,26 +94,22 @@ int fetch_object_info(const enum protocol_version version,
94 case protocol_unknown_version:
95 BUG("unknown protocol version");
96 }
97 + wanted = ask_size + ask_type;
98
95 - for (i = 0; i < object_info_options->nr; i++) {
99 + for (i = 0; i < wanted; i++) {
100 if (packet_reader_read(reader) != PACKET_READ_NORMAL) {
101 check_stateless_delimiter(stateless_rpc, reader,
102 "stateless delimiter expected");
103 return -1;
104 }
105
102 - if (!unsorted_string_list_has_string(object_info_options, reader->line))
103 - return -1;
104 -
106 if (!strcmp(reader->line, "size")) {
106 - /*
107 - * i is the number of supported options which currently
108 - * is only size. No risk of overflow.
109 - */
107 + if (!ask_size)
108 + die(_("object-info: unrequested 'size' attribute"));
109 + if (results->sizes)
110 + die(_("object-info: duplicate 'size' attribute"));
111 size_index = (int)i;
111 - for (size_t j = 0; j < oids->nr; j++)
112 - object_info_data[j].sizep =
113 - xcalloc(1, sizeof(*object_info_data[j].sizep));
112 + CALLOC_ARRAY(results->sizes, results->nr);
113 } else {
114 BUG("only size is supported");
115 }
@@ -137,24 +136,24 @@ int fetch_object_info(const enum protocol_version version,
136 */
137 if (object_info_values.nr >= 2 &&
138 !strcmp(object_info_values.items[1].string, "")) {
140 - object_info_data[i].unrecognized = 1;
139 + results->unrecognized[i] = 1;
140 string_list_clear(&object_info_values, 0);
141 continue;
142 }
143
144 /*
146 - * Because we filter the options to be only the supported by
147 - * the server we expect the server to answer with the same
148 - * number of attributes requested.
145 + * Because we only ask for attributes the server said it
146 + * supports, we expect the answer to have one value per
147 + * requested attribute, plus the OID.
148 */
150 - if (object_info_options->nr + 1 != object_info_values.nr)
149 + if (wanted + 1 != object_info_values.nr)
150 die("object-info: unexpected number of attributes: %s",
151 reader->line);
152
154 - if (size_index >= 0 &&
153 + if (results->sizes &&
154 parse_object_size(object_info_values.items[size_index + 1].string,
156 - object_info_data[i].sizep))
157 - die("object-info: ref %s has invalid size %s",
155 + &results->sizes[i]))
156 + die("object-info: object %s has invalid size %s",
157 object_info_values.items[0].string,
158 object_info_values.items[size_index + 1].string);
159
@@ -169,3 +168,10 @@ int fetch_object_info(const enum protocol_version version,
168
169 return 0;
170 }
171 +
172 +void free_fetch_object_info_results(struct fetch_object_info_results *results)
173 +{
174 + free(results->sizes);
175 + free(results->unrecognized);
176 + memset(results, 0, sizeof(*results));
177 +}
fetch-object-info.h
+21 -7
@@ -4,21 +4,35 @@
4 #include "pkt-line.h"
5 #include "protocol.h"
6
7 -struct object_info;
7 +struct fetch_object_info_results {
8 + size_t *sizes;
9 + uint8_t *unrecognized;
10 + size_t nr;
11 + unsigned wants_size:1;
12 + unsigned wants_type:1;
13 +};
14 +
15 +#define FETCH_OBJECT_INFO_RESULTS_INIT { 0 }
16 +
17 struct oid_array;
18 /*
10 - * Sends git-cat-file object-info command into the request buf and read the
19 + * Sends git-cat-file object-info command into the request buf and reads the
20 * results from packets.
21 *
13 - * Modifies object_info_options, on return it contains only the supported
14 - * options by the server.
22 + * The caller sets the wants_* flags in "results" to indicate which attributes
23 + * it is interested in. On return, "results" holds one array per attribute that
24 + * the server both advertised and answered with. An array left NULL means the
25 + * attribute is not available.
26 + * Release them with free_fetch_object_info_results().
27 */
28 int fetch_object_info(enum protocol_version version,
29 const struct string_list *server_options,
30 struct oid_array *oids,
19 - struct string_list *object_info_options,
31 struct packet_reader *reader,
21 - struct object_info *object_info_data,
22 - int stateless_rpc, int fd_out);
32 + struct fetch_object_info_results *results,
33 + int stateless_rpc,
34 + int fd_out);
35 +
36 +void free_fetch_object_info_results(struct fetch_object_info_results *results);
37
38 #endif /* FETCH_OBJECT_INFO_H */
object-file.c
-10
@@ -1740,13 +1740,3 @@ int odb_transaction_files_begin(struct odb_source *source,
1740
1741 return 0;
1742 }
1743 -
1744 -void free_object_info_contents(struct object_info *object_info)
1745 -{
1746 - if (!object_info)
1747 - return;
1748 - free(object_info->typep);
1749 - free(object_info->sizep);
1750 - free(object_info->disk_sizep);
1751 - free(object_info->delta_base_oid);
1752 -}
odb.h
-3
@@ -635,7 +635,4 @@ void parse_alternates(const char *string,
635 const char *relative_base,
636 struct strvec *out);
637
638 -/* Free pointers inside of object_info, but not object_info itself */
639 -void free_object_info_contents(struct object_info *object_info);
640 -
638 #endif /* ODB_H */
transport.c
+1 -2
@@ -451,9 +451,8 @@ static int fetch_object_info_via_pack(struct transport *transport)
451 ret = fetch_object_info(data->version,
452 transport->server_options,
453 transport->smart_options->object_info_oids,
454 - transport->smart_options->object_info_options,
454 &reader,
456 - data->options.object_info_data,
455 + data->options.object_info_results,
456 transport->stateless_rpc, data->fd[1]);
457
458 close(data->fd[0]);
transport.h
+3 -2
@@ -7,6 +7,8 @@
7 #include "string-list.h"
8 #include "connect.h"
9
10 +struct fetch_object_info_results;
11 +
12 struct git_transport_options {
13 unsigned thin : 1;
14 unsigned keep : 1;
@@ -57,8 +59,7 @@ struct git_transport_options {
59 struct oidset *acked_commits;
60
61 struct oid_array *object_info_oids;
60 - struct object_info *object_info_data;
61 - struct string_list *object_info_options;
62 + struct fetch_object_info_results *object_info_results;
63 };
64
65 enum transport_family {