Skip to content

Commit dd1968b

Browse files
pabloosabaterrgitster
authored andcommitted
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>
1 parent 0ae93f5 commit dd1968b

5 files changed

Lines changed: 113 additions & 35 deletions

File tree

builtin/cat-file.c

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,20 @@ struct expand_data {
338338
* Flags about when an object info is being fetched from remote.
339339
*/
340340
unsigned is_remote:1;
341-
};
342-
343-
#define EXPAND_DATA_INIT { .mode = S_IFINVALID, .type = OBJ_BAD }
344341

345-
static const char *remote_object_info_atoms[] = {
346-
"objectname",
347-
"objectsize",
342+
/*
343+
* List of atoms (i.e. "objectsize") that the server supports. Built
344+
* from the server's object-info advertised capabilities.
345+
*/
346+
struct string_list remote_allowed_atoms;
348347
};
349348

349+
#define EXPAND_DATA_INIT { \
350+
.mode = S_IFINVALID, \
351+
.type = OBJ_BAD, \
352+
.remote_allowed_atoms = STRING_LIST_INIT_NODUP, \
353+
}
354+
350355
static int is_atom(const char *atom, const char *s, int slen)
351356
{
352357
int alen = strlen(atom);
@@ -357,17 +362,12 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,
357362
struct expand_data *data)
358363
{
359364
if (data->is_remote) {
360-
size_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);
361-
for (i = 0; i < allowed_nr; i++)
362-
if (is_atom(remote_object_info_atoms[i], atom, len))
365+
size_t i;
366+
for (i = 0; i < data->remote_allowed_atoms.nr; i++)
367+
if (is_atom(data->remote_allowed_atoms.items[i].string,
368+
atom, len))
363369
break;
364-
365-
/*
366-
* On remote, skip unsupported atoms returning an empty sb,
367-
* honoring how for-each-ref handles known but inapplicable
368-
* atoms (e.g. %(tagger)).
369-
*/
370-
if (i == allowed_nr)
370+
if (i == data->remote_allowed_atoms.nr)
371371
return 1;
372372
}
373373

@@ -683,12 +683,12 @@ static void batch_one_object(const char *obj_name,
683683
static int get_remote_info(int argc,
684684
const char **argv,
685685
struct object_info **remote_object_info,
686-
struct oid_array *object_info_oids)
686+
struct oid_array *object_info_oids,
687+
struct string_list *object_info_options)
687688
{
688689
int retval = 0;
689690
struct remote *remote = NULL;
690691
struct object_id oid;
691-
struct string_list object_info_options = STRING_LIST_INIT_NODUP;
692692
struct transport *gtransport;
693693

694694
remote = remote_get(argv[0]);
@@ -728,13 +728,10 @@ static int get_remote_info(int argc,
728728
CALLOC_ARRAY(*remote_object_info, object_info_oids->nr);
729729
gtransport->smart_options->object_info_oids = object_info_oids;
730730

731-
string_list_append(&object_info_options, "size");
732-
733-
gtransport->smart_options->object_info_options = &object_info_options;
731+
gtransport->smart_options->object_info_options = object_info_options;
734732
gtransport->smart_options->object_info_data = *remote_object_info;
735733
retval = transport_fetch_object_info(gtransport);
736734
cleanup:
737-
string_list_clear(&object_info_options, 0);
738735
transport_disconnect(gtransport);
739736
return retval;
740737
}
@@ -820,6 +817,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,
820817
load_mailmap();
821818
}
822819

820+
struct protocol_placeholder_entry {
821+
const char *option;
822+
const char *atom;
823+
};
824+
825+
static const struct protocol_placeholder_entry remote_atom_map[] = {
826+
{"size", "objectsize"},
827+
{"type", "objecttype"},
828+
/*
829+
* Add new protocol options here. Even if the server doesn't support
830+
* them the allow_list will drop them if the server doesn't advertise
831+
* them.
832+
*/
833+
};
834+
823835
static void parse_cmd_remote_object_info(struct batch_options *opt,
824836
const char *line, struct strbuf *output,
825837
struct expand_data *data)
@@ -829,6 +841,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
829841
char *line_to_split;
830842
struct object_info *remote_object_info = NULL;
831843
struct oid_array object_info_oids = OID_ARRAY_INIT;
844+
struct string_list object_info_options = STRING_LIST_INIT_NODUP;
832845
const char *saved_format = opt->format;
833846

834847
if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)
@@ -848,10 +861,22 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
848861
die(_("remote-object-info supports at most %d objects"),
849862
MAX_ALLOWED_OBJ_LIMIT);
850863

864+
if (data->info.sizep)
865+
string_list_append(&object_info_options, "size");
866+
if (data->info.typep)
867+
string_list_append(&object_info_options, "type");
868+
851869
if (get_remote_info(count, argv, &remote_object_info,
852-
&object_info_oids))
870+
&object_info_oids, &object_info_options))
853871
die(_("failed to get object info from the remote: %s"), argv[0]);
854872

