odb: make backend-specific fields optional

The `struct object_info` carries two pieces of information about how an object was looked up: - The `whence` enum identifying the backend. - The backend-tagged union `u` exposing backend-specific details (currently only the packed-source case, which records the owning pack, offset and packed object type). The union is populated unconditionally, even though most callers don't care about provenance at all. Split the backend-specific union out into a new public type, `struct object_info_source`, and make the object info structure carry it via just another opt-in request pointer. As with all the other requestable information, callers that need source info allocate a `struct object_info_source` on the stack and point `sourcep` at it; callers that don't care about it simply leave the field as a `NULL` pointer. Adapt callers accordingly. Note that the `whence` enum is strictly-speaking also backend-specific information, so it would be another good candidate to be moved into the `struct object_info_source`. For now though it is left alone, as it will be replaced by a `struct odb_source` pointer in a subsequent commit. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jul 2, 2026 at 14:02 UTC 126076b62f5164f9da6bbe454fc71c164ea5cb42
7 files changed +87 -45
builtin/cat-file.c
+6 -2
@@ -835,7 +835,8 @@ static int batch_one_object_oi(const struct object_id *oid,
835 {
836 struct for_each_object_payload *payload = _payload;
837 if (oi && oi->whence == OI_PACKED)
838 - return payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,
838 + return payload->callback(oid, oi->sourcep->u.packed.pack,
839 + oi->sourcep->u.packed.offset,
840 payload->payload);
841 return payload->callback(oid, NULL, 0, payload->payload);
842 }
@@ -906,7 +907,10 @@ static void batch_each_object(struct batch_options *opt,
907 &payload, flags);
908 }
909 } else {
909 - struct object_info oi = { 0 };
910 + struct object_info_source oi_source;
911 + struct object_info oi = {
912 + .sourcep = &oi_source,
913 + };
914
915 for (source = the_repository->objects->sources; source; source = source->next) {
916 struct odb_source_files *files = odb_source_files_downcast(source);
builtin/index-pack.c
+6 -2
@@ -1825,11 +1825,15 @@ static void repack_local_links(void)
1825
1826 oidset_iter_init(&outgoing_links, &iter);
1827 while ((oid = oidset_iter_next(&iter))) {
1828 - struct object_info info = OBJECT_INFO_INIT;
1828 + struct object_info_source info_source;
1829 + struct object_info info = {
1830 + .sourcep = &info_source,
1831 + };
1832 +
1833 if (odb_read_object_info_extended(the_repository->objects, oid, &info, 0))
1834 /* Missing; assume it is a promisor object */
1835 continue;
1832 - if (info.whence == OI_PACKED && info.u.packed.pack->pack_promisor)
1836 + if (info.whence == OI_PACKED && info_source.u.packed.pack->pack_promisor)
1837 continue;
1838
1839 if (!cmd.args.nr) {
builtin/pack-objects.c
+11 -4
@@ -4491,8 +4491,9 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,
4491 void *data UNUSED)
4492 {
4493 if (cruft) {
4494 - add_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack,
4495 - oi->u.packed.offset, NULL, *oi->mtimep);
4494 + add_cruft_object_entry(oid, OBJ_NONE, oi->sourcep->u.packed.pack,
4495 + oi->sourcep->u.packed.offset, NULL,
4496 + *oi->mtimep);
4497 } else {
4498 add_object_entry(oid, OBJ_NONE, "", 0);
4499 }
@@ -4509,8 +4510,10 @@ static void add_objects_in_unpacked_packs(void)
4510 ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |
4511 ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS,
4512 };
4513 + struct object_info_source oi_source;
4514 struct object_info oi = {
4515 .mtimep = &mtime,
4516 + .sourcep = &oi_source,
4517 };
4518
4519 odb_prepare_alternates(to_pack.repo->objects);
@@ -5000,10 +5003,14 @@ static int option_parse_cruft_expiration(const struct option *opt UNUSED,
5003
5004 static int is_not_in_promisor_pack_obj(struct object *obj, void *data UNUSED)
5005 {
5003 - struct object_info info = OBJECT_INFO_INIT;
5006 + struct object_info_source info_source;
5007 + struct object_info info = {
5008 + .sourcep = &info_source,
5009 + };
5010 +
5011 if (odb_read_object_info_extended(the_repository->objects, &obj->oid, &info, 0))
5012 BUG("should_include_obj should only be called on existing objects");
5006 - return info.whence != OI_PACKED || !info.u.packed.pack->pack_promisor;
5013 + return info.whence != OI_PACKED || !info_source.u.packed.pack->pack_promisor;
5014 }
5015
5016 static int is_not_in_promisor_pack(struct commit *commit, void *data) {
odb.c
+2 -1
@@ -692,7 +692,8 @@ static int oid_object_info_convert(struct repository *r,
692 }
693 }
694 input_oi->whence = new_oi.whence;
695 - input_oi->u = new_oi.u;
695 + if (input_oi->sourcep)
696 + *input_oi->sourcep = *new_oi.sourcep;
697 return ret;
698 }
699
odb.h
+40 -20
@@ -248,6 +248,38 @@ int odb_pretend_object(struct object_database *odb,
248 void *buf, size_t len, enum object_type type,
249 struct object_id *oid);
250
251 +/*
252 + * Object information that can be used to uniquely identify an object and learn
253 + * more about how exactly it is stored.
254 + */
255 +struct object_info_source {
256 + /*
257 + * Backend-specific information about the specific object. This can be
258 + * used for example to uniquely identify a given object in case it
259 + * exists multiple times.
260 + */
261 + union {
262 + /*
263 + * struct {
264 + * ... Nothing to expose in this case
265 + * } cached;
266 + * struct {
267 + * ... Nothing to expose in this case
268 + * } loose;
269 + */
270 + struct {
271 + struct packed_git *pack;
272 + off_t offset;
273 + enum packed_object_type {
274 + PACKED_OBJECT_TYPE_UNKNOWN,
275 + PACKED_OBJECT_TYPE_FULL,
276 + PACKED_OBJECT_TYPE_OFS_DELTA,
277 + PACKED_OBJECT_TYPE_REF_DELTA,
278 + } type;
279 + } packed;
280 + } u;
281 +};
282 +
283 struct object_info {
284 /* Request */
285 enum object_type *typep;
@@ -269,32 +301,20 @@ struct object_info {
301 */
302 time_t *mtimep;
303
304 + /*
305 + * Backend-specific information that tells the caller where exactly an
306 + * object was looked up from. This information should help disambiguate
307 + * object lookups in case the same object exists in multiple sources,
308 + * or multiple times in the same source.
309 + */
310 + struct object_info_source *sourcep;
311 +
312 /* Response */
313 enum {
314 OI_CACHED,
315 OI_LOOSE,
316 OI_PACKED,
317 } whence;
278 - union {
279 - /*
280 - * struct {
281 - * ... Nothing to expose in this case
282 - * } cached;
283 - * struct {
284 - * ... Nothing to expose in this case
285 - * } loose;
286 - */
287 - struct {
288 - struct packed_git *pack;
289 - off_t offset;
290 - enum packed_object_type {
291 - PACKED_OBJECT_TYPE_UNKNOWN,
292 - PACKED_OBJECT_TYPE_FULL,
293 - PACKED_OBJECT_TYPE_OFS_DELTA,
294 - PACKED_OBJECT_TYPE_REF_DELTA,
295 - } type;
296 - } packed;
297 - } u;
318 };
319
320 /*
packfile.c
+18 -15
@@ -1422,22 +1422,25 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,
1422 }
1423
1424 oi->whence = OI_PACKED;
1425 - oi->u.packed.offset = obj_offset;
1426 - oi->u.packed.pack = p;
1425
1428 - switch (type) {
1429 - case OBJ_NONE:
1430 - oi->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;
1431 - break;
1432 - case OBJ_REF_DELTA:
1433 - oi->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;
1434 - break;
1435 - case OBJ_OFS_DELTA:
1436 - oi->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;
1437 - break;
1438 - default:
1439 - oi->u.packed.type = PACKED_OBJECT_TYPE_FULL;
1440 - break;
1426 + if (oi->sourcep) {
1427 + oi->sourcep->u.packed.offset = obj_offset;
1428 + oi->sourcep->u.packed.pack = p;
1429 +
1430 + switch (type) {
1431 + case OBJ_NONE:
1432 + oi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;
1433 + break;
1434 + case OBJ_REF_DELTA:
1435 + oi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;
1436 + break;
1437 + case OBJ_OFS_DELTA:
1438 + oi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;
1439 + break;
1440 + default:
1441 + oi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_FULL;
1442 + break;
1443 + }
1444 }
1445
1446 ret = 0;
reachable.c
+4 -1
@@ -235,7 +235,8 @@ static int add_recent_object(const struct object_id *oid,
235 add_pending_object(data->revs, obj, "");
236 if (data->cb) {
237 if (oi->whence == OI_PACKED)
238 - data->cb(obj, oi->u.packed.pack, oi->u.packed.offset, *oi->mtimep);
238 + data->cb(obj, oi->sourcep->u.packed.pack,
239 + oi->sourcep->u.packed.offset, *oi->mtimep);
240 else
241 data->cb(obj, NULL, 0, *oi->mtimep);
242 }
@@ -252,9 +253,11 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,
253 unsigned flags;
254 enum object_type type;
255 time_t mtime;
256 + struct object_info_source oi_source;
257 struct object_info oi = {
258 .mtimep = &mtime,
259 .typep = &type,
260 + .sourcep = &oi_source,
261 };
262 int r;
263