cat-file: make remote-object-info allow-list dynamic

The static allow-list in expand_atom() is hardcoded to only allow "objectname" and "objectsize" for remote queries. This works because up to this point all servers will either support object-info with name and size or they do not support them at all, but we cannot expect that in a future different servers with different git versions to have the same object-info capabilities. Therefore, the allow_list needs to be dynamic depending on what the server advertises. The client will now: 1. Request the protocol option that the placeholder refers to (i.e. "size" when "%(objectsize)"). 2. Filters the request in fetch_object_info() dropping any option that the server does not advertise. 3. After the fetching, the options that haven't been dropped are the ones fetched and supported by the server, these supported options are mapped and remote_allowed_atoms is populated with the placeholders. 4. expand_atom() checks remote_allowed_atoms with the same behaviour as the static allow_list had. Move object_info_options out of get_remote_info so the caller which has data can select what options will be requested instead of requesting always size. Move batch_object_write() out so there will always be an output even if all the placeholders are not supported by the server (returns an empty line). Include "type" in the object_info_options so once the server supports it, the clients know already how to request it. 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 Jul 10, 2026 at 18:41 UTC cc5851c06efde4a95e0b2b5f7a1f1c571308006e
2 files changed +84 -33
builtin/cat-file.c
+64 -33
@@ -341,13 +341,11 @@ struct expand_data {
341 * Flags about when an object info is being fetched from remote.
342 */
343 unsigned is_remote:1;
344 -};
345 -#define EXPAND_DATA_INIT { .mode = S_IFINVALID, .type = OBJ_BAD }
344
347 -static const char *remote_object_info_atoms[] = {
348 - "objectname",
349 - "objectsize",
345 + struct string_list remote_allowed_atoms;
346 };
347 +#define EXPAND_DATA_INIT { .mode = S_IFINVALID, .type = OBJ_BAD, \
348 + .remote_allowed_atoms = STRING_LIST_INIT_NODUP }
349
350 static int is_atom(const char *atom, const char *s, int slen)
351 {
@@ -359,17 +357,11 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,
357 struct expand_data *data)
358 {
359 if (data->is_remote) {
362 - size_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);
363 - for (i = 0; i < allowed_nr; i++)
364 - if (is_atom(remote_object_info_atoms[i], atom, len))
360 + size_t i;
361 + for (i = 0; i < data->remote_allowed_atoms.nr; i++)
362 + if (is_atom(data->remote_allowed_atoms.items[i].string, atom, len))
363 break;
366 -
367 - /*
368 - * On remote, skip unsupported atoms returning an empty sb,
369 - * honoring how for-each-ref handles known but inapplicable
370 - * atoms (e.g. %(tagger)).
371 - */
372 - if (i == allowed_nr)
364 + if (i == data->remote_allowed_atoms.nr)
365 return 1;
366 }
367
@@ -685,12 +677,12 @@ static int get_remote_info(struct batch_options *opt,
677 int argc,
678 const char **argv,
679 struct object_info **remote_object_info,
688 - struct oid_array *object_info_oids)
680 + struct oid_array *object_info_oids,
681 + struct string_list *object_info_options)
682 {
683 int retval = 0;
684 struct remote *remote = NULL;
685 struct object_id oid;
693 - struct string_list object_info_options = STRING_LIST_INIT_NODUP;
686 struct transport *gtransport;
687
688 /*
@@ -738,15 +730,12 @@ static int get_remote_info(struct batch_options *opt,
730 CALLOC_ARRAY(*remote_object_info, object_info_oids->nr);
731 gtransport->smart_options->object_info_oids = object_info_oids;
732
741 - string_list_append(&object_info_options, "size");
742 -
743 - if (object_info_options.nr > 0) {
744 - gtransport->smart_options->object_info_options = &object_info_options;
733 + if (object_info_options->nr > 0) {
734 + gtransport->smart_options->object_info_options = object_info_options;
735 gtransport->smart_options->object_info_data = *remote_object_info;
736 retval = transport_fetch_object_info(gtransport);
737 }
738 cleanup:
749 - string_list_clear(&object_info_options, 0);
739 transport_disconnect(gtransport);
740 return retval;
741 }
@@ -832,6 +821,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,
821 load_mailmap();
822 }
823
824 +struct protocol_placeholder_entry {
825 + const char *option;
826 + const char *atom;
827 +};
828 +
829 +static const struct protocol_placeholder_entry remote_atom_map[] = {
830 + {"size", "objectsize"},
831 + {"type", "objecttype"},
832 + /*
833 + * Add new protocol options here. Even if the server doesn't support
834 + * them the allow_list will drop them if the server doesn't advertise
835 + * them.
836 + */
837 +};
838 +
839 static void parse_cmd_remote_object_info(struct batch_options *opt,
840 const char *line, struct strbuf *output,
841 struct expand_data *data)
@@ -841,6 +845,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
845 char *line_to_split;
846 struct object_info *remote_object_info = NULL;
847 struct oid_array object_info_oids = OID_ARRAY_INIT;
848 + struct string_list object_info_options = STRING_LIST_INIT_NODUP;
849
850 if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)
851 die(_("remote-object-info command too long"));
@@ -853,32 +858,57 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
858 die(_("remote-object-info supports at most %d objects"),
859 MAX_ALLOWED_OBJ_LIMIT);
860
861 + if (data->info.sizep)
862 + string_list_append(&object_info_options, "size");
863 + if (data->info.typep)
864 + string_list_append(&object_info_options, "type");
865 +
866 if (get_remote_info(opt, count, argv, &remote_object_info,
857 - &object_info_oids))
867 + &object_info_oids, &object_info_options))
868 goto cleanup;
869
870 + string_list_clear(&data->remote_allowed_atoms, 0);
871 + string_list_append(&data->remote_allowed_atoms, "objectname");
872 + for (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)
873 + if (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))
874 + string_list_append(&data->remote_allowed_atoms,
875 + remote_atom_map[i].atom);
876 +
877 data->skip_object_info = 1;
878 for (size_t i = 0; i < object_info_oids.nr; i++) {
879 + int found = 0;
880 data->oid = object_info_oids.oid[i];
881 + /*
882 + * When reaching here, it means remote-object-info can retrieve
883 + * information from server without downloading them.
884 + */
885 if (remote_object_info[i].sizep) {
864 - /*
865 - * When reaching here, it means remote-object-info can retrieve
866 - * information from server without downloading them.
867 - */
886 data->size = *remote_object_info[i].sizep;
869 - opt->batch_mode = BATCH_MODE_INFO;
870 - data->is_remote = 1;
871 - batch_object_write(argv[i + 1], output, opt, data, NULL, 0);
872 - data->is_remote = 0;
873 - } else {
874 - report_object_status(opt, oid_to_hex(&data->oid), &data->oid, "missing");
887 + found = 1;
888 }
889 +
890 + if (remote_object_info[i].typep) {
891 + data->type = *remote_object_info[i].typep;
892 + found = 1;
893 + }
894 +
895 + if (!found && object_info_options.nr > 0) {
896 + report_object_status(opt, oid_to_hex(&data->oid),
897 + &data->oid, "missing");
898 + continue;
899 + }
900 +
901 + opt->batch_mode = BATCH_MODE_INFO;
902 + data->is_remote = 1;
903 + batch_object_write(argv[i + 1], output, opt, data, NULL, 0);
904 + data->is_remote = 0;
905 }
906 data->skip_object_info = 0;
907
908 cleanup:
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);
912 free(line_to_split);
913 free(argv);
914 free(remote_object_info);
@@ -1194,6 +1224,7 @@ static int batch_objects(struct batch_options *opt)
1224 cleanup:
1225 strbuf_release(&input);
1226 strbuf_release(&output);
1227 + string_list_clear(&data.remote_allowed_atoms, 0);
1228 cfg->warn_on_object_refname_ambiguity = save_warning;
1229 return retval;
1230 }
fetch-object-info.c
+20
@@ -55,6 +55,26 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar
55 case protocol_v2:
56 if (!server_supports_v2("object-info"))
57 die(_("object-info capability is not enabled on the server"));
58 + /*
59 + * When removing an element from the list it gets swapped by the
60 + * last element, iterate backwards to prevent elements skipping
61 + * evaluation.
62 + *
63 + * object_info_options->nr can be safely casted without overflow
64 + * beacuse the number of options is a small known number (the
65 + * supported placeholders which currently are size and type).
66 + */
67 + for (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)
68 + if (!server_supports_feature("object-info",
69 + args->object_info_options->items[i].string, 0))
70 + unsorted_string_list_delete_item(args->object_info_options, i, 0);
71 + /*
72 + * If no options are left after the filtering, avoid unnecessary
73 + * request to the server.
74 + */
75 + if (!args->object_info_options->nr)
76 + return 0;
77 +
78 send_object_info_request(fd_out, args);
79 break;
80 case protocol_v1: