protocol-caps: check object existence regardless of the attributes requested

Currently, send_info() only checks for existence when the attribute 'size' is also requested. Requesting a bare OID, without attributes only echoes back the OID. Extract the existence check to be done regardless of the number of attributes requested. While at it, introduce a wrapper called get_object_info() similar to odb_read_object_info() that returns OBJ_BAD on fail and adds OBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK flags. OBJECT_INFO_SKIP_FETCH_OBJECT is so a server with a partial clone doesn't trigger fetching objects when it gets an object-info request with an OID that is not available locally. A server should only report what it has locally. Tighten the condition used to determine whether an object is recognized. get_object_info() returns OBJ_BAD for unknown objects, but OBJ_NONE (0) can also mean "not found". Change the check from '< 0' to '<= OBJ_NONE' to cover both as unrecognized. With this patch, a bare OID has two possible responses: 1. Recognized OID: the server answers with "<OID>" 2. Unrecognized OID: the server answers with "<OID> SP" Update the object-info section in 'gitprotocol-v2.adoc': - Require full obj-oid explicitly. - Fix parentheses. - Define obj-size explicitly. - Make obj-size optional in obj-info and document the behavior for unrecognized object IDs. - Describe the attr header as zero or more pkt-lines, one per attribute, matching what the server implements. A request with no attributes gets no header. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Pablo Sabater committed Jul 24, 2026 at 12:54 UTC e44aca614596d49ca802f1602eaf9a4850c3c07d
3 files changed +115 -14
Documentation/gitprotocol-v2.adoc
+13 -8
@@ -568,21 +568,26 @@ An `object-info` request takes the following arguments:
568
569 oid <oid>
570 Indicates to the server an object which the client wants to obtain
571 - information for.
571 + information for. They must be full OIDs.
572
573 -The response of `object-info` is a list of the requested object ids
574 -and associated requested information, each separated by a single space.
573 +The response of `object-info` consists of one pkt-line per requested attribute,
574 +echoing the attributes the server will report, followed by one pkt-line per
575 +requested object id with its information, each field separated by a single
576 +space.
577
578 output = info flush-pkt
579
578 - info = PKT-LINE(attrs) LF)
579 - *PKT-LINE(obj-info LF)
580 -
581 - attrs = attr | attrs SP attrs
580 + info = *PKT-LINE(attr LF)
581 + *PKT-LINE(obj-info LF)
582
583 attr = "size"
584
585 - obj-info = obj-id SP obj-size
585 + obj-size = 1*DIGIT
586 +
587 + obj-info = obj-id [SP [obj-size]]
588 +
589 +If the server does not recognize the OID, the response will be `<oid> SP`
590 +regardless of the number of attributes requested.
591
592 bundle-uri
593 ~~~~~~~~~~
protocol-caps.c
+39 -6
@@ -30,6 +30,32 @@ static int parse_oid(const char *line, struct string_list *oid_str_list)
30 return 1;
31 }
32
33 +/*
34 + * odb_read_object_info_extended() wrapper. Similar to odb_read_object_info()
35 + * but uses the flags:
36 + *
37 + * - OBJECT_INFO_SKIP_FETCH_OBJECT so a server won't fetch an object when a
38 + * object-info request asks for an OID that it doesn't have.
39 + *
40 + * - OBJECT_INFO_QUICK to avoid re-scanning packs when the object is not found.
41 + */
42 +static enum object_type get_object_info(struct object_database *odb,
43 + const struct object_id *oid,
44 + size_t *sizep)
45 +{
46 + enum object_type type;
47 + struct object_info oi = OBJECT_INFO_INIT;
48 +
49 + oi.typep = &type;
50 + oi.sizep = sizep;
51 + if (odb_read_object_info_extended(odb, oid, &oi,
52 + OBJECT_INFO_LOOKUP_REPLACE |
53 + OBJECT_INFO_SKIP_FETCH_OBJECT |
54 + OBJECT_INFO_QUICK) < 0)
55 + return OBJ_BAD;
56 + return type;
57 +}
58 +
59 /*
60 * Validates and send requested info back to the client. Any errors detected
61 * are returned as they are detected.
@@ -62,15 +88,22 @@ static void send_info(struct repository *r, struct packet_writer *writer,
88
89 strbuf_addstr(&send_buffer, oid_str);
90
91 + /*
92 + * Check the existence of the object first.
93 + * If an object is not recognized by the server append SP to
94 + * the response.
95 + */
96 + if (get_object_info(r->objects, &oid, &object_size) <= OBJ_NONE) {
97 + strbuf_addstr(&send_buffer, " ");
98 + goto write;
99 + }
100 +
101 if (info->size) {
66 - if (odb_read_object_info(r->objects, &oid, &object_size) < 0) {
67 - strbuf_addstr(&send_buffer, " ");
68 - } else {
69 - strbuf_addf(&send_buffer, " %"PRIuMAX,
70 - (uintmax_t)object_size);
71 - }
102 + strbuf_addf(&send_buffer, " %"PRIuMAX,
103 + (uintmax_t)object_size);
104 }
105
106 +write:
107 packet_writer_write(writer, "%s", send_buffer.buf);
108 strbuf_reset(&send_buffer);
109 }
t/t5701-git-serve.sh
+63
@@ -7,6 +7,8 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
7
8 . ./test-lib.sh
9
10 +unknown_oid=$(printf "test" | git hash-object --stdin)
11 +
12 test_expect_success 'setup to generate files with expected content' '
13 printf "agent=git/%s" "$(git version | cut -d" " -f3)" >agent_capability &&
14
@@ -364,6 +366,67 @@ test_expect_success 'basics of object-info' '
366 test_cmp expect actual
367 '
368
369 +test_expect_success 'bare OID request' '
370 + test_config transfer.advertiseObjectInfo true &&
371 +
372 + test-tool pkt-line pack >in <<-EOF &&
373 + command=object-info
374 + object-format=$(test_oid algo)
375 + 0001
376 + oid $(git rev-parse two:two.t)
377 + 0000
378 + EOF
379 +
380 + cat >expect <<-EOF &&
381 + $(git rev-parse two:two.t)
382 + 0000
383 + EOF
384 +
385 + test-tool serve-v2 --stateless-rpc <in >out &&
386 + test-tool pkt-line unpack <out >actual &&
387 + test_cmp expect actual
388 +'
389 +
390 +test_expect_success 'object-info with bare unrecognized OID' '
391 + test_config transfer.advertiseObjectInfo true &&
392 +
393 + test-tool pkt-line pack >in <<-EOF &&
394 + command=object-info
395 + object-format=$(test_oid algo)
396 + 0001
397 + oid $unknown_oid
398 + 0000
399 + EOF
400 +
401 + printf "%s \n" "$unknown_oid" >expect &&
402 + printf "0000\n" >>expect &&
403 +
404 + test-tool serve-v2 --stateless-rpc <in >out &&
405 + test-tool pkt-line unpack <out >actual &&
406 + test_cmp expect actual
407 +'
408 +
409 +test_expect_success 'object-info with size for unrecognized OID' '
410 + test_config transfer.advertiseObjectInfo true &&
411 +
412 + test-tool pkt-line pack >in <<-EOF &&
413 + command=object-info
414 + object-format=$(test_oid algo)
415 + 0001
416 + size
417 + oid $unknown_oid
418 + 0000
419 + EOF
420 +
421 + printf "size\n" >expect &&
422 + printf "%s \n" "$unknown_oid" >>expect &&
423 + printf "0000\n" >>expect &&
424 +
425 + test-tool serve-v2 --stateless-rpc <in >out &&
426 + test-tool pkt-line unpack <out >actual &&
427 + test_cmp expect actual
428 +'
429 +
430 test_expect_success 'test capability advertisement with uploadpack.advertiseBundleURIs' '
431 test_config uploadpack.advertiseBundleURIs true &&
432