873+
string_list_clear(&data->remote_allowed_atoms, 0);
874+
string_list_append(&data->remote_allowed_atoms, "objectname");
875+
for (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)
876+
if (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))
877+
string_list_append(&data->remote_allowed_atoms,
878+
remote_atom_map[i].atom);
879+
855880
data->skip_object_info = 1;
856881
for (size_t i = 0; i < object_info_oids.nr; i++) {
857882
data->oid = object_info_oids.oid[i];
@@ -862,25 +887,29 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
862887
continue;
863888
}
864889

890+
/*
891+
* When reaching here, it means remote-object-info can retrieve
892+
* information from server without downloading them.
893+
*/
865894
if (remote_object_info[i].sizep) {
866-
/*
867-
* When reaching here, it means remote-object-info can retrieve
868-
* information from server without downloading them.
869-
*/
870895
data->size = *remote_object_info[i].sizep;
871-
opt->batch_mode = BATCH_MODE_INFO;
872-
data->is_remote = 1;
873-
batch_object_write(argv[i + 1], output, opt, data, NULL, 0);
874-
data->is_remote = 0;
875-
} else {
876-
report_object_status(opt, oid_to_hex(&data->oid), &data->oid, "missing");
877896
}
897+
898+
if (remote_object_info[i].typep) {
899+
data->type = *remote_object_info[i].typep;
900+
}
901+
902+
opt->batch_mode = BATCH_MODE_INFO;
903+
data->is_remote = 1;
904+
batch_object_write(argv[i + 1], output, opt, data, NULL, 0);
905+
data->is_remote = 0;
878906
}
879907
data->skip_object_info = 0;
880908
opt->format = saved_format;
881909

882910
for (size_t i = 0; i < object_info_oids.nr; i++)
883911
free_object_info_contents(&remote_object_info[i]);
912+
string_list_clear(&object_info_options, 0);
884913
free(line_to_split);
885914
free(argv);
886915
free(remote_object_info);
@@ -1200,6 +1229,7 @@ static int batch_objects(struct batch_options *opt)
12001229
cleanup:
12011230
strbuf_release(&input);
12021231
strbuf_release(&output);
1232+
string_list_clear(&data.remote_allowed_atoms, 0);
12031233
cfg->warn_on_object_refname_ambiguity = save_warning;
12041234
return retval;
12051235
}

fetch-object-info.c

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,24 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar
5555
case protocol_v2:
5656
if (!server_supports_v2("object-info"))
5757
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+
* because 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+
/*
73+
* Even if no options are left, we still send the oid so we get
74+
* at least an existence check.
75+
*/
5876
send_object_info_request(fd_out, args);
5977
break;
6078
case protocol_v1:
@@ -71,7 +89,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar
7189
return -1;
7290
}
7391

74-
if (!string_list_has_string(args->object_info_options, reader->line))
92+
if (!unsorted_string_list_has_string(args->object_info_options, reader->line))
7593
return -1;
7694

7795
if (!strcmp(reader->line, "size")) {

fetch-object-info.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ struct object_info;
1414
/*
1515
* Sends git-cat-file object-info command into the request buf and read the
1616
* results from packets.
17+
*
18+
* Modifies args->object_info_options, on return it contains only the supported
19+
* options by the server.
1720
*/
1821
int fetch_object_info(enum protocol_version version, struct object_info_args *args,
1922
struct packet_reader *reader, struct object_info *object_info_data,

t/t1017-cat-file-remote-object-info.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,34 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '
271271
)
272272
'
273273

274+
test_expect_success 'requesting only objectname echoes back' '
275+
(
276+
set_transport_variables "$daemon_parent" &&
277+
cd "$daemon_parent/daemon_client_empty" &&
278+
279+
echo $hello_oid >expect &&
280+
git cat-file --batch-command="%(objectname)" >actual <<-EOF &&
281+
remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
282+
EOF
283+
test_cmp expect actual
284+
)
285+
'
286+
287+
test_expect_success 'objectname goes through existence check' '
288+
(
289+
set_transport_variables "$daemon_parent" &&
290+
cd "$daemon_parent/daemon_client_empty" &&
291+
292+
echo "$unstored_oid missing" >expect &&
293+
294+
git cat-file --batch-command="%(objectname)" >actual <<-EOF &&
295+
remote-object-info "$GIT_DAEMON_URL/parent" $unstored_oid
296+
EOF
297+
298+
test_cmp expect actual
299+
)
300+
'
301+
274302
# Test --batch-command remote-object-info with 'git://' and
275303
# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
276304
test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '

transport.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,6 @@ static int fetch_object_info_via_pack(struct transport *transport)
443443
args.server_options = transport->server_options;
444444
args.oids = transport->smart_options->object_info_oids;
445445
args.object_info_options = transport->smart_options->object_info_options;
446-
string_list_sort(args.object_info_options);
447446

448447
connect_setup(transport, 0);
449448
packet_reader_init(&reader, data->fd[0], NULL, 0,

0 commit comments

Comments
 (0)