Files
git/fetch-object-info.c
Pablo Sabater dd1968bf84 cat-file: make remote-object-info allow-list adapt to the server
The static allow-list in expand_atom() is hardcoded to allow only
"objectname" and "objectsize" for remote queries. This works because,
up to this point, servers will either support object-info with name
and size or they do not support them at all.

As object-info gains new capabilities, we cannot expect different
servers with different Git versions to have the same object-info
capabilities. Therefore, the client needs to adapt its allow-list to
what the server advertises.

The client now:

1. Requests the protocol option that the placeholder refers to (i.e.
   "size" for "%(objectsize)").

2. Drops any requested option that the server does not advertise in
   fetch_object_info().

3. Maps the remaining advertised options back to their placeholders and
   populates remote_allowed_atoms.

4. Uses remote_allowed_atoms in expand_atom(), preserving the previous
   behavior for supported placeholders.

For example, if the client requests "%(objectsize) %(objecttype)" and
the server only supports 'size', then the client only requests 'size'.
The server returns the size (i.e "42") "%(objectsize)" is expanded
normally while "%(objecttype)" expands to an empty string:

	"42 "

Note that the empty string expansion is only for known but unsupported
placeholders. "%(objectcolor)" which doesn't exist would die().

This honors what for-each-ref does for known but inapplicable atoms
(placeholders).

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 output is always produced.
If there are no supported attributes, the output is a blank line.

Include "type" in the object_info_options even though the client does
not yet know how to parse the server's "type" capability.

As a result, "type" is always filtered out, allowing the tests to verify
that known but unsupported placeholders expand to an empty string.

Since the filter removes options by swapping with the last element,
the list is no longer kept sorted. Drop the pre-sort in
fetch_object_info_via_pack() and use the unsorted string_list lookup
for the response header. This has no effect in performance as the list
can only be two entries long ('size' and 'type').

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>
2026-07-24 08:49:09 -07:00

157 lines
4.7 KiB
C

#include "git-compat-util.h"
#include "gettext.h"
#include "hex.h"
#include "pkt-line.h"
#include "connect.h"
#include "oid-array.h"
#include "odb.h"
#include "fetch-object-info.h"
#include "string-list.h"
/* Sends object-info command and its arguments into the request buffer. */
static void send_object_info_request(const int fd_out, struct object_info_args *args)
{
struct strbuf req_buf = STRBUF_INIT;
write_command_and_capabilities(&req_buf, "object-info", args->server_options);
if (unsorted_string_list_has_string(args->object_info_options, "size"))
packet_buf_write(&req_buf, "size");
else if (args->object_info_options->nr)
BUG("only size should be in object_info_options");
if (args->oids)
for (size_t i = 0; i < args->oids->nr; i++)
packet_buf_write(&req_buf, "oid %s", oid_to_hex(&args->oids->oid[i]));
packet_buf_flush(&req_buf);
if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)
die_errno(_("unable to write request to remote"));
strbuf_release(&req_buf);
}
static int parse_object_size(const char *s, size_t *res)
{
uintmax_t uim;
if (!s[0] || s[strspn(s, "0123456789")])
return -1;
errno = 0;
uim = strtoumax(s, NULL, 10);
if (errno || uim > SIZE_MAX)
return -1;
*res = uim;
return 0;
}
int fetch_object_info(const enum protocol_version version, struct object_info_args *args,
struct packet_reader *reader, struct object_info *object_info_data,
const int stateless_rpc, const int fd_out)
{
int size_index = -1;
switch (version) {
case protocol_v2:
if (!server_supports_v2("object-info"))
die(_("object-info capability is not enabled on the server"));
/*
* When removing an element from the list it gets swapped by the
* last element, iterate backwards to prevent elements skipping
* evaluation.
*
* object_info_options->nr can be safely casted without overflow
* because the number of options is a small known number (the
* supported placeholders which currently are size and type).
*/
for (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)
if (!server_supports_feature("object-info",
args->object_info_options->items[i].string, 0))
unsorted_string_list_delete_item(args->object_info_options, i, 0);
/*
* Even if no options are left, we still send the oid so we get
* at least an existence check.
*/
send_object_info_request(fd_out, args);
break;
case protocol_v1:
case protocol_v0:
die(_("object-info requires protocol v2"));
case protocol_unknown_version:
BUG("unknown protocol version");
}
for (size_t i = 0; i < args->object_info_options->nr; i++) {
if (packet_reader_read(reader) != PACKET_READ_NORMAL) {
check_stateless_delimiter(stateless_rpc, reader,
"stateless delimiter expected");
return -1;
}
if (!unsorted_string_list_has_string(args->object_info_options, reader->line))
return -1;
if (!strcmp(reader->line, "size")) {
/*
* i is the number of supported options which currently
* is only size. No risk of overflow.
*/
size_index = (int)i;
for (size_t j = 0; j < args->oids->nr; j++)
object_info_data[j].sizep =
xcalloc(1, sizeof(*object_info_data[j].sizep));
} else {
BUG("only size is supported");
}
}
for (size_t i = 0;
packet_reader_read(reader) == PACKET_READ_NORMAL &&
i < args->oids->nr;
i++) {
struct string_list object_info_values = STRING_LIST_INIT_DUP;
string_list_split(&object_info_values, reader->line, " ", -1);
if (strcmp(object_info_values.items[0].string,
oid_to_hex(&args->oids->oid[i])))
die(_("object-info: expected OID: %s, got %s"),
oid_to_hex(&args->oids->oid[i]),
object_info_values.items[0].string);
/*
* If the response is two elements but the second one is an
* empty string, that means that the OID is unrecognized by the
* server.
*/
if (object_info_values.nr >= 2 &&
!strcmp(object_info_values.items[1].string, "")) {
object_info_data[i].unrecognized = 1;
string_list_clear(&object_info_values, 0);
continue;
}
/*
* Because we filter the options to be only the supported by
* the server we expect the server to answer with the same
* number of attributes requested.
*/
if (args->object_info_options->nr + 1 != object_info_values.nr)
die("object-info: unexpected number of attributes: %s",
reader->line);
if (size_index >= 0 &&
parse_object_size(object_info_values.items[size_index + 1].string,
object_info_data[i].sizep))
die("object-info: ref %s has invalid size %s",
object_info_values.items[0].string,
object_info_values.items[size_index + 1].string);
string_list_clear(&object_info_values, 0);
}
check_stateless_delimiter(stateless_rpc, reader, "stateless delimiter expected");
return 0;
}