{"thread":{"id":"65865","subject":"[PATCH 0/6] odb: refactor source-specific information in object info","startedAt":"2026-06-24T12:19:30Z","lastAt":"2026-07-05T01:28:05Z","messageCount":26,"participants":["Patrick Steinhardt","Junio C Hamano","Justin Tobler"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"546308","messageId":"20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im","threadId":"65865","inReplyTo":null,"subject":"[PATCH 0/6] odb: refactor source-specific information in object info","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-24T12:19:13Z","receivedAt":"2026-06-24T12:19:30Z","isPatch":true,"body":"Hi,\n\nthis patch series refactors `struct object_info` to not contain the\n`whence` field anymore.\n\nThis field only gave the caller information about the type of source\nthis was read from, but it didn't allow them to figure out which source\nspecifically yielded the object. So instead, we replace this information\nwith a new `struct object_info_source` field that both contains info\nabout the source, and any backend-specific data.\n\nWith this in place we can re-query the same backend for any given\nobject. More importantly though, we can eventually also use the backend-\nspecific data to also uniquely identify any given object, e.g. by\nrecording the packfile and offset, so that we can even yield the same\nobject in case one source contains the object multiple times.\n\nFurthermore, with this change all information in `struct object_info` is\nnow following the same request-response-field style.\n\nThe series is built on top of 26d8d94e94 (A few more topics before -rc2,\n2026-06-21) with ps/odb-source-packed at 1bba3c035d (odb/source-packed:\ndrop pointer to \"files\" parent source, 2026-06-17) merged into it.\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (6):\n      packfile: thread odb_source_packed through packed_object_info()\n      odb: make backend-specific fields optional\n      odb: add `source` field to struct object_info_source\n      treewide: convert users of `whence` to the new source field\n      odb: drop `whence` field from object info\n      odb: document object info fields\n\n builtin/cat-file.c     | 12 +++++---\n builtin/index-pack.c   |  9 ++++--\n builtin/pack-objects.c | 19 ++++++++----\n commit-graph.c         |  2 +-\n odb.c                  |  4 +--\n odb.h                  | 80 +++++++++++++++++++++++++++++++++++---------------\n odb/source-inmemory.c  |  3 +-\n odb/source-loose.c     |  4 +--\n odb/source-packed.c    |  4 +--\n pack-bitmap.c          |  2 +-\n packfile.c             | 45 ++++++++++++++++------------\n packfile.h             |  6 ++--\n reachable.c            |  7 +++--\n t/helper/test-bitmap.c |  2 +-\n 14 files changed, 130 insertions(+), 69 deletions(-)\n\n\n---\nbase-commit: 969dbd51a70f9105ee9965adec5c5a02e75ab5b3\nchange-id: 20260612-b4-pks-odb-drop-whence-1b0af9ab16f4\n\n"},{"id":"546309","messageId":"20260624-b4-pks-odb-drop-whence-v1-1-8d1877b790ac@pks.im","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im","subject":"[PATCH 1/6] packfile: thread odb_source_packed through packed_object_info()","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-24T12:19:14Z","receivedAt":"2026-06-24T12:19:31Z","isPatch":true,"body":"Add an optional `struct odb_source_packed *source` parameter to\n`packed_object_info()` and `packed_object_info_with_index_pos()`. This\nparameter is unused at this point in time, but it will be used in a\nfollow-up commit so that we can record the source of a specific object.\n\nNote that callers in \"odb/source-packed.c\" pass the already-available\nsource, but all other callers pass `NULL` instead. This is fine though,\nas we only care about populating this info when called via the packed\nstore.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     | 2 +-\n builtin/pack-objects.c | 4 ++--\n commit-graph.c         | 2 +-\n odb/source-packed.c    | 4 ++--\n pack-bitmap.c          | 2 +-\n packfile.c             | 8 +++++---\n packfile.h             | 6 ++++--\n t/helper/test-bitmap.c | 2 +-\n 8 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 0f3dbd9850..8726485f1f 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -497,7 +497,7 @@ static void batch_object_write(const char *obj_name,\n \t\t\tdata->info.sizep = &data->size;\n \n \t\tif (pack)\n-\t\t\tret = packed_object_info(pack, offset, &data->info);\n+\t\t\tret = packed_object_info(NULL, pack, offset, &data->info);\n \t\telse\n \t\t\tret = odb_read_object_info_extended(the_repository->objects,\n \t\t\t\t\t\t\t    &data->oid, &data->info,\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex bc5f9ef321..620d9ce085 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -2463,7 +2463,7 @@ static void drop_reused_delta(struct object_entry *entry)\n \n \toi.sizep = &size;\n \toi.typep = &type;\n-\tif (packed_object_info(IN_PACK(entry), entry->in_pack_offset, &oi) < 0) {\n+\tif (packed_object_info(NULL, IN_PACK(entry), entry->in_pack_offset, &oi) < 0) {\n \t\t/*\n \t\t * We failed to get the info from this pack for some reason;\n \t\t * fall back to odb_read_object_info, which may find another copy.\n@@ -3804,7 +3804,7 @@ static int add_object_entry_from_pack(const struct object_id *oid,\n \tofs = nth_packed_object_offset(p, pos);\n \n \toi.typep = &type;\n-\tif (packed_object_info(p, ofs, &oi) < 0) {\n+\tif (packed_object_info(NULL, p, ofs, &oi) < 0) {\n \t\tdie(_(\"could not get type of object %s in pack %s\"),\n \t\t    oid_to_hex(oid), p->pack_name);\n \t} else if (type == OBJ_COMMIT) {\ndiff --git a/commit-graph.c b/commit-graph.c\nindex c6d9c5c740..9dc8bd5eee 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1538,7 +1538,7 @@ static int add_packed_commits(const struct object_id *oid,\n \tstruct object_info oi = OBJECT_INFO_INIT;\n \n \toi.typep = &type;\n-\tif (packed_object_info(pack, offset, &oi) < 0)\n+\tif (packed_object_info(NULL, pack, offset, &oi) < 0)\n \t\tdie(_(\"unable to get type of object %s\"), oid_to_hex(oid));\n \n \treturn add_packed_commits_oi(oid, &oi, data);\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 42c28fba0e..43fb53b72d 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -59,7 +59,7 @@ static int odb_source_packed_read_object_info(struct odb_source *source,\n \tif (!oi)\n \t\treturn 0;\n \n-\tret = packed_object_info(e.p, e.offset, oi);\n+\tret = packed_object_info(packed, e.p, e.offset, oi);\n \tif (ret < 0) {\n \t\tmark_bad_packed_object(e.p, oid);\n \t\treturn -1;\n@@ -99,7 +99,7 @@ static int odb_source_packed_for_each_object_wrapper(const struct object_id *oid\n \t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n \t\tstruct object_info oi = *data->request;\n \n-\t\tif (packed_object_info_with_index_pos(pack, offset,\n+\t\tif (packed_object_info_with_index_pos(data->store, pack, offset,\n \t\t\t\t\t\t      &index_pos, &oi) < 0) {\n \t\t\tmark_bad_packed_object(pack, oid);\n \t\t\treturn -1;\ndiff --git a/pack-bitmap.c b/pack-bitmap.c\nindex 83eb47a28b..35774b6f0c 100644\n--- a/pack-bitmap.c\n+++ b/pack-bitmap.c\n@@ -1877,7 +1877,7 @@ static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git,\n \t\t\tofs = pack_pos_to_offset(pack, pos);\n \t\t}\n \n-\t\tif (packed_object_info(pack, ofs, &oi) < 0) {\n+\t\tif (packed_object_info(NULL, pack, ofs, &oi) < 0) {\n \t\t\tstruct object_id oid;\n \t\t\tnth_bitmap_object_oid(bitmap_git, &oid,\n \t\t\t\t\t      pack_pos_to_index(pack, pos));\ndiff --git a/packfile.c b/packfile.c\nindex 1d1b23b6cc..2b741d7a76 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1324,7 +1324,8 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,\n \thashmap_add(&delta_base_cache, &ent->ent);\n }\n \n-int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n+int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n+\t\t\t\t      struct packed_git *p, off_t obj_offset,\n \t\t\t\t      uint32_t *maybe_index_pos, struct object_info *oi)\n {\n \tstruct pack_window *w_curs = NULL;\n@@ -1446,10 +1447,11 @@ int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n \treturn ret;\n }\n \n-int packed_object_info(struct packed_git *p, off_t obj_offset,\n+int packed_object_info(struct odb_source_packed *source,\n+\t\t       struct packed_git *p, off_t obj_offset,\n \t\t       struct object_info *oi)\n {\n-\treturn packed_object_info_with_index_pos(p, obj_offset, NULL, oi);\n+\treturn packed_object_info_with_index_pos(source, p, obj_offset, NULL, oi);\n }\n \n static void *unpack_compressed_entry(struct packed_git *p,\ndiff --git a/packfile.h b/packfile.h\nindex 2329a69701..e1f77152b5 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -320,9 +320,11 @@ extern int do_check_packed_object_crc;\n  * Look up the object info for a specific offset in the packfile.\n  * Returns zero on success, a negative error code otherwise.\n  */\n-int packed_object_info(struct packed_git *pack,\n+int packed_object_info(struct odb_source_packed *source,\n+\t\t       struct packed_git *pack,\n \t\t       off_t offset, struct object_info *);\n-int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n+int packed_object_info_with_index_pos(struct odb_source_packed *source,\n+\t\t\t\t      struct packed_git *p, off_t obj_offset,\n \t\t\t\t      uint32_t *maybe_index_pos, struct object_info *oi);\n \n void mark_bad_packed_object(struct packed_git *, const struct object_id *);\ndiff --git a/t/helper/test-bitmap.c b/t/helper/test-bitmap.c\nindex b130832b81..8547ef67e2 100644\n--- a/t/helper/test-bitmap.c\n+++ b/t/helper/test-bitmap.c\n@@ -52,7 +52,7 @@ static int add_packed_object(const struct object_id *oid,\n \n \tentry = packlist_alloc(packed, oid);\n \tentry->idx.offset = nth_packed_object_offset(pack, pos);\n-\tif (packed_object_info(pack, entry->idx.offset, &oi) < 0)\n+\tif (packed_object_info(NULL, pack, entry->idx.offset, &oi) < 0)\n \t\tdie(\"could not get type of object %s\",\n \t\t    oid_to_hex(oid));\n \toe_set_type(entry, type);\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546310","messageId":"20260624-b4-pks-odb-drop-whence-v1-2-8d1877b790ac@pks.im","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im","subject":"[PATCH 2/6] odb: make backend-specific fields optional","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-24T12:19:15Z","receivedAt":"2026-06-24T12:19:34Z","isPatch":true,"body":"The `struct object_info` carries two pieces of information\nabout how an object was looked up:\n\n  - The `whence` enum identifying the backend.\n\n  - The backend-tagged union `u` exposing backend-specific details\n    (currently only the packed-source case, which records the owning\n    pack, offset and packed object type).\n\nThe union is populated unconditionally, even though most callers don't\ncare about provenance at all.\n\nSplit the backend-specific union out into a new public type, `struct\nobject_info_source`, and make the object info structure carry it via\njust another opt-in request pointer. As with all the other requestable\ninformation, callers that need source info allocate a `struct\nobject_info_source` on the stack and point `sourcep` at it; callers that\ndon't care about it simply leave the field as a `NULL` pointer. Adapt\ncallers accordingly.\n\nNote that the `whence` enum is strictly-speaking also backend-specific\ninformation, so it would be another good candidate to be moved into the\n`struct object_info_source`. For now though it is left alone, as it will\nbe replaced by a `struct odb_source` pointer in a subsequent commit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     |  8 +++++--\n builtin/index-pack.c   |  8 +++++--\n builtin/pack-objects.c | 15 +++++++++----\n odb.c                  |  3 ++-\n odb.h                  | 60 +++++++++++++++++++++++++++++++++-----------------\n packfile.c             | 33 ++++++++++++++-------------\n reachable.c            |  5 ++++-\n 7 files changed, 87 insertions(+), 45 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 8726485f1f..adc626ce30 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -835,7 +835,8 @@ static int batch_one_object_oi(const struct object_id *oid,\n {\n \tstruct for_each_object_payload *payload = _payload;\n \tif (oi && oi->whence == OI_PACKED)\n-\t\treturn payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,\n+\t\treturn payload->callback(oid, oi->sourcep->u.packed.pack,\n+\t\t\t\t\t oi->sourcep->u.packed.offset,\n \t\t\t\t\t payload->payload);\n \treturn payload->callback(oid, NULL, 0, payload->payload);\n }\n@@ -906,7 +907,10 @@ static void batch_each_object(struct batch_options *opt,\n \t\t\t\t\t\t&payload, flags);\n \t\t}\n \t} else {\n-\t\tstruct object_info oi = { 0 };\n+\t\tstruct object_info_source oi_source;\n+\t\tstruct object_info oi = {\n+\t\t\t.sourcep = &oi_source,\n+\t\t};\n \n \t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n \t\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex f396658468..77af26db8f 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1825,11 +1825,15 @@ static void repack_local_links(void)\n \n \toidset_iter_init(&outgoing_links, &iter);\n \twhile ((oid = oidset_iter_next(&iter))) {\n-\t\tstruct object_info info = OBJECT_INFO_INIT;\n+\t\tstruct object_info_source info_source;\n+\t\tstruct object_info info = {\n+\t\t\t.sourcep = &info_source,\n+\t\t};\n+\n \t\tif (odb_read_object_info_extended(the_repository->objects, oid, &info, 0))\n \t\t\t/* Missing; assume it is a promisor object */\n \t\t\tcontinue;\n-\t\tif (info.whence == OI_PACKED && info.u.packed.pack->pack_promisor)\n+\t\tif (info.whence == OI_PACKED && info_source.u.packed.pack->pack_promisor)\n \t\t\tcontinue;\n \n \t\tif (!cmd.args.nr) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 620d9ce085..9deb37e9e8 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4491,8 +4491,9 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,\n \t\t\t\t       void *data UNUSED)\n {\n \tif (cruft) {\n-\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack,\n-\t\t\t\t       oi->u.packed.offset, NULL, *oi->mtimep);\n+\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->sourcep->u.packed.pack,\n+\t\t\t\t       oi->sourcep->u.packed.offset, NULL,\n+\t\t\t\t       *oi->mtimep);\n \t} else {\n \t\tadd_object_entry(oid, OBJ_NONE, \"\", 0);\n \t}\n@@ -4509,8 +4510,10 @@ static void add_objects_in_unpacked_packs(void)\n \t\t\t ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n \t\t\t ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS,\n \t};\n+\tstruct object_info_source oi_source;\n \tstruct object_info oi = {\n \t\t.mtimep = &mtime,\n+\t\t.sourcep = &oi_source,\n \t};\n \n \todb_prepare_alternates(to_pack.repo->objects);\n@@ -5000,10 +5003,14 @@ static int option_parse_cruft_expiration(const struct option *opt UNUSED,\n \n static int is_not_in_promisor_pack_obj(struct object *obj, void *data UNUSED)\n {\n-\tstruct object_info info = OBJECT_INFO_INIT;\n+\tstruct object_info_source info_source;\n+\tstruct object_info info = {\n+\t\t.sourcep = &info_source,\n+\t};\n+\n \tif (odb_read_object_info_extended(the_repository->objects, &obj->oid, &info, 0))\n \t\tBUG(\"should_include_obj should only be called on existing objects\");\n-\treturn info.whence != OI_PACKED || !info.u.packed.pack->pack_promisor;\n+\treturn info.whence != OI_PACKED || !info_source.u.packed.pack->pack_promisor;\n }\n \n static int is_not_in_promisor_pack(struct commit *commit, void *data) {\ndiff --git a/odb.c b/odb.c\nindex 7d555be09f..99f4e7551c 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -692,7 +692,8 @@ static int oid_object_info_convert(struct repository *r,\n \t\t}\n \t}\n \tinput_oi->whence = new_oi.whence;\n-\tinput_oi->u = new_oi.u;\n+\tif (input_oi->sourcep)\n+\t\t*input_oi->sourcep = *new_oi.sourcep;\n \treturn ret;\n }\n \ndiff --git a/odb.h b/odb.h\nindex 3834a0dcbf..770900289a 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -248,6 +248,38 @@ int odb_pretend_object(struct object_database *odb,\n \t\t       void *buf, size_t len, enum object_type type,\n \t\t       struct object_id *oid);\n \n+/*\n+ * Object information that can be used to uniquely identify an object and learn\n+ * more about how exactly it is stored.\n+ */\n+struct object_info_source {\n+\t/*\n+\t * Backend-specific information about the specific object. This can be\n+\t * used for example to uniquely identify a given object in case it\n+\t * exists multiple times.\n+\t */\n+\tunion {\n+\t\t/*\n+\t\t * struct {\n+\t\t * \t... Nothing to expose in this case\n+\t\t * } cached;\n+\t\t * struct {\n+\t\t * \t... Nothing to expose in this case\n+\t\t * } loose;\n+\t\t */\n+\t\tstruct {\n+\t\t\tstruct packed_git *pack;\n+\t\t\toff_t offset;\n+\t\t\tenum packed_object_type {\n+\t\t\t\tPACKED_OBJECT_TYPE_UNKNOWN,\n+\t\t\t\tPACKED_OBJECT_TYPE_FULL,\n+\t\t\t\tPACKED_OBJECT_TYPE_OFS_DELTA,\n+\t\t\t\tPACKED_OBJECT_TYPE_REF_DELTA,\n+\t\t\t} type;\n+\t\t} packed;\n+\t} u;\n+};\n+\n struct object_info {\n \t/* Request */\n \tenum object_type *typep;\n@@ -269,32 +301,20 @@ struct object_info {\n \t */\n \ttime_t *mtimep;\n \n+\t/*\n+\t * Backend-specific information that tells the caller where exactly an\n+\t * object was looked up from. This information should help disambiguate\n+\t * object lookups in case the same object exists in multiple sources,\n+\t * or multiple times in the same source.\n+\t */\n+\tstruct object_info_source *sourcep;\n+\n \t/* Response */\n \tenum {\n \t\tOI_CACHED,\n \t\tOI_LOOSE,\n \t\tOI_PACKED,\n \t} whence;\n-\tunion {\n-\t\t/*\n-\t\t * struct {\n-\t\t * \t... Nothing to expose in this case\n-\t\t * } cached;\n-\t\t * struct {\n-\t\t * \t... Nothing to expose in this case\n-\t\t * } loose;\n-\t\t */\n-\t\tstruct {\n-\t\t\tstruct packed_git *pack;\n-\t\t\toff_t offset;\n-\t\t\tenum packed_object_type {\n-\t\t\t\tPACKED_OBJECT_TYPE_UNKNOWN,\n-\t\t\t\tPACKED_OBJECT_TYPE_FULL,\n-\t\t\t\tPACKED_OBJECT_TYPE_OFS_DELTA,\n-\t\t\t\tPACKED_OBJECT_TYPE_REF_DELTA,\n-\t\t\t} type;\n-\t\t} packed;\n-\t} u;\n };\n \n /*\ndiff --git a/packfile.c b/packfile.c\nindex 2b741d7a76..688c410b35 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1422,22 +1422,25 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n \t}\n \n \toi->whence = OI_PACKED;\n-\toi->u.packed.offset = obj_offset;\n-\toi->u.packed.pack = p;\n \n-\tswitch (type) {\n-\tcase OBJ_NONE:\n-\t\toi->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;\n-\t\tbreak;\n-\tcase OBJ_REF_DELTA:\n-\t\toi->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;\n-\t\tbreak;\n-\tcase OBJ_OFS_DELTA:\n-\t\toi->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;\n-\t\tbreak;\n-\tdefault:\n-\t\toi->u.packed.type = PACKED_OBJECT_TYPE_FULL;\n-\t\tbreak;\n+\tif (oi->sourcep) {\n+\t\toi->sourcep->u.packed.offset = obj_offset;\n+\t\toi->sourcep->u.packed.pack = p;\n+\n+\t\tswitch (type) {\n+\t\tcase OBJ_NONE:\n+\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;\n+\t\t\tbreak;\n+\t\tcase OBJ_REF_DELTA:\n+\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;\n+\t\t\tbreak;\n+\t\tcase OBJ_OFS_DELTA:\n+\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_FULL;\n+\t\t\tbreak;\n+\t\t}\n \t}\n \n \tret = 0;\ndiff --git a/reachable.c b/reachable.c\nindex 101cfc2727..2fc5b82d62 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -235,7 +235,8 @@ static int add_recent_object(const struct object_id *oid,\n \tadd_pending_object(data->revs, obj, \"\");\n \tif (data->cb) {\n \t\tif (oi->whence == OI_PACKED)\n-\t\t\tdata->cb(obj, oi->u.packed.pack, oi->u.packed.offset, *oi->mtimep);\n+\t\t\tdata->cb(obj, oi->sourcep->u.packed.pack,\n+\t\t\t\t oi->sourcep->u.packed.offset, *oi->mtimep);\n \t\telse\n \t\t\tdata->cb(obj, NULL, 0, *oi->mtimep);\n \t}\n@@ -252,9 +253,11 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \tunsigned flags;\n \tenum object_type type;\n \ttime_t mtime;\n+\tstruct object_info_source oi_source;\n \tstruct object_info oi = {\n \t\t.mtimep = &mtime,\n \t\t.typep = &type,\n+\t\t.sourcep = &oi_source,\n \t};\n \tint r;\n \n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546311","messageId":"20260624-b4-pks-odb-drop-whence-v1-3-8d1877b790ac@pks.im","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im","subject":"[PATCH 3/6] odb: add `source` field to struct object_info_source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-24T12:19:16Z","receivedAt":"2026-06-24T12:19:36Z","isPatch":true,"body":"The previous commit introduced `struct object_info_source` as an opt-in\ncontainer for backend-specific information, but for now we only moved\npreexisting data into this structure. Most importantly, the caller has\nno way yet to learn about which source an object was actually looked up\nfrom. Instead, callers have to rely on the `whence` enum to distinguish\nthe object type, but cannot use that enum to tell the object source.\n\nAdd a `struct odb_source *source` field to the structure and populate it\nfrom each backend's lookup path.\n\nThe `whence` enum is still set and used by callers; it will be removed\nin a subsequent commit now that `sourcep->source` can identify the\nbackend on its own.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.h                 | 3 +++\n odb/source-inmemory.c | 3 +++\n odb/source-loose.c    | 2 ++\n packfile.c            | 6 +++++-\n 4 files changed, 13 insertions(+), 1 deletion(-)\n\ndiff --git a/odb.h b/odb.h\nindex 770900289a..330a55879e 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -253,6 +253,9 @@ int odb_pretend_object(struct object_database *odb,\n  * more about how exactly it is stored.\n  */\n struct object_info_source {\n+\t/* The source that this object has been looked up from. */\n+\tstruct odb_source *source;\n+\n \t/*\n \t * Backend-specific information about the specific object. This can be\n \t * used for example to uniquely identify a given object in case it\ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex e004566d76..2328e62687 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -52,6 +52,9 @@ static void populate_object_info(struct odb_source_inmemory *source,\n \t\t*oi->contentp = xmemdupz(object->buf, object->size);\n \tif (oi->mtimep)\n \t\t*oi->mtimep = 0;\n+\tif (oi->sourcep)\n+\t\toi->sourcep->source = &source->base;\n+\n \toi->whence = OI_CACHED;\n }\n \ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex 66e6bb8d3f..5c4e9892b5 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -196,6 +196,8 @@ static int read_object_info_from_path(struct odb_source_loose *loose,\n \t\t\toi->typep = NULL;\n \t\tif (oi->delta_base_oid)\n \t\t\toidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo);\n+\t\tif (oi->sourcep && !ret)\n+\t\t\toi->sourcep->source = &loose->base;\n \t\tif (!ret)\n \t\t\toi->whence = OI_LOOSE;\n \t}\ndiff --git a/packfile.c b/packfile.c\nindex 688c410b35..fa22095b75 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1324,7 +1324,7 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,\n \thashmap_add(&delta_base_cache, &ent->ent);\n }\n \n-int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n+int packed_object_info_with_index_pos(struct odb_source_packed *source,\n \t\t\t\t      struct packed_git *p, off_t obj_offset,\n \t\t\t\t      uint32_t *maybe_index_pos, struct object_info *oi)\n {\n@@ -1424,6 +1424,10 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n \toi->whence = OI_PACKED;\n \n \tif (oi->sourcep) {\n+\t\tif (!source)\n+\t\t\tBUG(\"cannot request source without an owning source\");\n+\t\toi->sourcep->source = &source->base;\n+\n \t\toi->sourcep->u.packed.offset = obj_offset;\n \t\toi->sourcep->u.packed.pack = p;\n \n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546312","messageId":"20260624-b4-pks-odb-drop-whence-v1-4-8d1877b790ac@pks.im","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im","subject":"[PATCH 4/6] treewide: convert users of `whence` to the new source field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-24T12:19:17Z","receivedAt":"2026-06-24T12:19:39Z","isPatch":true,"body":"The `whence` field has become redundant now that callers can learn about\nthe exact source an object has been looked up from via the `struct\nobject_info_source::source` field.\n\nAdapt callers to use the new field. Note that all callsites already set\nup the `info.sourcep` request pointer, so the conversion is rather\nstraight-forward.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     | 2 +-\n builtin/index-pack.c   | 3 ++-\n builtin/pack-objects.c | 2 +-\n reachable.c            | 2 +-\n 4 files changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex adc626ce30..1b96150e5b 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -834,7 +834,7 @@ static int batch_one_object_oi(const struct object_id *oid,\n \t\t\t       void *_payload)\n {\n \tstruct for_each_object_payload *payload = _payload;\n-\tif (oi && oi->whence == OI_PACKED)\n+\tif (oi && oi->sourcep->source->type == ODB_SOURCE_PACKED)\n \t\treturn payload->callback(oid, oi->sourcep->u.packed.pack,\n \t\t\t\t\t oi->sourcep->u.packed.offset,\n \t\t\t\t\t payload->payload);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 77af26db8f..1b03b07e5e 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1833,7 +1833,8 @@ static void repack_local_links(void)\n \t\tif (odb_read_object_info_extended(the_repository->objects, oid, &info, 0))\n \t\t\t/* Missing; assume it is a promisor object */\n \t\t\tcontinue;\n-\t\tif (info.whence == OI_PACKED && info_source.u.packed.pack->pack_promisor)\n+\t\tif (info_source.source->type == ODB_SOURCE_PACKED &&\n+\t\t    info_source.u.packed.pack->pack_promisor)\n \t\t\tcontinue;\n \n \t\tif (!cmd.args.nr) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 9deb37e9e8..d0fdfad750 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -5010,7 +5010,7 @@ static int is_not_in_promisor_pack_obj(struct object *obj, void *data UNUSED)\n \n \tif (odb_read_object_info_extended(the_repository->objects, &obj->oid, &info, 0))\n \t\tBUG(\"should_include_obj should only be called on existing objects\");\n-\treturn info.whence != OI_PACKED || !info_source.u.packed.pack->pack_promisor;\n+\treturn info_source.source->type != ODB_SOURCE_PACKED || !info_source.u.packed.pack->pack_promisor;\n }\n \n static int is_not_in_promisor_pack(struct commit *commit, void *data) {\ndiff --git a/reachable.c b/reachable.c\nindex 2fc5b82d62..123a658944 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -234,7 +234,7 @@ static int add_recent_object(const struct object_id *oid,\n \n \tadd_pending_object(data->revs, obj, \"\");\n \tif (data->cb) {\n-\t\tif (oi->whence == OI_PACKED)\n+\t\tif (oi->sourcep->source->type == ODB_SOURCE_PACKED)\n \t\t\tdata->cb(obj, oi->sourcep->u.packed.pack,\n \t\t\t\t oi->sourcep->u.packed.offset, *oi->mtimep);\n \t\telse\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546313","messageId":"20260624-b4-pks-odb-drop-whence-v1-5-8d1877b790ac@pks.im","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im","subject":"[PATCH 5/6] odb: drop `whence` field from object info","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-24T12:19:18Z","receivedAt":"2026-06-24T12:19:42Z","isPatch":true,"body":"In the preceding commits we have migrated all callers to derive their\ninformation of how a specific object is stored to use the new object\ninfo source instead, and hence the field is now unused. Drop it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c                 | 1 -\n odb.h                 | 7 -------\n odb/source-inmemory.c | 2 --\n odb/source-loose.c    | 2 --\n packfile.c            | 2 --\n 5 files changed, 14 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 99f4e7551c..82c41f1793 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -691,7 +691,6 @@ static int oid_object_info_convert(struct repository *r,\n \t\t\treturn -1;\n \t\t}\n \t}\n-\tinput_oi->whence = new_oi.whence;\n \tif (input_oi->sourcep)\n \t\t*input_oi->sourcep = *new_oi.sourcep;\n \treturn ret;\ndiff --git a/odb.h b/odb.h\nindex 330a55879e..e0d05eaf87 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -311,13 +311,6 @@ struct object_info {\n \t * or multiple times in the same source.\n \t */\n \tstruct object_info_source *sourcep;\n-\n-\t/* Response */\n-\tenum {\n-\t\tOI_CACHED,\n-\t\tOI_LOOSE,\n-\t\tOI_PACKED,\n-\t} whence;\n };\n \n /*\ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex 2328e62687..008e49bfe9 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -54,8 +54,6 @@ static void populate_object_info(struct odb_source_inmemory *source,\n \t\t*oi->mtimep = 0;\n \tif (oi->sourcep)\n \t\toi->sourcep->source = &source->base;\n-\n-\toi->whence = OI_CACHED;\n }\n \n static int odb_source_inmemory_read_object_info(struct odb_source *source,\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex 5c4e9892b5..e743ccab42 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -198,8 +198,6 @@ static int read_object_info_from_path(struct odb_source_loose *loose,\n \t\t\toidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo);\n \t\tif (oi->sourcep && !ret)\n \t\t\toi->sourcep->source = &loose->base;\n-\t\tif (!ret)\n-\t\t\toi->whence = OI_LOOSE;\n \t}\n \n \treturn ret;\ndiff --git a/packfile.c b/packfile.c\nindex fa22095b75..4a8c108034 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1421,8 +1421,6 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source,\n \t\t\toidclr(oi->delta_base_oid, p->repo->hash_algo);\n \t}\n \n-\toi->whence = OI_PACKED;\n-\n \tif (oi->sourcep) {\n \t\tif (!source)\n \t\t\tBUG(\"cannot request source without an owning source\");\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546314","messageId":"20260624-b4-pks-odb-drop-whence-v1-6-8d1877b790ac@pks.im","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im","subject":"[PATCH 6/6] odb: document object info fields","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-24T12:19:19Z","receivedAt":"2026-06-24T12:19:44Z","isPatch":true,"body":"Some of the fields in `struct object_info` are undocumented. Add these\nmissing comments.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.h | 18 +++++++++++++++++-\n 1 file changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/odb.h b/odb.h\nindex e0d05eaf87..a44ec46b08 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -283,12 +283,28 @@ struct object_info_source {\n \t} u;\n };\n \n+/*\n+ * The object info contains the query and response that is to be used for\n+ * functions that end up reading object information. Callers are expected to\n+ * populate pointers whose information they want to request.\n+ */\n struct object_info {\n-\t/* Request */\n+\t/* The object type. */\n \tenum object_type *typep;\n+\n+\t/* The inflated object size in bytes. */\n \tsize_t *sizep;\n+\n+\t/* The object size as stored on disk. */\n \toff_t *disk_sizep;\n+\n+\t/*\n+\t * The base the object is deltified against, in case it is stored as a\n+\t * delta.\n+\t */\n \tstruct object_id *delta_base_oid;\n+\n+\t/* The object contents. Ownership of memory goes over to the caller. */\n \tvoid **contentp;\n \n \t/*\n\n-- \n2.55.0.rc1.745.g43192e7977.dirty\n\n"},{"id":"546338","messageId":"xmqq5x37kgjt.fsf@gitster.g","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im","subject":"Re: [PATCH 0/6] odb: refactor source-specific information in object info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-24T17:13:26Z","receivedAt":"2026-06-24T17:13:29Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> this patch series refactors `struct object_info` to not contain the\n> `whence` field anymore.\n>\n> This field only gave the caller information about the type of source\n> this was read from, but it didn't allow them to figure out which source\n> specifically yielded the object. So instead, we replace this information\n> with a new `struct object_info_source` field that both contains info\n> about the source, and any backend-specific data.\n\nGreat.\n\n"},{"id":"546683","messageId":"akKge0zmT3WSfdyz@denethor","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-1-8d1877b790ac@pks.im","subject":"Re: [PATCH 1/6] packfile: thread odb_source_packed through packed_object_info()","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-29T17:01:47Z","receivedAt":"2026-06-29T17:01:51Z","isPatch":true,"body":"On 26/06/24 02:19PM, Patrick Steinhardt wrote:\n> Add an optional `struct odb_source_packed *source` parameter to\n> `packed_object_info()` and `packed_object_info_with_index_pos()`. This\n> parameter is unused at this point in time, but it will be used in a\n> follow-up commit so that we can record the source of a specific object.\n\nOk so `packed_object_info()` is responsible for populating `struct\nobject_info` from the provided packfile and object offset. By\nadditionally providing the object source, the ultimate goal is to store\nthe this information in `struct object_info` or some equivalent\nstructure.\n\nAt first, I wondered if it would make more sense for `struct packed_git`\nto record the `struct odb_source_packed` it comes from, but maybe that\nwouldn't be the best layer to handle this bookkeeping?\n\n> Note that callers in \"odb/source-packed.c\" pass the already-available\n> source, but all other callers pass `NULL` instead. This is fine though,\n> as we only care about populating this info when called via the packed\n> store.\n\nHmmm, is this because knowing the ODB source the object comes from is\nonly useful for callers from in \"odb/source-packed.c\"? Maybe this will\nbecome a bit more clear to me in subsequent patches.\n\nThe patch itself is just wiring up `struct odb_source_packed` to the\nabove mentioned functions, but doesn't do anything yet with them. This\nstep looks trivially correct though.\n\n-Justin\n"},{"id":"546686","messageId":"akKmwPGSAGEGKZjL@denethor","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-2-8d1877b790ac@pks.im","subject":"Re: [PATCH 2/6] odb: make backend-specific fields optional","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-29T17:25:21Z","receivedAt":"2026-06-29T17:25:27Z","isPatch":true,"body":"On 26/06/24 02:19PM, Patrick Steinhardt wrote:\n> The `struct object_info` carries two pieces of information\n> about how an object was looked up:\n> \n>   - The `whence` enum identifying the backend.\n> \n>   - The backend-tagged union `u` exposing backend-specific details\n>     (currently only the packed-source case, which records the owning\n>     pack, offset and packed object type).\n> \n> The union is populated unconditionally, even though most callers don't\n> care about provenance at all.\n> \n> Split the backend-specific union out into a new public type, `struct\n> object_info_source`, and make the object info structure carry it via\n> just another opt-in request pointer. As with all the other requestable\n> information, callers that need source info allocate a `struct\n> object_info_source` on the stack and point `sourcep` at it; callers that\n> don't care about it simply leave the field as a `NULL` pointer. Adapt\n> callers accordingly.\n\nSince not all callers may require this information, requiring callers to\nexplicitly request it seems reasonable to me.\n\n> Note that the `whence` enum is strictly-speaking also backend-specific\n> information, so it would be another good candidate to be moved into the\n> `struct object_info_source`. For now though it is left alone, as it will\n> be replaced by a `struct odb_source` pointer in a subsequent commit.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/cat-file.c     |  8 +++++--\n>  builtin/index-pack.c   |  8 +++++--\n>  builtin/pack-objects.c | 15 +++++++++----\n>  odb.c                  |  3 ++-\n>  odb.h                  | 60 +++++++++++++++++++++++++++++++++-----------------\n>  packfile.c             | 33 ++++++++++++++-------------\n>  reachable.c            |  5 ++++-\n>  7 files changed, 87 insertions(+), 45 deletions(-)\n> \n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 8726485f1f..adc626ce30 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -835,7 +835,8 @@ static int batch_one_object_oi(const struct object_id *oid,\n>  {\n>  \tstruct for_each_object_payload *payload = _payload;\n>  \tif (oi && oi->whence == OI_PACKED)\n> -\t\treturn payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,\n> +\t\treturn payload->callback(oid, oi->sourcep->u.packed.pack,\n> +\t\t\t\t\t oi->sourcep->u.packed.offset,\n>  \t\t\t\t\t payload->payload);\n\nWe update callsites now that object source info is stored differently in\n`struct object_info`.\n\n>  \treturn payload->callback(oid, NULL, 0, payload->payload);\n>  }\n> @@ -906,7 +907,10 @@ static void batch_each_object(struct batch_options *opt,\n>  \t\t\t\t\t\t&payload, flags);\n>  \t\t}\n>  \t} else {\n> -\t\tstruct object_info oi = { 0 };\n> +\t\tstruct object_info_source oi_source;\n> +\t\tstruct object_info oi = {\n> +\t\t\t.sourcep = &oi_source,\n> +\t\t};\n\nCaller that wish to know information regarding the source of the object\nare required to explicitly request it. Makes sense.\n\n[snip]\n>  struct object_info {\n>  \t/* Request */\n>  \tenum object_type *typep;\n> @@ -269,32 +301,20 @@ struct object_info {\n>  \t */\n>  \ttime_t *mtimep;\n>  \n> +\t/*\n> +\t * Backend-specific information that tells the caller where exactly an\n> +\t * object was looked up from. This information should help disambiguate\n> +\t * object lookups in case the same object exists in multiple sources,\n> +\t * or multiple times in the same source.\n> +\t */\n> +\tstruct object_info_source *sourcep;\n\nTo me, the name `sourcep` makes me think a pointer to `struct\nodb_source`. This did confuse me slightly when initially reading, but\nI'm not sure it's worth it to be overly verbose here.\n\n[snip]\n> diff --git a/packfile.c b/packfile.c\n> index 2b741d7a76..688c410b35 100644\n> --- a/packfile.c\n> +++ b/packfile.c\n> @@ -1422,22 +1422,25 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n>  \t}\n>  \n>  \toi->whence = OI_PACKED;\n> -\toi->u.packed.offset = obj_offset;\n> -\toi->u.packed.pack = p;\n>  \n> -\tswitch (type) {\n> -\tcase OBJ_NONE:\n> -\t\toi->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;\n> -\t\tbreak;\n> -\tcase OBJ_REF_DELTA:\n> -\t\toi->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;\n> -\t\tbreak;\n> -\tcase OBJ_OFS_DELTA:\n> -\t\toi->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;\n> -\t\tbreak;\n> -\tdefault:\n> -\t\toi->u.packed.type = PACKED_OBJECT_TYPE_FULL;\n> -\t\tbreak;\n> +\tif (oi->sourcep) {\n> +\t\toi->sourcep->u.packed.offset = obj_offset;\n> +\t\toi->sourcep->u.packed.pack = p;\n> +\n> +\t\tswitch (type) {\n> +\t\tcase OBJ_NONE:\n> +\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;\n> +\t\t\tbreak;\n> +\t\tcase OBJ_REF_DELTA:\n> +\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;\n> +\t\t\tbreak;\n> +\t\tcase OBJ_OFS_DELTA:\n> +\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;\n> +\t\t\tbreak;\n> +\t\tdefault:\n> +\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_FULL;\n> +\t\t\tbreak;\n> +\t\t}\n\nSource information is no longer unconditionally set.\n\nOverall, this patch looks good.\n\n-Justin\n"},{"id":"546688","messageId":"akKtc4ybxFRVJmNv@denethor","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-3-8d1877b790ac@pks.im","subject":"Re: [PATCH 3/6] odb: add `source` field to struct object_info_source","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-29T17:49:08Z","receivedAt":"2026-06-29T17:49:12Z","isPatch":true,"body":"On 26/06/24 02:19PM, Patrick Steinhardt wrote:\n> The previous commit introduced `struct object_info_source` as an opt-in\n> container for backend-specific information, but for now we only moved\n> preexisting data into this structure. Most importantly, the caller has\n> no way yet to learn about which source an object was actually looked up\n> from. Instead, callers have to rely on the `whence` enum to distinguish\n> the object type, but cannot use that enum to tell the object source.\n> \n> Add a `struct odb_source *source` field to the structure and populate it\n> from each backend's lookup path.\n\nMakes sense.\n\n> The `whence` enum is still set and used by callers; it will be removed\n> in a subsequent commit now that `sourcep->source` can identify the\n> backend on its own.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  odb.h                 | 3 +++\n>  odb/source-inmemory.c | 3 +++\n>  odb/source-loose.c    | 2 ++\n>  packfile.c            | 6 +++++-\n>  4 files changed, 13 insertions(+), 1 deletion(-)\n> \n> diff --git a/odb.h b/odb.h\n> index 770900289a..330a55879e 100644\n> --- a/odb.h\n> +++ b/odb.h\n> @@ -253,6 +253,9 @@ int odb_pretend_object(struct object_database *odb,\n>   * more about how exactly it is stored.\n>   */\n>  struct object_info_source {\n> +\t/* The source that this object has been looked up from. */\n> +\tstruct odb_source *source;\n\nHere we add the `struct odb_source` so we can begin recording it.\n\n> +\n>  \t/*\n>  \t * Backend-specific information about the specific object. This can be\n>  \t * used for example to uniquely identify a given object in case it\n> diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\n> index e004566d76..2328e62687 100644\n> --- a/odb/source-inmemory.c\n> +++ b/odb/source-inmemory.c\n> @@ -52,6 +52,9 @@ static void populate_object_info(struct odb_source_inmemory *source,\n>  \t\t*oi->contentp = xmemdupz(object->buf, object->size);\n>  \tif (oi->mtimep)\n>  \t\t*oi->mtimep = 0;\n> +\tif (oi->sourcep)\n> +\t\toi->sourcep->source = &source->base;\n\nHere we set the source for the in-memory backend.\n\n> +\n>  \toi->whence = OI_CACHED;\n>  }\n>  \n> diff --git a/odb/source-loose.c b/odb/source-loose.c\n> index 66e6bb8d3f..5c4e9892b5 100644\n> --- a/odb/source-loose.c\n> +++ b/odb/source-loose.c\n> @@ -196,6 +196,8 @@ static int read_object_info_from_path(struct odb_source_loose *loose,\n>  \t\t\toi->typep = NULL;\n>  \t\tif (oi->delta_base_oid)\n>  \t\t\toidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo);\n> +\t\tif (oi->sourcep && !ret)\n> +\t\t\toi->sourcep->source = &loose->base;\n\nHere it is set for the loose backend.\n\n>  \t\tif (!ret)\n>  \t\t\toi->whence = OI_LOOSE;\n>  \t}\n> diff --git a/packfile.c b/packfile.c\n> index 688c410b35..fa22095b75 100644\n> --- a/packfile.c\n> +++ b/packfile.c\n> @@ -1324,7 +1324,7 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,\n>  \thashmap_add(&delta_base_cache, &ent->ent);\n>  }\n>  \n> -int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n> +int packed_object_info_with_index_pos(struct odb_source_packed *source,\n>  \t\t\t\t      struct packed_git *p, off_t obj_offset,\n>  \t\t\t\t      uint32_t *maybe_index_pos, struct object_info *oi)\n>  {\n> @@ -1424,6 +1424,10 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n>  \toi->whence = OI_PACKED;\n>  \n>  \tif (oi->sourcep) {\n> +\t\tif (!source)\n> +\t\t\tBUG(\"cannot request source without an owning source\");\n> +\t\toi->sourcep->source = &source->base;\n\nAnd here it is set for the packed backend. Looks good.\n\nNaive question: I understand that some `packed_info_object()` callers\nmay not have the `struct odb_source` on hand, but when the `struct\npacked_git` is intially setup, is it not always known the ODB source it\ncomes from? It makes me wonder if the ODB source should also be recorded\nwhen `struct packed_git` is initialized.\n\n-Justin\n"},{"id":"546691","messageId":"akKwnZ8hReIfKyyM@denethor","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-4-8d1877b790ac@pks.im","subject":"Re: [PATCH 4/6] treewide: convert users of `whence` to the new source field","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-29T17:55:26Z","receivedAt":"2026-06-29T17:55:28Z","isPatch":true,"body":"On 26/06/24 02:19PM, Patrick Steinhardt wrote:\n> The `whence` field has become redundant now that callers can learn about\n> the exact source an object has been looked up from via the `struct\n> object_info_source::source` field.\n> \n> Adapt callers to use the new field. Note that all callsites already set\n> up the `info.sourcep` request pointer, so the conversion is rather\n> straight-forward.\n\nOk, looks like we only adapt the callers here in this patch. I'm\nguessing a subsequent patch will do the remaining cleanup.\n\nLooking good.\n\n-Justin\n"},{"id":"546692","messageId":"akKxwkg0jbnkL1XW@denethor","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-5-8d1877b790ac@pks.im","subject":"Re: [PATCH 5/6] odb: drop `whence` field from object info","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-29T17:57:38Z","receivedAt":"2026-06-29T17:57:40Z","isPatch":true,"body":"On 26/06/24 02:19PM, Patrick Steinhardt wrote:\n> In the preceding commits we have migrated all callers to derive their\n> information of how a specific object is stored to use the new object\n> info source instead, and hence the field is now unused. Drop it.\n\nMakes sense.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n[snip]\n> diff --git a/odb.h b/odb.h\n> index 330a55879e..e0d05eaf87 100644\n> --- a/odb.h\n> +++ b/odb.h\n> @@ -311,13 +311,6 @@ struct object_info {\n>  \t * or multiple times in the same source.\n>  \t */\n>  \tstruct object_info_source *sourcep;\n> -\n> -\t/* Response */\n> -\tenum {\n> -\t\tOI_CACHED,\n> -\t\tOI_LOOSE,\n> -\t\tOI_PACKED,\n> -\t} whence;\n>  };\n\nNice. This patch also looks good.\n\n-Justin\n"},{"id":"546710","messageId":"xmqqmrwdul8y.fsf@gitster.g","threadId":"65865","inReplyTo":"akKtc4ybxFRVJmNv@denethor","subject":"Re: [PATCH 3/6] odb: add `source` field to struct object_info_source","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-29T20:47:41Z","receivedAt":"2026-06-29T20:47:43Z","isPatch":true,"body":"Justin Tobler <jltobler@gmail.com> writes:\n\n>> @@ -1424,6 +1424,10 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n>>  \toi->whence = OI_PACKED;\n>>  \n>>  \tif (oi->sourcep) {\n>> +\t\tif (!source)\n>> +\t\t\tBUG(\"cannot request source without an owning source\");\n>> +\t\toi->sourcep->source = &source->base;\n>\n> And here it is set for the packed backend. Looks good.\n>\n> Naive question: I understand that some `packed_info_object()` callers\n> may not have the `struct odb_source` on hand, but when the `struct\n> packed_git` is intially setup, is it not always known the ODB source it\n> comes from? It makes me wonder if the ODB source should also be recorded\n> when `struct packed_git` is initialized.\n\nAs with your reaction to [PATCH 1/6], I do share this puzzlement: if\nthe source can almost always be NULL, what is it good for and isn't\nit something that can be computed from the available information?\n\nPerhaps it is the naming?\n\nI am confused what the above quoted code actually is doing (\"if you\nhave a source, then grab its base and set it to .source member of\nthe struct the out parameter points at\", makes it sound like the out\nparameter sourcep should be pointing at a structure with .base\nmember, not .source member, or perhaps the caller should be passing\n&oi->sourcep->source as *base to be assigned to, or something).\n"},{"id":"546752","messageId":"akOoY4LKve0-ys9_@pks.im","threadId":"65865","inReplyTo":"akKmwPGSAGEGKZjL@denethor","subject":"Re: [PATCH 2/6] odb: make backend-specific fields optional","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-30T11:28:35Z","receivedAt":"2026-06-30T11:28:46Z","isPatch":true,"body":"On Mon, Jun 29, 2026 at 12:25:21PM -0500, Justin Tobler wrote:\n> On 26/06/24 02:19PM, Patrick Steinhardt wrote:\n> > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > index 8726485f1f..adc626ce30 100644\n> > --- a/builtin/cat-file.c\n> > +++ b/builtin/cat-file.c\n> > @@ -269,32 +301,20 @@ struct object_info {\n> >  \t */\n> >  \ttime_t *mtimep;\n> >  \n> > +\t/*\n> > +\t * Backend-specific information that tells the caller where exactly an\n> > +\t * object was looked up from. This information should help disambiguate\n> > +\t * object lookups in case the same object exists in multiple sources,\n> > +\t * or multiple times in the same source.\n> > +\t */\n> > +\tstruct object_info_source *sourcep;\n> \n> To me, the name `sourcep` makes me think a pointer to `struct\n> odb_source`. This did confuse me slightly when initially reading, but\n> I'm not sure it's worth it to be overly verbose here.\n\nYeah, good point. But as you say, I haven't been able to really come up\nwith a name that is not overly verbose. We could potentially rename the\nstructure itself to `odb_source_info` and then call the field itself\n`source_infop`. Would that help?\n\nPatrick\n"},{"id":"546753","messageId":"akOocmBK61006i-p@pks.im","threadId":"65865","inReplyTo":"akKge0zmT3WSfdyz@denethor","subject":"Re: [PATCH 1/6] packfile: thread odb_source_packed through packed_object_info()","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-30T11:28:50Z","receivedAt":"2026-06-30T11:28:54Z","isPatch":true,"body":"On Mon, Jun 29, 2026 at 12:01:47PM -0500, Justin Tobler wrote:\n> On 26/06/24 02:19PM, Patrick Steinhardt wrote:\n> > Add an optional `struct odb_source_packed *source` parameter to\n> > `packed_object_info()` and `packed_object_info_with_index_pos()`. This\n> > parameter is unused at this point in time, but it will be used in a\n> > follow-up commit so that we can record the source of a specific object.\n> \n> Ok so `packed_object_info()` is responsible for populating `struct\n> object_info` from the provided packfile and object offset. By\n> additionally providing the object source, the ultimate goal is to store\n> the this information in `struct object_info` or some equivalent\n> structure.\n> \n> At first, I wondered if it would make more sense for `struct packed_git`\n> to record the `struct odb_source_packed` it comes from, but maybe that\n> wouldn't be the best layer to handle this bookkeeping?\n\nYeah, I was thinking about that, too. But I feel like that would be a\nlayering violation: a packfile can in theory live standalone without a\nsource. So tracking that information as part of the packfile itself just\nfeels wrong to me.\n\nWe could in theory adapt all callers of `packed_object_info()` to track\nthe origin of the packfiles. I _think_ that should be feasible at almost\nall sites. But I'm just not sure myself whether that really buys us much\nin the first place, because...\n\n> > Note that callers in \"odb/source-packed.c\" pass the already-available\n> > source, but all other callers pass `NULL` instead. This is fine though,\n> > as we only care about populating this info when called via the packed\n> > store.\n> \n> Hmmm, is this because knowing the ODB source the object comes from is\n> only useful for callers from in \"odb/source-packed.c\"? Maybe this will\n> become a bit more clear to me in subsequent patches.\n\n... right now none none of the callers that call `packed_object_info()`\ndirectly care about the source information at all. It's really only\ncallers of `odb_read_object_info()` that do.\n\nSo I understand that this feels a bit iffy. But arguably, the right way\nto fix this is to stop using `packed_object_info()` altogether. It is an\ninternal implementation detail of the object source backend, and ideally\nwe shouldn't need to care about it.\n\nI already have a patch series that fixes git-cat-file(1). The\ncommit-graph is a bigger building site, as I'm still not a 100% decided\non how to represent such auxiliary data structures with pluggable object\nbackends. And for the other commands I don't yet have a good answer.\n\nPatrick\n"},{"id":"546754","messageId":"akOod6X1a2axIXKZ@pks.im","threadId":"65865","inReplyTo":"xmqqmrwdul8y.fsf@gitster.g","subject":"Re: [PATCH 3/6] odb: add `source` field to struct object_info_source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-30T11:28:55Z","receivedAt":"2026-06-30T11:29:00Z","isPatch":true,"body":"On Mon, Jun 29, 2026 at 01:47:41PM -0700, Junio C Hamano wrote:\n> Justin Tobler <jltobler@gmail.com> writes:\n> \n> >> @@ -1424,6 +1424,10 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n> >>  \toi->whence = OI_PACKED;\n> >>  \n> >>  \tif (oi->sourcep) {\n> >> +\t\tif (!source)\n> >> +\t\t\tBUG(\"cannot request source without an owning source\");\n> >> +\t\toi->sourcep->source = &source->base;\n> >\n> > And here it is set for the packed backend. Looks good.\n> >\n> > Naive question: I understand that some `packed_info_object()` callers\n> > may not have the `struct odb_source` on hand, but when the `struct\n> > packed_git` is intially setup, is it not always known the ODB source it\n> > comes from? It makes me wonder if the ODB source should also be recorded\n> > when `struct packed_git` is initialized.\n\nI've addressed this comment on patch 1.\n\n> As with your reaction to [PATCH 1/6], I do share this puzzlement: if\n> the source can almost always be NULL, what is it good for and isn't\n> it something that can be computed from the available information?\n\nIt's not almost always NULL, even though it looks like this because we\nended up adapting more callers to pass `NULL` than we adapted callers to\npass an actual source. But in the end it's rather the opposite: there\nare very few low-level callers that don't have the source info\navailable, and everyone else instead uses `odb_read_object_info()`,\nwhere we do have it available. But those callers don't need to be\nadjusted, so they weren't visible in the diff.\n\n> Perhaps it is the naming?\n\nYeah, as Justin pointed out, calling this `sourcep` is confusing.\n\n> I am confused what the above quoted code actually is doing (\"if you\n> have a source, then grab its base and set it to .source member of\n> the struct the out parameter points at\", makes it sound like the out\n> parameter sourcep should be pointing at a structure with .base\n> member, not .source member, or perhaps the caller should be passing\n> &oi->sourcep->source as *base to be assigned to, or something).\n\nWe have to return the generic source here, not the specialized source,\nso that this interface can be used by every implementation. Other sites\nwould end up storing their own source, which of course would have a\ndifferent specialized backend.\n\nSo an alternative to write this would have been:\n\n    oi->sourcep->source = (struct odb_source *) source;\n\nBut by assigning the base we avoid having to cast.\n\nPatrick\n"},{"id":"546799","messageId":"xmqqv7b0rmt6.fsf@gitster.g","threadId":"65865","inReplyTo":"akOod6X1a2axIXKZ@pks.im","subject":"Re: [PATCH 3/6] odb: add `source` field to struct object_info_source","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-30T16:54:29Z","receivedAt":"2026-06-30T16:54:32Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, Jun 29, 2026 at 01:47:41PM -0700, Junio C Hamano wrote:\n>> Justin Tobler <jltobler@gmail.com> writes:\n>> \n>> >> @@ -1424,6 +1424,10 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n>> >>  \toi->whence = OI_PACKED;\n>> >>  \n>> >>  \tif (oi->sourcep) {\n>> >> +\t\tif (!source)\n>> >> +\t\t\tBUG(\"cannot request source without an owning source\");\n>> >> +\t\toi->sourcep->source = &source->base;\n>> >\n>> > And here it is set for the packed backend. Looks good.\n>> >\n>> > Naive question: I understand that some `packed_info_object()` callers\n>> > may not have the `struct odb_source` on hand, but when the `struct\n>> > packed_git` is intially setup, is it not always known the ODB source it\n>> > comes from? It makes me wonder if the ODB source should also be recorded\n>> > when `struct packed_git` is initialized.\n>\n> I've addressed this comment on patch 1.\n>\n>> As with your reaction to [PATCH 1/6], I do share this puzzlement: if\n>> the source can almost always be NULL, what is it good for and isn't\n>> it something that can be computed from the available information?\n>\n> It's not almost always NULL, even though it looks like this because we\n> ended up adapting more callers to pass `NULL` than we adapted callers to\n> pass an actual source. But in the end it's rather the opposite: there\n> are very few low-level callers that don't have the source info\n> available, and everyone else instead uses `odb_read_object_info()`,\n> where we do have it available. But those callers don't need to be\n> adjusted, so they weren't visible in the diff.\n>\n>> Perhaps it is the naming?\n>\n> Yeah, as Justin pointed out, calling this `sourcep` is confusing.\n\nOK, everything makes sense.\n\n>> I am confused what the above quoted code actually is doing (\"if you\n>> have a source, then grab its base and set it to .source member of\n>> the struct the out parameter points at\", makes it sound like the out\n>> parameter sourcep should be pointing at a structure with .base\n>> member, not .source member, or perhaps the caller should be passing\n>> &oi->sourcep->source as *base to be assigned to, or something).\n>\n> We have to return the generic source here, not the specialized source,\n> so that this interface can be used by every implementation. Other sites\n> would end up storing their own source, which of course would have a\n> different specialized backend.\n>\n> So an alternative to write this would have been:\n>\n>     oi->sourcep->source = (struct odb_source *) source;\n>\n> But by assigning the base we avoid having to cast.\n\nYuck.\n\nI guess it may be OK as the caller or whategver the caller calls\nlater may have to downcast this pointer the usual way to access what\nwe return here anyway.  As a pointer to a struct object, when\nsuitably converted, points to its first member, this upcast should\nalways be safe.  And taking the address of the .base member is far\nmore explicit.\n\nWhile it hides the fact that there is such an upcast involved, which\nmight be confusing to readers not familiar with this corner of the\ncodebase, I am fine with the way it was written.\n\nThanks.\n\n\n\n\n\n\n"},{"id":"546988","messageId":"20260702-b4-pks-odb-drop-whence-v2-0-b0af7468ad95@pks.im","threadId":"65865","inReplyTo":"20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im","subject":"[PATCH v2 0/6] odb: refactor source-specific information in object info","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-07-02T12:01:58Z","receivedAt":"2026-07-02T12:02:07Z","isPatch":true,"body":"Hi,\n\nthis patch series refactors `struct object_info` to not contain the\n`whence` field anymore.\n\nThis field only gave the caller information about the type of source\nthis was read from, but it didn't allow them to figure out which source\nspecifically yielded the object. So instead, we replace this information\nwith a new `struct odb_source_info` field that both contains info about\nthe source, and any backend-specific data.\n\nWith this in place we can re-query the same backend for any given\nobject. More importantly though, we can eventually also use the backend-\nspecific data to also uniquely identify any given object, e.g. by\nrecording the packfile and offset, so that we can even yield the same\nobject in case one source contains the object multiple times.\n\nFurthermore, with this change all information in `struct object_info` is\nnow following the same request-response-field style.\n\nThe series is built on top of 26d8d94e94 (A few more topics before -rc2,\n2026-06-21) with ps/odb-source-packed at 1bba3c035d (odb/source-packed:\ndrop pointer to \"files\" parent source, 2026-06-17) merged into it.\n\nChanges in v2:\n  - Rename `struct object_info_source` to `odb_source_info` and the\n    `sourcep` pointer to `source_infop`. This follows a suggestion made\n    by Justin, as the current naming is too easy to confuse with the\n    actual source.\n  - Link to v1: https://patch.msgid.link/20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (6):\n      packfile: thread odb_source_packed through packed_object_info()\n      odb: make backend-specific fields optional\n      odb: add `source` field to struct object_info_source\n      treewide: convert users of `whence` to the new source field\n      odb: drop `whence` field from object info\n      odb: document object info fields\n\n builtin/cat-file.c     | 12 +++++---\n builtin/index-pack.c   |  9 ++++--\n builtin/pack-objects.c | 19 ++++++++----\n commit-graph.c         |  2 +-\n odb.c                  |  4 +--\n odb.h                  | 80 +++++++++++++++++++++++++++++++++++---------------\n odb/source-inmemory.c  |  3 +-\n odb/source-loose.c     |  4 +--\n odb/source-packed.c    |  4 +--\n pack-bitmap.c          |  2 +-\n packfile.c             | 45 ++++++++++++++++------------\n packfile.h             |  6 ++--\n reachable.c            |  7 +++--\n t/helper/test-bitmap.c |  2 +-\n 14 files changed, 130 insertions(+), 69 deletions(-)\n\nRange-diff versus v1:\n\n1:  52cf49cd37 = 1:  19c247567e packfile: thread odb_source_packed through packed_object_info()\n2:  325878ed04 = 2:  084c3592b4 odb: make backend-specific fields optional\n3:  cb7f9154b6 < -:  ---------- odb: add `source` field to struct object_info_source\n4:  63d2eb7b7c < -:  ---------- treewide: convert users of `whence` to the new source field\n-:  ---------- > 3:  dc878542bf odb: add `source` field to struct object_info_source\n-:  ---------- > 4:  d2e2b90842 treewide: convert users of `whence` to the new source field\n5:  332899f012 ! 5:  7e09bc6aa9 odb: drop `whence` field from object info\n    @@ odb.c: static int oid_object_info_convert(struct repository *r,\n      \t\t}\n      \t}\n     -\tinput_oi->whence = new_oi.whence;\n    - \tif (input_oi->sourcep)\n    - \t\t*input_oi->sourcep = *new_oi.sourcep;\n    + \tif (input_oi->source_infop)\n    + \t\t*input_oi->source_infop = *new_oi.source_infop;\n      \treturn ret;\n     \n      ## odb.h ##\n     @@ odb.h: struct object_info {\n      \t * or multiple times in the same source.\n      \t */\n    - \tstruct object_info_source *sourcep;\n    + \tstruct odb_source_info *source_infop;\n     -\n     -\t/* Response */\n     -\tenum {\n    @@ odb.h: struct object_info {\n      ## odb/source-inmemory.c ##\n     @@ odb/source-inmemory.c: static void populate_object_info(struct odb_source_inmemory *source,\n      \t\t*oi->mtimep = 0;\n    - \tif (oi->sourcep)\n    - \t\toi->sourcep->source = &source->base;\n    + \tif (oi->source_infop)\n    + \t\toi->source_infop->source = &source->base;\n     -\n     -\toi->whence = OI_CACHED;\n      }\n    @@ odb/source-inmemory.c: static void populate_object_info(struct odb_source_inmemo\n      ## odb/source-loose.c ##\n     @@ odb/source-loose.c: static int read_object_info_from_path(struct odb_source_loose *loose,\n      \t\t\toidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo);\n    - \t\tif (oi->sourcep && !ret)\n    - \t\t\toi->sourcep->source = &loose->base;\n    + \t\tif (oi->source_infop && !ret)\n    + \t\t\toi->source_infop->source = &loose->base;\n     -\t\tif (!ret)\n     -\t\t\toi->whence = OI_LOOSE;\n      \t}\n    @@ packfile.c: int packed_object_info_with_index_pos(struct odb_source_packed *sour\n      \n     -\toi->whence = OI_PACKED;\n     -\n    - \tif (oi->sourcep) {\n    + \tif (oi->source_infop) {\n      \t\tif (!source)\n      \t\t\tBUG(\"cannot request source without an owning source\");\n6:  1e79d64921 ! 6:  1c25e56d0b odb: document object info fields\n    @@ Commit message\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## odb.h ##\n    -@@ odb.h: struct object_info_source {\n    +@@ odb.h: struct odb_source_info {\n      \t} u;\n      };\n      \n\n---\nbase-commit: 969dbd51a70f9105ee9965adec5c5a02e75ab5b3\nchange-id: 20260612-b4-pks-odb-drop-whence-1b0af9ab16f4\n\n"},{"id":"546989","messageId":"20260702-b4-pks-odb-drop-whence-v2-1-b0af7468ad95@pks.im","threadId":"65865","inReplyTo":"20260702-b4-pks-odb-drop-whence-v2-0-b0af7468ad95@pks.im","subject":"[PATCH v2 1/6] packfile: thread odb_source_packed through packed_object_info()","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-07-02T12:01:59Z","receivedAt":"2026-07-02T12:02:08Z","isPatch":true,"body":"Add an optional `struct odb_source_packed *source` parameter to\n`packed_object_info()` and `packed_object_info_with_index_pos()`. This\nparameter is unused at this point in time, but it will be used in a\nfollow-up commit so that we can record the source of a specific object.\n\nNote that callers in \"odb/source-packed.c\" pass the already-available\nsource, but all other callers pass `NULL` instead. This is fine though,\nas we only care about populating this info when called via the packed\nstore.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     | 2 +-\n builtin/pack-objects.c | 4 ++--\n commit-graph.c         | 2 +-\n odb/source-packed.c    | 4 ++--\n pack-bitmap.c          | 2 +-\n packfile.c             | 8 +++++---\n packfile.h             | 6 ++++--\n t/helper/test-bitmap.c | 2 +-\n 8 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 0f3dbd9850..8726485f1f 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -497,7 +497,7 @@ static void batch_object_write(const char *obj_name,\n \t\t\tdata->info.sizep = &data->size;\n \n \t\tif (pack)\n-\t\t\tret = packed_object_info(pack, offset, &data->info);\n+\t\t\tret = packed_object_info(NULL, pack, offset, &data->info);\n \t\telse\n \t\t\tret = odb_read_object_info_extended(the_repository->objects,\n \t\t\t\t\t\t\t    &data->oid, &data->info,\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex bc5f9ef321..620d9ce085 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -2463,7 +2463,7 @@ static void drop_reused_delta(struct object_entry *entry)\n \n \toi.sizep = &size;\n \toi.typep = &type;\n-\tif (packed_object_info(IN_PACK(entry), entry->in_pack_offset, &oi) < 0) {\n+\tif (packed_object_info(NULL, IN_PACK(entry), entry->in_pack_offset, &oi) < 0) {\n \t\t/*\n \t\t * We failed to get the info from this pack for some reason;\n \t\t * fall back to odb_read_object_info, which may find another copy.\n@@ -3804,7 +3804,7 @@ static int add_object_entry_from_pack(const struct object_id *oid,\n \tofs = nth_packed_object_offset(p, pos);\n \n \toi.typep = &type;\n-\tif (packed_object_info(p, ofs, &oi) < 0) {\n+\tif (packed_object_info(NULL, p, ofs, &oi) < 0) {\n \t\tdie(_(\"could not get type of object %s in pack %s\"),\n \t\t    oid_to_hex(oid), p->pack_name);\n \t} else if (type == OBJ_COMMIT) {\ndiff --git a/commit-graph.c b/commit-graph.c\nindex c6d9c5c740..9dc8bd5eee 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1538,7 +1538,7 @@ static int add_packed_commits(const struct object_id *oid,\n \tstruct object_info oi = OBJECT_INFO_INIT;\n \n \toi.typep = &type;\n-\tif (packed_object_info(pack, offset, &oi) < 0)\n+\tif (packed_object_info(NULL, pack, offset, &oi) < 0)\n \t\tdie(_(\"unable to get type of object %s\"), oid_to_hex(oid));\n \n \treturn add_packed_commits_oi(oid, &oi, data);\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 42c28fba0e..43fb53b72d 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -59,7 +59,7 @@ static int odb_source_packed_read_object_info(struct odb_source *source,\n \tif (!oi)\n \t\treturn 0;\n \n-\tret = packed_object_info(e.p, e.offset, oi);\n+\tret = packed_object_info(packed, e.p, e.offset, oi);\n \tif (ret < 0) {\n \t\tmark_bad_packed_object(e.p, oid);\n \t\treturn -1;\n@@ -99,7 +99,7 @@ static int odb_source_packed_for_each_object_wrapper(const struct object_id *oid\n \t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n \t\tstruct object_info oi = *data->request;\n \n-\t\tif (packed_object_info_with_index_pos(pack, offset,\n+\t\tif (packed_object_info_with_index_pos(data->store, pack, offset,\n \t\t\t\t\t\t      &index_pos, &oi) < 0) {\n \t\t\tmark_bad_packed_object(pack, oid);\n \t\t\treturn -1;\ndiff --git a/pack-bitmap.c b/pack-bitmap.c\nindex 83eb47a28b..35774b6f0c 100644\n--- a/pack-bitmap.c\n+++ b/pack-bitmap.c\n@@ -1877,7 +1877,7 @@ static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git,\n \t\t\tofs = pack_pos_to_offset(pack, pos);\n \t\t}\n \n-\t\tif (packed_object_info(pack, ofs, &oi) < 0) {\n+\t\tif (packed_object_info(NULL, pack, ofs, &oi) < 0) {\n \t\t\tstruct object_id oid;\n \t\t\tnth_bitmap_object_oid(bitmap_git, &oid,\n \t\t\t\t\t      pack_pos_to_index(pack, pos));\ndiff --git a/packfile.c b/packfile.c\nindex 1d1b23b6cc..2b741d7a76 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1324,7 +1324,8 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,\n \thashmap_add(&delta_base_cache, &ent->ent);\n }\n \n-int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n+int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n+\t\t\t\t      struct packed_git *p, off_t obj_offset,\n \t\t\t\t      uint32_t *maybe_index_pos, struct object_info *oi)\n {\n \tstruct pack_window *w_curs = NULL;\n@@ -1446,10 +1447,11 @@ int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n \treturn ret;\n }\n \n-int packed_object_info(struct packed_git *p, off_t obj_offset,\n+int packed_object_info(struct odb_source_packed *source,\n+\t\t       struct packed_git *p, off_t obj_offset,\n \t\t       struct object_info *oi)\n {\n-\treturn packed_object_info_with_index_pos(p, obj_offset, NULL, oi);\n+\treturn packed_object_info_with_index_pos(source, p, obj_offset, NULL, oi);\n }\n \n static void *unpack_compressed_entry(struct packed_git *p,\ndiff --git a/packfile.h b/packfile.h\nindex 2329a69701..e1f77152b5 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -320,9 +320,11 @@ extern int do_check_packed_object_crc;\n  * Look up the object info for a specific offset in the packfile.\n  * Returns zero on success, a negative error code otherwise.\n  */\n-int packed_object_info(struct packed_git *pack,\n+int packed_object_info(struct odb_source_packed *source,\n+\t\t       struct packed_git *pack,\n \t\t       off_t offset, struct object_info *);\n-int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n+int packed_object_info_with_index_pos(struct odb_source_packed *source,\n+\t\t\t\t      struct packed_git *p, off_t obj_offset,\n \t\t\t\t      uint32_t *maybe_index_pos, struct object_info *oi);\n \n void mark_bad_packed_object(struct packed_git *, const struct object_id *);\ndiff --git a/t/helper/test-bitmap.c b/t/helper/test-bitmap.c\nindex b130832b81..8547ef67e2 100644\n--- a/t/helper/test-bitmap.c\n+++ b/t/helper/test-bitmap.c\n@@ -52,7 +52,7 @@ static int add_packed_object(const struct object_id *oid,\n \n \tentry = packlist_alloc(packed, oid);\n \tentry->idx.offset = nth_packed_object_offset(pack, pos);\n-\tif (packed_object_info(pack, entry->idx.offset, &oi) < 0)\n+\tif (packed_object_info(NULL, pack, entry->idx.offset, &oi) < 0)\n \t\tdie(\"could not get type of object %s\",\n \t\t    oid_to_hex(oid));\n \toe_set_type(entry, type);\n\n-- \n2.55.0.795.g602f6c329a.dirty\n\n"},{"id":"546990","messageId":"20260702-b4-pks-odb-drop-whence-v2-2-b0af7468ad95@pks.im","threadId":"65865","inReplyTo":"20260702-b4-pks-odb-drop-whence-v2-0-b0af7468ad95@pks.im","subject":"[PATCH v2 2/6] odb: make backend-specific fields optional","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-07-02T12:02:00Z","receivedAt":"2026-07-02T12:02:10Z","isPatch":true,"body":"The `struct object_info` carries two pieces of information\nabout how an object was looked up:\n\n  - The `whence` enum identifying the backend.\n\n  - The backend-tagged union `u` exposing backend-specific details\n    (currently only the packed-source case, which records the owning\n    pack, offset and packed object type).\n\nThe union is populated unconditionally, even though most callers don't\ncare about provenance at all.\n\nSplit the backend-specific union out into a new public type, `struct\nobject_info_source`, and make the object info structure carry it via\njust another opt-in request pointer. As with all the other requestable\ninformation, callers that need source info allocate a `struct\nobject_info_source` on the stack and point `sourcep` at it; callers that\ndon't care about it simply leave the field as a `NULL` pointer. Adapt\ncallers accordingly.\n\nNote that the `whence` enum is strictly-speaking also backend-specific\ninformation, so it would be another good candidate to be moved into the\n`struct object_info_source`. For now though it is left alone, as it will\nbe replaced by a `struct odb_source` pointer in a subsequent commit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     |  8 +++++--\n builtin/index-pack.c   |  8 +++++--\n builtin/pack-objects.c | 15 +++++++++----\n odb.c                  |  3 ++-\n odb.h                  | 60 +++++++++++++++++++++++++++++++++-----------------\n packfile.c             | 33 ++++++++++++++-------------\n reachable.c            |  5 ++++-\n 7 files changed, 87 insertions(+), 45 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 8726485f1f..adc626ce30 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -835,7 +835,8 @@ static int batch_one_object_oi(const struct object_id *oid,\n {\n \tstruct for_each_object_payload *payload = _payload;\n \tif (oi && oi->whence == OI_PACKED)\n-\t\treturn payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,\n+\t\treturn payload->callback(oid, oi->sourcep->u.packed.pack,\n+\t\t\t\t\t oi->sourcep->u.packed.offset,\n \t\t\t\t\t payload->payload);\n \treturn payload->callback(oid, NULL, 0, payload->payload);\n }\n@@ -906,7 +907,10 @@ static void batch_each_object(struct batch_options *opt,\n \t\t\t\t\t\t&payload, flags);\n \t\t}\n \t} else {\n-\t\tstruct object_info oi = { 0 };\n+\t\tstruct object_info_source oi_source;\n+\t\tstruct object_info oi = {\n+\t\t\t.sourcep = &oi_source,\n+\t\t};\n \n \t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n \t\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex f396658468..77af26db8f 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1825,11 +1825,15 @@ static void repack_local_links(void)\n \n \toidset_iter_init(&outgoing_links, &iter);\n \twhile ((oid = oidset_iter_next(&iter))) {\n-\t\tstruct object_info info = OBJECT_INFO_INIT;\n+\t\tstruct object_info_source info_source;\n+\t\tstruct object_info info = {\n+\t\t\t.sourcep = &info_source,\n+\t\t};\n+\n \t\tif (odb_read_object_info_extended(the_repository->objects, oid, &info, 0))\n \t\t\t/* Missing; assume it is a promisor object */\n \t\t\tcontinue;\n-\t\tif (info.whence == OI_PACKED && info.u.packed.pack->pack_promisor)\n+\t\tif (info.whence == OI_PACKED && info_source.u.packed.pack->pack_promisor)\n \t\t\tcontinue;\n \n \t\tif (!cmd.args.nr) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 620d9ce085..9deb37e9e8 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4491,8 +4491,9 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,\n \t\t\t\t       void *data UNUSED)\n {\n \tif (cruft) {\n-\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack,\n-\t\t\t\t       oi->u.packed.offset, NULL, *oi->mtimep);\n+\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->sourcep->u.packed.pack,\n+\t\t\t\t       oi->sourcep->u.packed.offset, NULL,\n+\t\t\t\t       *oi->mtimep);\n \t} else {\n \t\tadd_object_entry(oid, OBJ_NONE, \"\", 0);\n \t}\n@@ -4509,8 +4510,10 @@ static void add_objects_in_unpacked_packs(void)\n \t\t\t ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n \t\t\t ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS,\n \t};\n+\tstruct object_info_source oi_source;\n \tstruct object_info oi = {\n \t\t.mtimep = &mtime,\n+\t\t.sourcep = &oi_source,\n \t};\n \n \todb_prepare_alternates(to_pack.repo->objects);\n@@ -5000,10 +5003,14 @@ static int option_parse_cruft_expiration(const struct option *opt UNUSED,\n \n static int is_not_in_promisor_pack_obj(struct object *obj, void *data UNUSED)\n {\n-\tstruct object_info info = OBJECT_INFO_INIT;\n+\tstruct object_info_source info_source;\n+\tstruct object_info info = {\n+\t\t.sourcep = &info_source,\n+\t};\n+\n \tif (odb_read_object_info_extended(the_repository->objects, &obj->oid, &info, 0))\n \t\tBUG(\"should_include_obj should only be called on existing objects\");\n-\treturn info.whence != OI_PACKED || !info.u.packed.pack->pack_promisor;\n+\treturn info.whence != OI_PACKED || !info_source.u.packed.pack->pack_promisor;\n }\n \n static int is_not_in_promisor_pack(struct commit *commit, void *data) {\ndiff --git a/odb.c b/odb.c\nindex 7d555be09f..99f4e7551c 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -692,7 +692,8 @@ static int oid_object_info_convert(struct repository *r,\n \t\t}\n \t}\n \tinput_oi->whence = new_oi.whence;\n-\tinput_oi->u = new_oi.u;\n+\tif (input_oi->sourcep)\n+\t\t*input_oi->sourcep = *new_oi.sourcep;\n \treturn ret;\n }\n \ndiff --git a/odb.h b/odb.h\nindex 3834a0dcbf..770900289a 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -248,6 +248,38 @@ int odb_pretend_object(struct object_database *odb,\n \t\t       void *buf, size_t len, enum object_type type,\n \t\t       struct object_id *oid);\n \n+/*\n+ * Object information that can be used to uniquely identify an object and learn\n+ * more about how exactly it is stored.\n+ */\n+struct object_info_source {\n+\t/*\n+\t * Backend-specific information about the specific object. This can be\n+\t * used for example to uniquely identify a given object in case it\n+\t * exists multiple times.\n+\t */\n+\tunion {\n+\t\t/*\n+\t\t * struct {\n+\t\t * \t... Nothing to expose in this case\n+\t\t * } cached;\n+\t\t * struct {\n+\t\t * \t... Nothing to expose in this case\n+\t\t * } loose;\n+\t\t */\n+\t\tstruct {\n+\t\t\tstruct packed_git *pack;\n+\t\t\toff_t offset;\n+\t\t\tenum packed_object_type {\n+\t\t\t\tPACKED_OBJECT_TYPE_UNKNOWN,\n+\t\t\t\tPACKED_OBJECT_TYPE_FULL,\n+\t\t\t\tPACKED_OBJECT_TYPE_OFS_DELTA,\n+\t\t\t\tPACKED_OBJECT_TYPE_REF_DELTA,\n+\t\t\t} type;\n+\t\t} packed;\n+\t} u;\n+};\n+\n struct object_info {\n \t/* Request */\n \tenum object_type *typep;\n@@ -269,32 +301,20 @@ struct object_info {\n \t */\n \ttime_t *mtimep;\n \n+\t/*\n+\t * Backend-specific information that tells the caller where exactly an\n+\t * object was looked up from. This information should help disambiguate\n+\t * object lookups in case the same object exists in multiple sources,\n+\t * or multiple times in the same source.\n+\t */\n+\tstruct object_info_source *sourcep;\n+\n \t/* Response */\n \tenum {\n \t\tOI_CACHED,\n \t\tOI_LOOSE,\n \t\tOI_PACKED,\n \t} whence;\n-\tunion {\n-\t\t/*\n-\t\t * struct {\n-\t\t * \t... Nothing to expose in this case\n-\t\t * } cached;\n-\t\t * struct {\n-\t\t * \t... Nothing to expose in this case\n-\t\t * } loose;\n-\t\t */\n-\t\tstruct {\n-\t\t\tstruct packed_git *pack;\n-\t\t\toff_t offset;\n-\t\t\tenum packed_object_type {\n-\t\t\t\tPACKED_OBJECT_TYPE_UNKNOWN,\n-\t\t\t\tPACKED_OBJECT_TYPE_FULL,\n-\t\t\t\tPACKED_OBJECT_TYPE_OFS_DELTA,\n-\t\t\t\tPACKED_OBJECT_TYPE_REF_DELTA,\n-\t\t\t} type;\n-\t\t} packed;\n-\t} u;\n };\n \n /*\ndiff --git a/packfile.c b/packfile.c\nindex 2b741d7a76..688c410b35 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1422,22 +1422,25 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n \t}\n \n \toi->whence = OI_PACKED;\n-\toi->u.packed.offset = obj_offset;\n-\toi->u.packed.pack = p;\n \n-\tswitch (type) {\n-\tcase OBJ_NONE:\n-\t\toi->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;\n-\t\tbreak;\n-\tcase OBJ_REF_DELTA:\n-\t\toi->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;\n-\t\tbreak;\n-\tcase OBJ_OFS_DELTA:\n-\t\toi->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;\n-\t\tbreak;\n-\tdefault:\n-\t\toi->u.packed.type = PACKED_OBJECT_TYPE_FULL;\n-\t\tbreak;\n+\tif (oi->sourcep) {\n+\t\toi->sourcep->u.packed.offset = obj_offset;\n+\t\toi->sourcep->u.packed.pack = p;\n+\n+\t\tswitch (type) {\n+\t\tcase OBJ_NONE:\n+\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;\n+\t\t\tbreak;\n+\t\tcase OBJ_REF_DELTA:\n+\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;\n+\t\t\tbreak;\n+\t\tcase OBJ_OFS_DELTA:\n+\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_FULL;\n+\t\t\tbreak;\n+\t\t}\n \t}\n \n \tret = 0;\ndiff --git a/reachable.c b/reachable.c\nindex 101cfc2727..2fc5b82d62 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -235,7 +235,8 @@ static int add_recent_object(const struct object_id *oid,\n \tadd_pending_object(data->revs, obj, \"\");\n \tif (data->cb) {\n \t\tif (oi->whence == OI_PACKED)\n-\t\t\tdata->cb(obj, oi->u.packed.pack, oi->u.packed.offset, *oi->mtimep);\n+\t\t\tdata->cb(obj, oi->sourcep->u.packed.pack,\n+\t\t\t\t oi->sourcep->u.packed.offset, *oi->mtimep);\n \t\telse\n \t\t\tdata->cb(obj, NULL, 0, *oi->mtimep);\n \t}\n@@ -252,9 +253,11 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \tunsigned flags;\n \tenum object_type type;\n \ttime_t mtime;\n+\tstruct object_info_source oi_source;\n \tstruct object_info oi = {\n \t\t.mtimep = &mtime,\n \t\t.typep = &type,\n+\t\t.sourcep = &oi_source,\n \t};\n \tint r;\n \n\n-- \n2.55.0.795.g602f6c329a.dirty\n\n"},{"id":"546991","messageId":"20260702-b4-pks-odb-drop-whence-v2-3-b0af7468ad95@pks.im","threadId":"65865","inReplyTo":"20260702-b4-pks-odb-drop-whence-v2-0-b0af7468ad95@pks.im","subject":"[PATCH v2 3/6] odb: add `source` field to struct object_info_source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-07-02T12:02:01Z","receivedAt":"2026-07-02T12:02:13Z","isPatch":true,"body":"The previous commit introduced `struct object_info_source` as an opt-in\ncontainer for backend-specific information, but for now we only moved\npreexisting data into this structure. Most importantly, the caller has\nno way yet to learn about which source an object was actually looked up\nfrom. Instead, callers have to rely on the `whence` enum to distinguish\nthe object type, but cannot use that enum to tell the object source.\n\nAdd a `struct odb_source *source` field to the structure and populate it\nfrom each backend's lookup path.\n\nThe `whence` enum is still set and used by callers; it will be removed\nin a subsequent commit now that `sourcep->source` can identify the\nbackend on its own.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     |  8 ++++----\n builtin/index-pack.c   |  6 +++---\n builtin/pack-objects.c | 14 +++++++-------\n odb.c                  |  4 ++--\n odb.h                  | 11 +++++++----\n odb/source-inmemory.c  |  3 +++\n odb/source-loose.c     |  2 ++\n packfile.c             | 20 ++++++++++++--------\n reachable.c            |  8 ++++----\n 9 files changed, 44 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex adc626ce30..0aca6acb75 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -835,8 +835,8 @@ static int batch_one_object_oi(const struct object_id *oid,\n {\n \tstruct for_each_object_payload *payload = _payload;\n \tif (oi && oi->whence == OI_PACKED)\n-\t\treturn payload->callback(oid, oi->sourcep->u.packed.pack,\n-\t\t\t\t\t oi->sourcep->u.packed.offset,\n+\t\treturn payload->callback(oid, oi->source_infop->u.packed.pack,\n+\t\t\t\t\t oi->source_infop->u.packed.offset,\n \t\t\t\t\t payload->payload);\n \treturn payload->callback(oid, NULL, 0, payload->payload);\n }\n@@ -907,9 +907,9 @@ static void batch_each_object(struct batch_options *opt,\n \t\t\t\t\t\t&payload, flags);\n \t\t}\n \t} else {\n-\t\tstruct object_info_source oi_source;\n+\t\tstruct odb_source_info source_info;\n \t\tstruct object_info oi = {\n-\t\t\t.sourcep = &oi_source,\n+\t\t\t.source_infop = &source_info,\n \t\t};\n \n \t\tfor (source = the_repository->objects->sources; source; source = source->next) {\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 77af26db8f..fe6e70522d 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1825,15 +1825,15 @@ static void repack_local_links(void)\n \n \toidset_iter_init(&outgoing_links, &iter);\n \twhile ((oid = oidset_iter_next(&iter))) {\n-\t\tstruct object_info_source info_source;\n+\t\tstruct odb_source_info source_info;\n \t\tstruct object_info info = {\n-\t\t\t.sourcep = &info_source,\n+\t\t\t.source_infop = &source_info,\n \t\t};\n \n \t\tif (odb_read_object_info_extended(the_repository->objects, oid, &info, 0))\n \t\t\t/* Missing; assume it is a promisor object */\n \t\t\tcontinue;\n-\t\tif (info.whence == OI_PACKED && info_source.u.packed.pack->pack_promisor)\n+\t\tif (info.whence == OI_PACKED && source_info.u.packed.pack->pack_promisor)\n \t\t\tcontinue;\n \n \t\tif (!cmd.args.nr) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 9deb37e9e8..b7ef90f67c 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4491,8 +4491,8 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,\n \t\t\t\t       void *data UNUSED)\n {\n \tif (cruft) {\n-\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->sourcep->u.packed.pack,\n-\t\t\t\t       oi->sourcep->u.packed.offset, NULL,\n+\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->source_infop->u.packed.pack,\n+\t\t\t\t       oi->source_infop->u.packed.offset, NULL,\n \t\t\t\t       *oi->mtimep);\n \t} else {\n \t\tadd_object_entry(oid, OBJ_NONE, \"\", 0);\n@@ -4510,10 +4510,10 @@ static void add_objects_in_unpacked_packs(void)\n \t\t\t ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n \t\t\t ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS,\n \t};\n-\tstruct object_info_source oi_source;\n+\tstruct odb_source_info source_info;\n \tstruct object_info oi = {\n \t\t.mtimep = &mtime,\n-\t\t.sourcep = &oi_source,\n+\t\t.source_infop = &source_info,\n \t};\n \n \todb_prepare_alternates(to_pack.repo->objects);\n@@ -5003,14 +5003,14 @@ static int option_parse_cruft_expiration(const struct option *opt UNUSED,\n \n static int is_not_in_promisor_pack_obj(struct object *obj, void *data UNUSED)\n {\n-\tstruct object_info_source info_source;\n+\tstruct odb_source_info source_info;\n \tstruct object_info info = {\n-\t\t.sourcep = &info_source,\n+\t\t.source_infop = &source_info,\n \t};\n \n \tif (odb_read_object_info_extended(the_repository->objects, &obj->oid, &info, 0))\n \t\tBUG(\"should_include_obj should only be called on existing objects\");\n-\treturn info.whence != OI_PACKED || !info_source.u.packed.pack->pack_promisor;\n+\treturn info.whence != OI_PACKED || !source_info.u.packed.pack->pack_promisor;\n }\n \n static int is_not_in_promisor_pack(struct commit *commit, void *data) {\ndiff --git a/odb.c b/odb.c\nindex 99f4e7551c..34c35c47a5 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -692,8 +692,8 @@ static int oid_object_info_convert(struct repository *r,\n \t\t}\n \t}\n \tinput_oi->whence = new_oi.whence;\n-\tif (input_oi->sourcep)\n-\t\t*input_oi->sourcep = *new_oi.sourcep;\n+\tif (input_oi->source_infop)\n+\t\t*input_oi->source_infop = *new_oi.source_infop;\n \treturn ret;\n }\n \ndiff --git a/odb.h b/odb.h\nindex 770900289a..659bf8afe1 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -249,10 +249,13 @@ int odb_pretend_object(struct object_database *odb,\n \t\t       struct object_id *oid);\n \n /*\n- * Object information that can be used to uniquely identify an object and learn\n- * more about how exactly it is stored.\n+ * Object database source information that can be used to uniquely identify an\n+ * object and learn more about how exactly it is stored.\n  */\n-struct object_info_source {\n+struct odb_source_info {\n+\t/* The source that this object has been looked up from. */\n+\tstruct odb_source *source;\n+\n \t/*\n \t * Backend-specific information about the specific object. This can be\n \t * used for example to uniquely identify a given object in case it\n@@ -307,7 +310,7 @@ struct object_info {\n \t * object lookups in case the same object exists in multiple sources,\n \t * or multiple times in the same source.\n \t */\n-\tstruct object_info_source *sourcep;\n+\tstruct odb_source_info *source_infop;\n \n \t/* Response */\n \tenum {\ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex e004566d76..1d173bfa46 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -52,6 +52,9 @@ static void populate_object_info(struct odb_source_inmemory *source,\n \t\t*oi->contentp = xmemdupz(object->buf, object->size);\n \tif (oi->mtimep)\n \t\t*oi->mtimep = 0;\n+\tif (oi->source_infop)\n+\t\toi->source_infop->source = &source->base;\n+\n \toi->whence = OI_CACHED;\n }\n \ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex 66e6bb8d3f..c254957602 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -196,6 +196,8 @@ static int read_object_info_from_path(struct odb_source_loose *loose,\n \t\t\toi->typep = NULL;\n \t\tif (oi->delta_base_oid)\n \t\t\toidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo);\n+\t\tif (oi->source_infop && !ret)\n+\t\t\toi->source_infop->source = &loose->base;\n \t\tif (!ret)\n \t\t\toi->whence = OI_LOOSE;\n \t}\ndiff --git a/packfile.c b/packfile.c\nindex 688c410b35..ce51d1e5a3 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1324,7 +1324,7 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,\n \thashmap_add(&delta_base_cache, &ent->ent);\n }\n \n-int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n+int packed_object_info_with_index_pos(struct odb_source_packed *source,\n \t\t\t\t      struct packed_git *p, off_t obj_offset,\n \t\t\t\t      uint32_t *maybe_index_pos, struct object_info *oi)\n {\n@@ -1423,22 +1423,26 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source UNUSED,\n \n \toi->whence = OI_PACKED;\n \n-\tif (oi->sourcep) {\n-\t\toi->sourcep->u.packed.offset = obj_offset;\n-\t\toi->sourcep->u.packed.pack = p;\n+\tif (oi->source_infop) {\n+\t\tif (!source)\n+\t\t\tBUG(\"cannot request source without an owning source\");\n+\t\toi->source_infop->source = &source->base;\n+\n+\t\toi->source_infop->u.packed.offset = obj_offset;\n+\t\toi->source_infop->u.packed.pack = p;\n \n \t\tswitch (type) {\n \t\tcase OBJ_NONE:\n-\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;\n+\t\t\toi->source_infop->u.packed.type = PACKED_OBJECT_TYPE_UNKNOWN;\n \t\t\tbreak;\n \t\tcase OBJ_REF_DELTA:\n-\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;\n+\t\t\toi->source_infop->u.packed.type = PACKED_OBJECT_TYPE_REF_DELTA;\n \t\t\tbreak;\n \t\tcase OBJ_OFS_DELTA:\n-\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;\n+\t\t\toi->source_infop->u.packed.type = PACKED_OBJECT_TYPE_OFS_DELTA;\n \t\t\tbreak;\n \t\tdefault:\n-\t\t\toi->sourcep->u.packed.type = PACKED_OBJECT_TYPE_FULL;\n+\t\t\toi->source_infop->u.packed.type = PACKED_OBJECT_TYPE_FULL;\n \t\t\tbreak;\n \t\t}\n \t}\ndiff --git a/reachable.c b/reachable.c\nindex 2fc5b82d62..bf76b48fc5 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -235,8 +235,8 @@ static int add_recent_object(const struct object_id *oid,\n \tadd_pending_object(data->revs, obj, \"\");\n \tif (data->cb) {\n \t\tif (oi->whence == OI_PACKED)\n-\t\t\tdata->cb(obj, oi->sourcep->u.packed.pack,\n-\t\t\t\t oi->sourcep->u.packed.offset, *oi->mtimep);\n+\t\t\tdata->cb(obj, oi->source_infop->u.packed.pack,\n+\t\t\t\t oi->source_infop->u.packed.offset, *oi->mtimep);\n \t\telse\n \t\t\tdata->cb(obj, NULL, 0, *oi->mtimep);\n \t}\n@@ -253,11 +253,11 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \tunsigned flags;\n \tenum object_type type;\n \ttime_t mtime;\n-\tstruct object_info_source oi_source;\n+\tstruct odb_source_info source_info;\n \tstruct object_info oi = {\n \t\t.mtimep = &mtime,\n \t\t.typep = &type,\n-\t\t.sourcep = &oi_source,\n+\t\t.source_infop = &source_info,\n \t};\n \tint r;\n \n\n-- \n2.55.0.795.g602f6c329a.dirty\n\n"},{"id":"546992","messageId":"20260702-b4-pks-odb-drop-whence-v2-4-b0af7468ad95@pks.im","threadId":"65865","inReplyTo":"20260702-b4-pks-odb-drop-whence-v2-0-b0af7468ad95@pks.im","subject":"[PATCH v2 4/6] treewide: convert users of `whence` to the new source field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-07-02T12:02:02Z","receivedAt":"2026-07-02T12:02:16Z","isPatch":true,"body":"The `whence` field has become redundant now that callers can learn about\nthe exact source an object has been looked up from via the `struct\nobject_info_source::source` field.\n\nAdapt callers to use the new field. Note that all callsites already set\nup the `info.sourcep` request pointer, so the conversion is rather\nstraight-forward.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     | 2 +-\n builtin/index-pack.c   | 3 ++-\n builtin/pack-objects.c | 2 +-\n reachable.c            | 2 +-\n 4 files changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 0aca6acb75..758f8fc736 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -834,7 +834,7 @@ static int batch_one_object_oi(const struct object_id *oid,\n \t\t\t       void *_payload)\n {\n \tstruct for_each_object_payload *payload = _payload;\n-\tif (oi && oi->whence == OI_PACKED)\n+\tif (oi && oi->source_infop->source->type == ODB_SOURCE_PACKED)\n \t\treturn payload->callback(oid, oi->source_infop->u.packed.pack,\n \t\t\t\t\t oi->source_infop->u.packed.offset,\n \t\t\t\t\t payload->payload);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex fe6e70522d..7af1aea6f9 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1833,7 +1833,8 @@ static void repack_local_links(void)\n \t\tif (odb_read_object_info_extended(the_repository->objects, oid, &info, 0))\n \t\t\t/* Missing; assume it is a promisor object */\n \t\t\tcontinue;\n-\t\tif (info.whence == OI_PACKED && source_info.u.packed.pack->pack_promisor)\n+\t\tif (source_info.source->type == ODB_SOURCE_PACKED &&\n+\t\t    source_info.u.packed.pack->pack_promisor)\n \t\t\tcontinue;\n \n \t\tif (!cmd.args.nr) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex b7ef90f67c..4fdb6dbf6f 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -5010,7 +5010,7 @@ static int is_not_in_promisor_pack_obj(struct object *obj, void *data UNUSED)\n \n \tif (odb_read_object_info_extended(the_repository->objects, &obj->oid, &info, 0))\n \t\tBUG(\"should_include_obj should only be called on existing objects\");\n-\treturn info.whence != OI_PACKED || !source_info.u.packed.pack->pack_promisor;\n+\treturn source_info.source->type != ODB_SOURCE_PACKED || !source_info.u.packed.pack->pack_promisor;\n }\n \n static int is_not_in_promisor_pack(struct commit *commit, void *data) {\ndiff --git a/reachable.c b/reachable.c\nindex bf76b48fc5..caadacc02a 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -234,7 +234,7 @@ static int add_recent_object(const struct object_id *oid,\n \n \tadd_pending_object(data->revs, obj, \"\");\n \tif (data->cb) {\n-\t\tif (oi->whence == OI_PACKED)\n+\t\tif (oi->source_infop->source->type == ODB_SOURCE_PACKED)\n \t\t\tdata->cb(obj, oi->source_infop->u.packed.pack,\n \t\t\t\t oi->source_infop->u.packed.offset, *oi->mtimep);\n \t\telse\n\n-- \n2.55.0.795.g602f6c329a.dirty\n\n"},{"id":"546993","messageId":"20260702-b4-pks-odb-drop-whence-v2-5-b0af7468ad95@pks.im","threadId":"65865","inReplyTo":"20260702-b4-pks-odb-drop-whence-v2-0-b0af7468ad95@pks.im","subject":"[PATCH v2 5/6] odb: drop `whence` field from object info","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-07-02T12:02:03Z","receivedAt":"2026-07-02T12:02:18Z","isPatch":true,"body":"In the preceding commits we have migrated all callers to derive their\ninformation of how a specific object is stored to use the new object\ninfo source instead, and hence the field is now unused. Drop it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c                 | 1 -\n odb.h                 | 7 -------\n odb/source-inmemory.c | 2 --\n odb/source-loose.c    | 2 --\n packfile.c            | 2 --\n 5 files changed, 14 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 34c35c47a5..175a1ee42c 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -691,7 +691,6 @@ static int oid_object_info_convert(struct repository *r,\n \t\t\treturn -1;\n \t\t}\n \t}\n-\tinput_oi->whence = new_oi.whence;\n \tif (input_oi->source_infop)\n \t\t*input_oi->source_infop = *new_oi.source_infop;\n \treturn ret;\ndiff --git a/odb.h b/odb.h\nindex 659bf8afe1..c251788d50 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -311,13 +311,6 @@ struct object_info {\n \t * or multiple times in the same source.\n \t */\n \tstruct odb_source_info *source_infop;\n-\n-\t/* Response */\n-\tenum {\n-\t\tOI_CACHED,\n-\t\tOI_LOOSE,\n-\t\tOI_PACKED,\n-\t} whence;\n };\n \n /*\ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex 1d173bfa46..460aec821c 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -54,8 +54,6 @@ static void populate_object_info(struct odb_source_inmemory *source,\n \t\t*oi->mtimep = 0;\n \tif (oi->source_infop)\n \t\toi->source_infop->source = &source->base;\n-\n-\toi->whence = OI_CACHED;\n }\n \n static int odb_source_inmemory_read_object_info(struct odb_source *source,\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex c254957602..54df2e57d3 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -198,8 +198,6 @@ static int read_object_info_from_path(struct odb_source_loose *loose,\n \t\t\toidclr(oi->delta_base_oid, loose->base.odb->repo->hash_algo);\n \t\tif (oi->source_infop && !ret)\n \t\t\toi->source_infop->source = &loose->base;\n-\t\tif (!ret)\n-\t\t\toi->whence = OI_LOOSE;\n \t}\n \n \treturn ret;\ndiff --git a/packfile.c b/packfile.c\nindex ce51d1e5a3..8fa6309a09 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1421,8 +1421,6 @@ int packed_object_info_with_index_pos(struct odb_source_packed *source,\n \t\t\toidclr(oi->delta_base_oid, p->repo->hash_algo);\n \t}\n \n-\toi->whence = OI_PACKED;\n-\n \tif (oi->source_infop) {\n \t\tif (!source)\n \t\t\tBUG(\"cannot request source without an owning source\");\n\n-- \n2.55.0.795.g602f6c329a.dirty\n\n"},{"id":"546994","messageId":"20260702-b4-pks-odb-drop-whence-v2-6-b0af7468ad95@pks.im","threadId":"65865","inReplyTo":"20260702-b4-pks-odb-drop-whence-v2-0-b0af7468ad95@pks.im","subject":"[PATCH v2 6/6] odb: document object info fields","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-07-02T12:02:04Z","receivedAt":"2026-07-02T12:02:20Z","isPatch":true,"body":"Some of the fields in `struct object_info` are undocumented. Add these\nmissing comments.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.h | 18 +++++++++++++++++-\n 1 file changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/odb.h b/odb.h\nindex c251788d50..a1e222f605 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -283,12 +283,28 @@ struct odb_source_info {\n \t} u;\n };\n \n+/*\n+ * The object info contains the query and response that is to be used for\n+ * functions that end up reading object information. Callers are expected to\n+ * populate pointers whose information they want to request.\n+ */\n struct object_info {\n-\t/* Request */\n+\t/* The object type. */\n \tenum object_type *typep;\n+\n+\t/* The inflated object size in bytes. */\n \tsize_t *sizep;\n+\n+\t/* The object size as stored on disk. */\n \toff_t *disk_sizep;\n+\n+\t/*\n+\t * The base the object is deltified against, in case it is stored as a\n+\t * delta.\n+\t */\n \tstruct object_id *delta_base_oid;\n+\n+\t/* The object contents. Ownership of memory goes over to the caller. */\n \tvoid **contentp;\n \n \t/*\n\n-- \n2.55.0.795.g602f6c329a.dirty\n\n"},{"id":"547143","messageId":"xmqqy0fqdy3i.fsf@gitster.g","threadId":"65865","inReplyTo":"20260702-b4-pks-odb-drop-whence-v2-0-b0af7468ad95@pks.im","subject":"Re: [PATCH v2 0/6] odb: refactor source-specific information in object info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-05T01:28:01Z","receivedAt":"2026-07-05T01:28:05Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Changes in v2:\n>   - Rename `struct object_info_source` to `odb_source_info` and the\n>     `sourcep` pointer to `source_infop`. This follows a suggestion made\n>     by Justin, as the current naming is too easy to confuse with the\n>     actual source.\n>   - Link to v1: https://patch.msgid.link/20260624-b4-pks-odb-drop-whence-v1-0-8d1877b790ac@pks.im\n\nThanks.  Queued.\n"}]}