cat-file: add remote-object-info to batch-command

Since the info command in cat-file --batch-command prints object info for a given object, it is natural to add another command in cat-file --batch-command to print object info for a given object from a remote. Add remote-object-info command to cat-file --batch-command. While info takes object ids one at a time, this creates overhead when making requests to a server. So remote-object-info instead can take multiple object ids at once. The cat-file --batch-command command is generally implemented in the following manner: - Receive and parse input from user - Call respective function attached to command - Get object info, print object info In --buffer mode, this changes to: - Receive and parse input from user - Store respective function attached to command in a queue - After flush, loop through commands in queue - Call respective function attached to command - Get object info, print object info Notice how the getting and printing of object info is accomplished one at a time. As described above, this creates a problem for making requests to a server. Therefore, remote-object-info is implemented in the following manner: - Receive and parse input from user If command is remote-object-info: - Get object info from remote - Loop through and print each object info Else: - Call respective function attached to command - Parse input, get object info, print object info And finally for --buffer mode remote-object-info: - Receive and parse input from user - Store respective function attached to command in a queue - After flush, loop through commands in queue: If command is remote-object-info: - Get object info from remote - Loop through and print each object info Else: - Call respective function attached to command - Get object info, print object info To summarize, remote-object-info gets object info from the remote and then loops through the object info passed in, printing the info. In order for remote-object-info to avoid remote communication overhead in the non-buffer mode, the objects are passed in as such: remote-object-info <remote> <oid> <oid> ... <oid> rather than remote-object-info <remote> <oid> remote-object-info <remote> <oid> ... remote-object-info <remote> <oid> Placeholders in the format are validated against an allow-list of the atoms the remote path supports: "objectname" and "objectsize". Unsupported atoms expand to an empty string, honoring how for-each-ref handles known but inapplicable atoms. Without this, atoms like %(objecttype) would mark data->info.typep and because the server only sends size, type_name() would later crash. As extra safety, even outside of the remote path, initialize expand_data's type to OBJ_BAD and handle type_name() returning NULL. Helped-by: Jonathan Tan <jonathantanmy@google.com> Helped-by: Christian Couder <chriscool@tuxfamily.org> Mentored-by: Karthik Nayak <karthik.188@gmail.com> Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com> Signed-off-by: Calvin Wan <calvinwan@google.com> Signed-off-by: Eric Ju <eric.peijian@gmail.com> [pablo: added the atom allow-list validation] Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Eric Ju committed Jul 24, 2026 at 12:54 UTC 0ae93f56ecd792d227149161b57f292a1c909d0c
6 files changed +930 -7
Documentation/git-cat-file.adoc
+21 -2
@@ -169,6 +169,13 @@ info <object>::
169 Print object info for object reference `<object>`. This corresponds to the
170 output of `--batch-check`.
171
172 +remote-object-info <remote> <object>...::
173 + Print object info for object references `<object>` at specified
174 + `<remote>` without downloading objects from the remote.
175 + Raise an error when the `object-info` capability is not supported by the remote.
176 + Raise an error when no object references are provided.
177 + This command may be combined with `--buffer`.
178 +
179 flush::
180 Used with `--buffer` to execute all preceding commands that were issued
181 since the beginning or since the last flush was issued. When `--buffer`
@@ -301,7 +308,8 @@ one per line, and print information based on the command given. With
308 `--batch-command`, the `info` command followed by an object will print
309 information about the object the same way `--batch-check` would, and the
310 `contents` command followed by an object prints contents in the same way
304 -`--batch` would.
311 +`--batch` would. The `remote-object-info` command followed by a remote and
312 +object IDs prints object info from the remote without downloading the objects.
313
314 You can specify the information shown for each object by using a custom
315 `<format>`. The `<format>` is copied literally to stdout for each
@@ -340,8 +348,15 @@ newline. The available atoms are:
348 after that first run of whitespace (i.e., the "rest" of the
349 line) are output in place of the `%(rest)` atom.
350
351 +The command `remote-object-info` only supports the `%(objectname)` and
352 +`%(objectsize)` placeholders. See `CAVEATS` below for more information.
353 +
354 If no format is specified, the default format is `%(objectname)
344 -%(objecttype) %(objectsize)`.
355 +%(objecttype) %(objectsize)`, except for `remote-object-info` commands which
356 +use `%(objectname) %(objectsize)` because `%(objecttype)` is not supported yet.
357 +
358 +WARNING: When "%(objecttype)" is supported, the default format WILL be unified,
359 +so DO NOT RELY on the current default format to stay the same!!!
360
361 If `--batch` is specified, or if `--batch-command` is used with the `contents`
362 command, the object information is followed by the object contents (consisting
@@ -438,6 +453,10 @@ scripting purposes.
453 CAVEATS
454 -------
455
456 +Note that only `%(objectname)` and `%(objectsize)` are currently
457 +supported by the `remote-object-info` command. Using any other placeholder in
458 +the format string will return an empty string in its position.
459 +
460 Note that the sizes of objects on disk are reported accurately, but care
461 should be taken in drawing conclusions about which refs or objects are
462 responsible for disk usage. The size of a packed non-delta object may be
builtin/cat-file.c
+176 -5
@@ -29,6 +29,22 @@
29 #include "promisor-remote.h"
30 #include "mailmap.h"
31 #include "write-or-die.h"
32 +#include "alias.h"
33 +#include "remote.h"
34 +#include "transport.h"
35 +
36 +/*
37 + * Maximum length for a remote URL. While no universal standard exists,
38 + * 8K is assumed to be a reasonable limit.
39 + */
40 +#define MAX_REMOTE_URL_LEN (8 * 1024)
41 +
42 +/* Maximum number of objects allowed in a single remote-object-info request. */
43 +#define MAX_ALLOWED_OBJ_LIMIT 10000
44 +
45 +/* Maximum input size permitted for the remote-object-info command. */
46 +#define MAX_REMOTE_OBJ_INFO_LINE \
47 + (MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))
48
49 enum batch_mode {
50 BATCH_MODE_CONTENTS,
@@ -317,8 +333,19 @@ struct expand_data {
333 * optimized out.
334 */
335 unsigned skip_object_info : 1;
336 +
337 + /*
338 + * Flags about when an object info is being fetched from remote.
339 + */
340 + unsigned is_remote:1;
341 +};
342 +
343 +#define EXPAND_DATA_INIT { .mode = S_IFINVALID, .type = OBJ_BAD }
344 +
345 +static const char *remote_object_info_atoms[] = {
346 + "objectname",
347 + "objectsize",
348 };
321 -#define EXPAND_DATA_INIT { .mode = S_IFINVALID }
349
350 static int is_atom(const char *atom, const char *s, int slen)
351 {
@@ -329,14 +356,31 @@ static int is_atom(const char *atom, const char *s, int slen)
356 static int expand_atom(struct strbuf *sb, const char *atom, int len,
357 struct expand_data *data)
358 {
359 + 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))
363 + 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)
371 + return 1;
372 + }
373 +
374 if (is_atom("objectname", atom, len)) {
375 if (!data->mark_query)
376 strbuf_add_oid_hex(sb, &data->oid);
377 } else if (is_atom("objecttype", atom, len)) {
336 - if (data->mark_query)
378 + if (data->mark_query) {
379 data->info.typep = &data->type;
338 - else
339 - strbuf_addstr(sb, type_name(data->type));
380 + } else {
381 + const char *t = type_name(data->type);
382 + strbuf_addstr(sb, t ? t : "");
383 + }
384 } else if (is_atom("objectsize", atom, len)) {
385 if (data->mark_query)
386 data->info.sizep = &data->size;
@@ -636,6 +680,65 @@ out:
680 object_context_release(&ctx);
681 }
682
683 +static int get_remote_info(int argc,
684 + const char **argv,
685 + struct object_info **remote_object_info,
686 + struct oid_array *object_info_oids)
687 +{
688 + int retval = 0;
689 + struct remote *remote = NULL;
690 + struct object_id oid;
691 + struct string_list object_info_options = STRING_LIST_INIT_NODUP;
692 + struct transport *gtransport;
693 +
694 + remote = remote_get(argv[0]);
695 + if (!remote)
696 + die(_("must supply valid remote when using remote-object-info"));
697 +
698 + oid_array_clear(object_info_oids);
699 + for (size_t i = 1; i < argc; i++) {
700 + if (get_oid_hex(argv[i], &oid)) {
701 + size_t len = strlen(argv[i]);
702 +
703 + if (len < the_hash_algo->hexsz && len >= 4) {
704 + size_t j;
705 + for (j = 0; j < len; j++)
706 + if (!isxdigit(argv[i][j]))
707 + break;
708 + if (j == len)
709 + die(_("remote-object-info does not support "
710 + "short oids, %d characters required"),
711 + (int)the_hash_algo->hexsz);
712 + }
713 + die(_("not a valid object name '%s'"), argv[i]);
714 + }
715 + oid_array_append(object_info_oids, &oid);
716 + }
717 +
718 + if (!object_info_oids->nr)
719 + die(_("remote-object-info requires objects"));
720 +
721 + gtransport = transport_get(remote, NULL);
722 +
723 + if (!gtransport->smart_options) {
724 + retval = -1;
725 + goto cleanup;
726 + }
727 +
728 + CALLOC_ARRAY(*remote_object_info, object_info_oids->nr);
729 + gtransport->smart_options->object_info_oids = object_info_oids;
730 +
731 + string_list_append(&object_info_options, "size");
732 +
733 + gtransport->smart_options->object_info_options = &object_info_options;
734 + gtransport->smart_options->object_info_data = *remote_object_info;
735 + retval = transport_fetch_object_info(gtransport);
736 +cleanup:
737 + string_list_clear(&object_info_options, 0);
738 + transport_disconnect(gtransport);
739 + return retval;
740 +}
741 +
742 struct object_cb_data {
743 struct batch_options *opt;
744 struct expand_data *expand;
@@ -717,6 +820,73 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,
820 load_mailmap();
821 }
822
823 +static void parse_cmd_remote_object_info(struct batch_options *opt,
824 + const char *line, struct strbuf *output,
825 + struct expand_data *data)
826 +{
827 + int count;
828 + const char **argv;
829 + char *line_to_split;
830 + struct object_info *remote_object_info = NULL;
831 + struct oid_array object_info_oids = OID_ARRAY_INIT;
832 + const char *saved_format = opt->format;
833 +
834 + if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)
835 + die(_("remote-object-info command too long"));
836 + /*
837 + * TODO: Use the default format once %(objecttype) is supported.
838 + */
839 + if (!opt->format)
840 + opt->format = "%(objectname) %(objectsize)";
841 +
842 + line_to_split = xstrdup(line);
843 + count = split_cmdline(line_to_split, &argv);
844 + if (count < 0)
845 + die(_("remote-object-info: failed to parse command line: %s"),
846 + split_cmdline_strerror(count));
847 + if (count - 1 > MAX_ALLOWED_OBJ_LIMIT)
848 + die(_("remote-object-info supports at most %d objects"),
849 + MAX_ALLOWED_OBJ_LIMIT);
850 +
851 + if (get_remote_info(count, argv, &remote_object_info,
852 + &object_info_oids))
853 + die(_("failed to get object info from the remote: %s"), argv[0]);
854 +
855 + data->skip_object_info = 1;
856 + for (size_t i = 0; i < object_info_oids.nr; i++) {
857 + data->oid = object_info_oids.oid[i];
858 +
859 + if (remote_object_info[i].unrecognized) {
860 + report_object_status(opt, oid_to_hex(&data->oid),
861 + &data->oid, "missing");
862 + continue;
863 + }
864 +
865 + 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 + */
870 + 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");
877 + }
878 + }
879 + data->skip_object_info = 0;
880 + opt->format = saved_format;
881 +
882 + for (size_t i = 0; i < object_info_oids.nr; i++)
883 + free_object_info_contents(&remote_object_info[i]);
884 + free(line_to_split);
885 + free(argv);
886 + free(remote_object_info);
887 + oid_array_clear(&object_info_oids);
888 +}
889 +
890 static void dispatch_calls(struct batch_options *opt,
891 struct strbuf *output,
892 struct expand_data *data,
@@ -747,9 +917,10 @@ static const struct parse_cmd {
917 unsigned takes_args;
918 } commands[] = {
919 { "contents", parse_cmd_contents, 1 },
750 - { "info", parse_cmd_info, 1 },
920 { "flush", NULL, 0 },
921 + { "info", parse_cmd_info, 1 },
922 { "mailmap", parse_cmd_mailmap, 1 },
923 + { "remote-object-info", parse_cmd_remote_object_info, 1 },
924 };
925
926 static void batch_objects_command(struct batch_options *opt,
object-file.c
+10
@@ -1698,3 +1698,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
1698
1699 return &transaction->base;
1700 }
1701 +
1702 +void free_object_info_contents(struct object_info *object_info)
1703 +{
1704 + if (!object_info)
1705 + return;
1706 + free(object_info->typep);
1707 + free(object_info->sizep);
1708 + free(object_info->disk_sizep);
1709 + free(object_info->delta_base_oid);
1710 +}
odb.h
+3
@@ -623,4 +623,7 @@ void parse_alternates(const char *string,
623 const char *relative_base,
624 struct strvec *out);
625
626 +/* Free pointers inside of object_info, but not object_info itself */
627 +void free_object_info_contents(struct object_info *object_info);
628 +
629 #endif /* ODB_H */
t/meson.build
+1
@@ -171,6 +171,7 @@ integration_tests = [
171 't1014-read-tree-confusing.sh',
172 't1015-read-index-unmerged.sh',
173 't1016-compatObjectFormat.sh',
174 + 't1017-cat-file-remote-object-info.sh',
175 't1020-subdirectory.sh',
176 't1022-read-tree-partial-clone.sh',
177 't1050-large.sh',
t/t1017-cat-file-remote-object-info.sh new
+719
@@ -0,0 +1,719 @@
1 +#!/bin/sh
2 +
3 +test_description='git cat-file --batch-command with remote-object-info command'
4 +
5 +. ./test-lib.sh
6 +. "$TEST_DIRECTORY"/lib-cat-file.sh
7 +
8 +hello_content="Hello World"
9 +hello_size=$(strlen "$hello_content")
10 +hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin)
11 +hello_short_oid=$(git rev-parse --short "$hello_oid")
12 +
13 +unstored_content="Hello Git"
14 +unstored_oid=$(echo_without_newline "$unstored_content" | git hash-object --stdin)
15 +
16 +# This is how we get 13:
17 +# 13 = <file mode> + <a_space> + <file name> + <a_null>, where
18 +# file mode is 100644, which is 6 characters;
19 +# file name is hello, which is 5 characters
20 +# a space is 1 character and a null is 1 character
21 +tree_size=$(($(test_oid rawsz) + 13))
22 +
23 +commit_message="Initial commit"
24 +
25 +# This is how we get 137:
26 +# 137 = <tree header> + <a_space> + <a newline> +
27 +# <Author line> + <a newline> +
28 +# <Committer line> + <a newline> +
29 +# <a newline> +
30 +# <commit message length>
31 +# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,
32 +# to get 177, 2. then deduct 40 hex characters to get 137
33 +commit_size=$(($(test_oid hexsz) + 137))
34 +
35 +tag_header_without_oid="type blob
36 +tag hellotag
37 +tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
38 +tag_header_without_timestamp="object $hello_oid
39 +$tag_header_without_oid"
40 +tag_description="This is a tag"
41 +tag_content="$tag_header_without_timestamp 0 +0000
42 +
43 +$tag_description"
44 +
45 +tag_oid=$(echo_without_newline "$tag_content" | git hash-object -t tag --stdin -w)
46 +tag_size=$(strlen "$tag_content")
47 +
48 +set_transport_variables () {
49 + hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin)
50 + tree_oid=$(git -C "$1" write-tree)
51 + commit_oid=$(echo_without_newline "$commit_message" | git -C "$1" commit-tree $tree_oid)
52 + tag_oid=$(echo_without_newline "$tag_content" | git -C "$1" hash-object -t tag --stdin -w)
53 + tag_size=$(strlen "$tag_content")
54 +}
55 +
56 +# This section tests --batch-command with remote-object-info command
57 +# Since "%(objecttype)" is currently not supported by the command remote-object-info ,
58 +# the filters are set to "%(objectname) %(objectsize)" in some test cases.
59 +
60 +# Test --batch-command remote-object-info with 'git://' transport with
61 +# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability
62 +. "$TEST_DIRECTORY"/lib-git-daemon.sh
63 +start_git_daemon --export-all
64 +daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent
65 +
66 +test_expect_success 'create repo to be served by git-daemon' '
67 + git init "$daemon_parent" &&
68 + echo_without_newline "$hello_content" > $daemon_parent/hello &&
69 + git -C "$daemon_parent" update-index --add hello &&
70 + git -C "$daemon_parent" config transfer.advertiseobjectinfo true &&
71 + git clone "$GIT_DAEMON_URL/parent" -n "$daemon_parent/daemon_client_empty"
72 +'
73 +
74 +test_expect_success 'batch-command remote-object-info git://' '
75 + (
76 + set_transport_variables "$daemon_parent" &&
77 + cd "$daemon_parent/daemon_client_empty" &&
78 +
79 + # These results prove remote-object-info can get object info from the remote
80 + echo "$hello_oid $hello_size" >expect &&
81 + echo "$tree_oid $tree_size" >>expect &&
82 + echo "$commit_oid $commit_size" >>expect &&
83 + echo "$tag_oid $tag_size" >>expect &&
84 +
85 + # These results prove remote-object-info did not download objects from the remote
86 + echo "$hello_oid missing" >>expect &&
87 + echo "$tree_oid missing" >>expect &&
88 + echo "$commit_oid missing" >>expect &&
89 + echo "$tag_oid missing" >>expect &&
90 +
91 + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
92 + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
93 + remote-object-info "$GIT_DAEMON_URL/parent" $tree_oid
94 + remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid
95 + remote-object-info "$GIT_DAEMON_URL/parent" $tag_oid
96 + info $hello_oid
97 + info $tree_oid
98 + info $commit_oid
99 + info $tag_oid
100 + EOF
101 + test_cmp expect actual
102 + )
103 +'
104 +
105 +test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '
106 + (
107 + set_transport_variables "$daemon_parent" &&
108 + cd "$daemon_parent/daemon_client_empty" &&
109 +
110 + # These results prove remote-object-info can get object info from the remote
111 + echo "$hello_oid $hello_size" >expect &&
112 + echo "$tree_oid $tree_size" >>expect &&
113 + echo "$commit_oid $commit_size" >>expect &&
114 + echo "$tag_oid $tag_size" >>expect &&
115 +
116 + # These results prove remote-object-info did not download objects from the remote
117 + echo "$hello_oid missing" >>expect &&
118 + echo "$tree_oid missing" >>expect &&
119 + echo "$commit_oid missing" >>expect &&
120 + echo "$tag_oid missing" >>expect &&
121 +
122 + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
123 + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid $commit_oid $tag_oid
124 + info $hello_oid
125 + info $tree_oid
126 + info $commit_oid
127 + info $tag_oid
128 + EOF
129 + test_cmp expect actual
130 + )
131 +'
132 +
133 +test_expect_success 'batch-command remote-object-info git:// default filter' '
134 + (
135 + set_transport_variables "$daemon_parent" &&
136 + cd "$daemon_parent/daemon_client_empty" &&
137 +
138 + echo "$hello_oid $hello_size" >expect &&
139 + echo "$tree_oid $tree_size" >>expect &&
140 + echo "$commit_oid $commit_size" >>expect &&
141 + echo "$tag_oid $tag_size" >>expect &&
142 +
143 + git cat-file --batch-command >actual <<-EOF &&
144 + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid
145 + remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid $tag_oid
146 + EOF
147 + test_cmp expect actual
148 + )
149 +'
150 +
151 +test_expect_success 'remote-object-info does not change the default format of info' '
152 + (
153 + set_transport_variables "$daemon_parent" &&
154 + cd "$daemon_parent/daemon_client_empty" &&
155 +
156 + local_content="local object" &&
157 + local_oid=$(echo_without_newline "$local_content" | git hash-object -w --stdin) &&
158 + local_size=$(strlen "$local_content") &&
159 +
160 + echo "$local_oid blob $local_size" >expect &&
161 + echo "$hello_oid $hello_size" >>expect &&
162 + echo "$local_oid blob $local_size" >>expect &&
163 +
164 + git cat-file --batch-command >actual <<-EOF &&
165 + info $local_oid
166 + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
167 + info $local_oid
168 + EOF
169 + test_cmp expect actual
170 + )
171 +'
172 +
173 +test_expect_success 'batch-command --buffer remote-object-info git://' '
174 + (
175 + set_transport_variables "$daemon_parent" &&
176 + cd "$daemon_parent/daemon_client_empty" &&
177 +
178 + # These results prove remote-object-info can get object info from the remote
179 + echo "$hello_oid $hello_size" >expect &&
180 + echo "$tree_oid $tree_size" >>expect &&
181 + echo "$commit_oid $commit_size" >>expect &&
182 + echo "$tag_oid $tag_size" >>expect &&
183 +
184 + # These results prove remote-object-info did not download objects from the remote
185 + echo "$hello_oid missing" >>expect &&
186 + echo "$tree_oid missing" >>expect &&
187 + echo "$commit_oid missing" >>expect &&
188 + echo "$tag_oid missing" >>expect &&
189 +
190 + git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF &&
191 + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid
192 + remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid $tag_oid
193 + info $hello_oid
194 + info $tree_oid
195 + info $commit_oid
196 + info $tag_oid
197 + flush
198 + EOF
199 + test_cmp expect actual
200 + )
201 +'
202 +
203 +test_expect_success 'batch-command -Z remote-object-info git:// default filter' '
204 + (
205 + set_transport_variables "$daemon_parent" &&
206 + cd "$daemon_parent/daemon_client_empty" &&
207 +
208 + printf "%s\0" "$hello_oid $hello_size" >expect &&
209 + printf "%s\0" "$tree_oid $tree_size" >>expect &&
210 + printf "%s\0" "$commit_oid $commit_size" >>expect &&
211 + printf "%s\0" "$tag_oid $tag_size" >>expect &&
212 +
213 + printf "%s\0" "$hello_oid missing" >>expect &&
214 + printf "%s\0" "$tree_oid missing" >>expect &&
215 + printf "%s\0" "$commit_oid missing" >>expect &&
216 + printf "%s\0" "$tag_oid missing" >>expect &&
217 +
218 + batch_input="remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid
219 +remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid
220 +info $hello_oid
221 +info $tree_oid
222 +info $commit_oid
223 +info $tag_oid
224 +" &&
225 + echo_without_newline_nul "$batch_input" >commands_null_delimited &&
226 +
227 + git cat-file --batch-command -Z < commands_null_delimited >actual &&
228 + test_cmp expect actual
229 + )
230 +'
231 +
232 +test_expect_success 'remote-object-info does not support short oids' '
233 + (
234 + set_transport_variables "$daemon_parent" &&
235 + cd "$daemon_parent/daemon_client_empty" &&
236 +
237 + test_must_fail git cat-file --batch-command 2>err <<-EOF &&
238 + remote-object-info $GIT_DAEMON_URL/parent $hello_short_oid
239 + EOF
240 + test_grep "does not support short oids" err
241 + )
242 +'
243 +
244 +test_expect_success 'remote-object-info does not die on missing oid like info' '
245 + (
246 + set_transport_variables "$daemon_parent" &&
247 + cd "$daemon_parent/daemon_client_empty" &&
248 +
249 + git cat-file --batch-command >local <<-EOF &&
250 + info $unstored_oid
251 + EOF
252 + git cat-file --batch-command >remote <<-EOF &&
253 + remote-object-info $GIT_DAEMON_URL/parent $unstored_oid
254 + EOF
255 + test_cmp local remote
256 + )
257 +'
258 +
259 +# This tests depends on %(objecttype) not being supported yet, once supported
260 +# it needs to be updated.
261 +test_expect_success 'unsupported placeholder on remote returns empty string' '
262 + (
263 + set_transport_variables "$daemon_parent" &&
264 + cd "$daemon_parent/daemon_client_empty" &&
265 +
266 + echo "" >expect &&
267 + git cat-file --batch-command="%(objecttype)" >actual <<-EOF &&
268 + remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
269 + EOF
270 + test_cmp expect actual
271 + )
272 +'
273 +
274 +# Test --batch-command remote-object-info with 'git://' and
275 +# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
276 +test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '
277 + (
278 + git -C "$daemon_parent" config transfer.advertiseobjectinfo false &&
279 + set_transport_variables "$daemon_parent" &&
280 +
281 + test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
282 + remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid
283 + EOF
284 + test_grep "object-info capability is not enabled on the server" err &&
285 +
286 + # revert server state back
287 + git -C "$daemon_parent" config transfer.advertiseobjectinfo true
288 +
289 + )
290 +'
291 +
292 +stop_git_daemon
293 +
294 +# Test --batch-command remote-object-info with 'file://' transport with
295 +# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability
296 +# shellcheck disable=SC2016
297 +test_expect_success 'create repo to be served by file:// transport' '
298 + git init server &&
299 + git -C server config protocol.version 2 &&
300 + git -C server config transfer.advertiseobjectinfo true &&
301 + echo_without_newline "$hello_content" > server/hello &&
302 + git -C server update-index --add hello &&
303 + git clone -n "file://$(pwd)/server" file_client_empty
304 +'
305 +
306 +test_expect_success 'batch-command remote-object-info file://' '
307 + (
308 + set_transport_variables "server" &&
309 + server_path="$(pwd)/server" &&
310 + cd file_client_empty &&
311 +
312 + # These results prove remote-object-info can get object info from the remote
313 + echo "$hello_oid $hello_size" >expect &&
314 + echo "$tree_oid $tree_size" >>expect &&
315 + echo "$commit_oid $commit_size" >>expect &&
316 + echo "$tag_oid $tag_size" >>expect &&
317 +
318 + # These results prove remote-object-info did not download objects from the remote
319 + echo "$hello_oid missing" >>expect &&
320 + echo "$tree_oid missing" >>expect &&
321 + echo "$commit_oid missing" >>expect &&
322 + echo "$tag_oid missing" >>expect &&
323 +
324 + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
325 + remote-object-info "file://${server_path}" $hello_oid
326 + remote-object-info "file://${server_path}" $tree_oid
327 + remote-object-info "file://${server_path}" $commit_oid
328 + remote-object-info "file://${server_path}" $tag_oid
329 + info $hello_oid
330 + info $tree_oid
331 + info $commit_oid
332 + info $tag_oid
333 + EOF
334 + test_cmp expect actual
335 + )
336 +'
337 +
338 +test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '
339 + (
340 + set_transport_variables "server" &&
341 + server_path="$(pwd)/server" &&
342 + cd file_client_empty &&
343 +
344 + # These results prove remote-object-info can get object info from the remote
345 + echo "$hello_oid $hello_size" >expect &&
346 + echo "$tree_oid $tree_size" >>expect &&
347 + echo "$commit_oid $commit_size" >>expect &&
348 + echo "$tag_oid $tag_size" >>expect &&
349 +
350 + # These results prove remote-object-info did not download objects from the remote
351 + echo "$hello_oid missing" >>expect &&
352 + echo "$tree_oid missing" >>expect &&
353 + echo "$commit_oid missing" >>expect &&
354 + echo "$tag_oid missing" >>expect &&
355 +
356 +
357 + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
358 + remote-object-info "file://${server_path}" $hello_oid $tree_oid $commit_oid $tag_oid
359 + info $hello_oid
360 + info $tree_oid
361 + info $commit_oid
362 + info $tag_oid
363 + EOF
364 + test_cmp expect actual
365 + )
366 +'
367 +
368 +test_expect_success 'batch-command --buffer remote-object-info file://' '
369 + (
370 + set_transport_variables "server" &&
371 + server_path="$(pwd)/server" &&
372 + cd file_client_empty &&
373 +
374 + # These results prove remote-object-info can get object info from the remote
375 + echo "$hello_oid $hello_size" >expect &&
376 + echo "$tree_oid $tree_size" >>expect &&
377 + echo "$commit_oid $commit_size" >>expect &&
378 + echo "$tag_oid $tag_size" >>expect &&
379 +
380 + # These results prove remote-object-info did not download objects from the remote
381 + echo "$hello_oid missing" >>expect &&
382 + echo "$tree_oid missing" >>expect &&
383 + echo "$commit_oid missing" >>expect &&
384 + echo "$tag_oid missing" >>expect &&
385 +
386 + git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF &&
387 + remote-object-info "file://${server_path}" $hello_oid $tree_oid
388 + remote-object-info "file://${server_path}" $commit_oid $tag_oid
389 + info $hello_oid
390 + info $tree_oid
391 + info $commit_oid
392 + info $tag_oid
393 + flush
394 + EOF
395 + test_cmp expect actual
396 + )
397 +'
398 +
399 +test_expect_success 'batch-command remote-object-info file:// default filter' '
400 + (
401 + set_transport_variables "server" &&
402 + server_path="$(pwd)/server" &&
403 + cd file_client_empty &&
404 +
405 + echo "$hello_oid $hello_size" >expect &&
406 + echo "$tree_oid $tree_size" >>expect &&
407 + echo "$commit_oid $commit_size" >>expect &&
408 + echo "$tag_oid $tag_size" >>expect &&
409 +
410 + git cat-file --batch-command >actual <<-EOF &&
411 + remote-object-info "file://${server_path}" $hello_oid $tree_oid
412 + remote-object-info "file://${server_path}" $commit_oid $tag_oid
413 + EOF
414 + test_cmp expect actual
415 + )
416 +'
417 +
418 +test_expect_success 'batch-command -Z remote-object-info file:// default filter' '
419 + (
420 + set_transport_variables "server" &&
421 + server_path="$(pwd)/server" &&
422 + cd file_client_empty &&
423 +
424 + printf "%s\0" "$hello_oid $hello_size" >expect &&
425 + printf "%s\0" "$tree_oid $tree_size" >>expect &&
426 + printf "%s\0" "$commit_oid $commit_size" >>expect &&
427 + printf "%s\0" "$tag_oid $tag_size" >>expect &&
428 +
429 + printf "%s\0" "$hello_oid missing" >>expect &&
430 + printf "%s\0" "$tree_oid missing" >>expect &&
431 + printf "%s\0" "$commit_oid missing" >>expect &&
432 + printf "%s\0" "$tag_oid missing" >>expect &&
433 +
434 + batch_input="remote-object-info \"file://${server_path}\" $hello_oid $tree_oid
435 +remote-object-info \"file://${server_path}\" $commit_oid $tag_oid
436 +info $hello_oid
437 +info $tree_oid
438 +info $commit_oid
439 +info $tag_oid
440 +" &&
441 + echo_without_newline_nul "$batch_input" >commands_null_delimited &&
442 +
443 + git cat-file --batch-command -Z < commands_null_delimited >actual &&
444 + test_cmp expect actual
445 + )
446 +'
447 +
448 +# Test --batch-command remote-object-info with 'file://' and
449 +# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
450 +test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '
451 + (
452 + set_transport_variables "server" &&
453 + server_path="$(pwd)/server" &&
454 + git -C "${server_path}" config transfer.advertiseobjectinfo false &&
455 +
456 + test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
457 + remote-object-info "file://${server_path}" $hello_oid $tree_oid $commit_oid $tag_oid
458 + EOF
459 + test_grep "object-info capability is not enabled on the server" err &&
460 +
461 + # revert server state back
462 + git -C "${server_path}" config transfer.advertiseobjectinfo true
463 + )
464 +'
465 +
466 +# Test --batch-command remote-object-info with 'http://' transport with
467 +# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability
468 +
469 +. "$TEST_DIRECTORY"/lib-httpd.sh
470 +start_httpd
471 +
472 +test_expect_success 'create repo to be served by http:// transport' '
473 + git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
474 + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true &&
475 + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo true &&
476 + echo_without_newline "$hello_content" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&
477 + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" update-index --add hello &&
478 + git clone "$HTTPD_URL/smart/http_parent" -n "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty"
479 +'
480 +
481 +test_expect_success 'batch-command remote-object-info http://' '
482 + (
483 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
484 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
485 +
486 + # These results prove remote-object-info can get object info from the remote
487 + echo "$hello_oid $hello_size" >expect &&
488 + echo "$tree_oid $tree_size" >>expect &&
489 + echo "$commit_oid $commit_size" >>expect &&
490 + echo "$tag_oid $tag_size" >>expect &&
491 +
492 + # These results prove remote-object-info did not download objects from the remote
493 + echo "$hello_oid missing" >>expect &&
494 + echo "$tree_oid missing" >>expect &&
495 + echo "$commit_oid missing" >>expect &&
496 + echo "$tag_oid missing" >>expect &&
497 +
498 + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
499 + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
500 + remote-object-info "$HTTPD_URL/smart/http_parent" $tree_oid
501 + remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid
502 + remote-object-info "$HTTPD_URL/smart/http_parent" $tag_oid
503 + info $hello_oid
504 + info $tree_oid
505 + info $commit_oid
506 + info $tag_oid
507 + EOF
508 + test_cmp expect actual
509 + )
510 +'
511 +
512 +test_expect_success 'batch-command remote-object-info http:// one line' '
513 + (
514 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
515 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
516 +
517 + # These results prove remote-object-info can get object info from the remote
518 + echo "$hello_oid $hello_size" >expect &&
519 + echo "$tree_oid $tree_size" >>expect &&
520 + echo "$commit_oid $commit_size" >>expect &&
521 + echo "$tag_oid $tag_size" >>expect &&
522 +
523 + # These results prove remote-object-info did not download objects from the remote
524 + echo "$hello_oid missing" >>expect &&
525 + echo "$tree_oid missing" >>expect &&
526 + echo "$commit_oid missing" >>expect &&
527 + echo "$tag_oid missing" >>expect &&
528 +
529 + git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
530 + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid $commit_oid $tag_oid
531 + info $hello_oid
532 + info $tree_oid
533 + info $commit_oid
534 + info $tag_oid
535 + EOF
536 + test_cmp expect actual
537 + )
538 +'
539 +
540 +test_expect_success 'batch-command --buffer remote-object-info http://' '
541 + (
542 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
543 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
544 +
545 + # These results prove remote-object-info can get object info from the remote
546 + echo "$hello_oid $hello_size" >expect &&
547 + echo "$tree_oid $tree_size" >>expect &&
548 + echo "$commit_oid $commit_size" >>expect &&
549 + echo "$tag_oid $tag_size" >>expect &&
550 +
551 + # These results prove remote-object-info did not download objects from the remote
552 + echo "$hello_oid missing" >>expect &&
553 + echo "$tree_oid missing" >>expect &&
554 + echo "$commit_oid missing" >>expect &&
555 + echo "$tag_oid missing" >>expect &&
556 +
557 + git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF &&
558 + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid
559 + remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid $tag_oid
560 + info $hello_oid
561 + info $tree_oid
562 + info $commit_oid
563 + info $tag_oid
564 + flush
565 + EOF
566 + test_cmp expect actual
567 + )
568 +'
569 +
570 +test_expect_success 'batch-command remote-object-info http:// default filter' '
571 + (
572 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
573 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
574 +
575 + echo "$hello_oid $hello_size" >expect &&
576 + echo "$tree_oid $tree_size" >>expect &&
577 + echo "$commit_oid $commit_size" >>expect &&
578 + echo "$tag_oid $tag_size" >>expect &&
579 +
580 + git cat-file --batch-command >actual <<-EOF &&
581 + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid
582 + remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid $tag_oid
583 + EOF
584 + test_cmp expect actual
585 + )
586 +'
587 +
588 +test_expect_success 'batch-command -Z remote-object-info http:// default filter' '
589 + (
590 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
591 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
592 +
593 + printf "%s\0" "$hello_oid $hello_size" >expect &&
594 + printf "%s\0" "$tree_oid $tree_size" >>expect &&
595 + printf "%s\0" "$commit_oid $commit_size" >>expect &&
596 + printf "%s\0" "$tag_oid $tag_size" >>expect &&
597 +
598 + batch_input="remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid
599 +remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid
600 +" &&
601 + echo_without_newline_nul "$batch_input" >commands_null_delimited &&
602 +
603 + git cat-file --batch-command -Z < commands_null_delimited >actual &&
604 + test_cmp expect actual
605 + )
606 +'
607 +
608 +test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '
609 + (
610 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
611 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
612 +
613 + echo "$hello_oid " >expect &&
614 +
615 + git cat-file --batch-command="%(objectname) %(objectsize:disk)" >actual <<-EOF &&
616 + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
617 + EOF
618 + test_cmp expect actual
619 + )
620 +'
621 +
622 +test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '
623 + (
624 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
625 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
626 +
627 + echo "" >expect &&
628 +
629 + git cat-file --batch-command="%(deltabase)" >actual <<-EOF &&
630 + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
631 + EOF
632 + test_cmp expect actual
633 + )
634 +'
635 +
636 +test_expect_success 'remote-object-info fails on server with legacy protocol' '
637 + (
638 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
639 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
640 +
641 + test_must_fail git -c protocol.version=0 cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
642 + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
643 + EOF
644 + test_grep "object-info requires protocol v2" err
645 + )
646 +'
647 +
648 +test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '
649 + (
650 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
651 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
652 +
653 + test_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&
654 + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
655 + EOF
656 + test_grep "object-info requires protocol v2" err
657 + )
658 +'
659 +
660 +test_expect_success 'remote-object-info fails on malformed OID' '
661 + (
662 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
663 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
664 + malformed_object_id="this_id_is_not_valid" &&
665 +
666 + test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
667 + remote-object-info "$HTTPD_URL/smart/http_parent" $malformed_object_id
668 + EOF
669 + test_grep "not a valid object name '$malformed_object_id'" err
670 + )
671 +'
672 +
673 +test_expect_success 'remote-object-info fails on malformed OID with default filter' '
674 + (
675 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
676 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
677 + malformed_object_id="this_id_is_not_valid" &&
678 +
679 + test_must_fail git cat-file --batch-command 2>err <<-EOF &&
680 + remote-object-info "$HTTPD_URL/smart/http_parent" $malformed_object_id
681 + EOF
682 + test_grep "not a valid object name '$malformed_object_id'" err
683 + )
684 +'
685 +
686 +test_expect_success 'remote-object-info fails on not providing OID' '
687 + (
688 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
689 + cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
690 +
691 + test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
692 + remote-object-info "$HTTPD_URL/smart/http_parent"
693 + EOF
694 + test_grep "remote-object-info requires objects" err
695 + )
696 +'
697 +
698 +
699 +# Test --batch-command remote-object-info with 'http://' transport and
700 +# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
701 +test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '
702 + (
703 + set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
704 + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo false &&
705 +
706 + test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
707 + remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid $commit_oid $tag_oid
708 + EOF
709 + test_grep "object-info capability is not enabled on the server" err &&
710 +
711 + # revert server state back
712 + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo true
713 + )
714 +'
715 +
716 +# DO NOT add non-httpd-specific tests here, because the last part of this
717 +# test script is only executed when httpd is available and enabled.
718 +
719 +test_done