{"thread":{"id":"65752","subject":"[PATCH 00/16] odb: make packed object source a proper `struct odb_source`","startedAt":"2026-06-04T11:25:42Z","lastAt":"2026-06-19T16:40:53Z","messageCount":85,"participants":["Patrick Steinhardt","Karthik Nayak","Justin Tobler","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":16},"messages":[{"id":"544725","messageId":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":null,"subject":"[PATCH 00/16] odb: make packed object source a proper `struct odb_source`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:27Z","receivedAt":"2026-06-04T11:25:42Z","isPatch":true,"body":"Hi,\n\nthis patch series converts the \"packed\" source into a proper `struct\nodb_source`. It's thus the equivalent to [1], which did the same thing\nfor the \"loose\" source.\n\nThis series here is unfortunately a bit bigger, mostly because I'm also\nrenaming `struct packfile_store` to `struct odb_source_packed`. Back\nwhen I introduced the packfile store I didn't yet have the full vision\nof how the final layout will look like, so I didn't have the foresight\nyet to call it `struct odb_source_packed`. But now that the layout has\nmaterialized I think it's sensible to adjust its naming to match all the\nother sources that we have.\n\nAlso: I don't have anything else in the pipeline anymore that moves\naround large pieces of our code in the vicinity of the object database.\nSo after this series got merged, subsequent changes should be of a more\nincremental nature.\n\nThis series is built on top of 9ac3f193c0 (The 11th batch, 2026-06-02)\nwith ps/odb-source-loose at ef4778bcba (odb/source-loose: drop pointer\nto the \"files\" source, 2026-06-01) merged into it.\n\nNote that there's a trivial conflict with 8570d9ba31 (Merge branch\n'wy/docs-typofixes' into seen, 2026-06-04) that can be solved like this:\n\ndiff --cc packfile.h\nindex ed49ab7f21,5729a37018..0000000000\n--- a/packfile.h\n+++ b/packfile.h\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex ef5a10b224..1d312f7dea 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -61,7 +61,7 @@ struct odb_source_packed {\n         * that packs that contain a lot of accessed objects will be located\n         * towards the front.\n         *\n-        * This is usually desireable, but there are exceptions. One exception\n+        * This is usually desirable, but there are exceptions. One exception\n         * is when the looking up multiple objects in a loop for each packfile.\n         * In that case, we may easily end up with an infinite loop as the\n         * packfiles get reordered to the front repeatedly.\n\nThanks!\n\nPatrick\n\n[1]: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>\n\n---\nPatrick Steinhardt (16):\n      packfile: rename `struct packfile_store` to `odb_source_packed`\n      packfile: move packed source into \"odb/\" subsystem\n      odb/source-packed: store pointer to \"files\" instead of generic source\n      odb/source-packed: start converting to a proper `struct odb_source`\n      odb/source-packed: wire up `close()` callback\n      odb/source-packed: wire up `reprepare()` callback\n      packfile: use higher-level interface to implement `has_object_pack()`\n      odb/source-packed: wire up `read_object_info()` callback\n      odb/source-packed: wire up `read_object_stream()` callback\n      odb/source-packed: wire up `for_each_object()` callback\n      odb/source-packed: wire up `count_objects()` callback\n      odb/source-packed: wire up `find_abbrev_len()` callback\n      odb/source-packed: wire up `freshen_object()` callback\n      odb/source-packed: stub out remaining functions\n      midx: refactor interfaces to work on \"packed\" source\n      odb/source-packed: drop pointer to \"files\" parent source\n\n Makefile                   |   1 +\n builtin/cat-file.c         |   4 +-\n builtin/grep.c             |   2 +-\n builtin/multi-pack-index.c |  29 +-\n builtin/pack-objects.c     |   7 +-\n builtin/repack.c           |   8 +-\n commit-graph.c             |   4 +-\n meson.build                |   1 +\n midx-write.c               |  34 +-\n midx.c                     | 118 +++----\n midx.h                     |  30 +-\n odb/source-files.c         |  20 +-\n odb/source-files.h         |   4 +-\n odb/source-packed.c        | 764 +++++++++++++++++++++++++++++++++++++++++++++\n odb/source-packed.h        | 102 ++++++\n odb/source.h               |   3 +\n pack-bitmap.c              |   8 +-\n pack-revindex.c            |   6 +-\n packfile.c                 | 701 +----------------------------------------\n packfile.h                 | 166 +---------\n repack-geometry.c          |   3 +-\n repack-midx.c              |   9 +-\n repack.c                   |   6 +-\n t/helper/test-read-midx.c  |   7 +-\n 24 files changed, 1054 insertions(+), 983 deletions(-)\n\n\n---\nbase-commit: 06d49cec508464ced5d42541890ce5d749542a61\nchange-id: 20260602-pks-odb-source-packed-3826c352f059\n\n"},{"id":"544726","messageId":"20260604-pks-odb-source-packed-v1-1-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 01/16] packfile: rename `struct packfile_store` to `odb_source_packed`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:28Z","receivedAt":"2026-06-04T11:25:43Z","isPatch":true,"body":"Not too long ago, we have introduced the packfile store in b7983adb51\n(packfile: introduce a new `struct packfile_store`, 2025-09-23). This\nstruct is responsible for managing all of our access to packfiles and is\nused as one of the two sources of objects for the \"files\" source.\n\nBack when I introduced this structure I didn't have the clear vision yet\nthat it will eventually also turn into a proper object database source,\nand how exactly that infrastructure will look like. Now though it's\nbecoming increasingly clear that it does make sense to treat it just the\nsame as any of our other ODB sources.\n\nThe consequence is that the naming is now a bit out-of-date: it's just\nanother source and will be turned into a proper `struct odb_source` over\nthe next couple of commits, but it's not named accordingly.\n\nRename the structure to `odb_source_packed` to align it with this goal\nand to bring it in line with the other sources we already have.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.h |  4 ++--\n packfile.c         | 56 +++++++++++++++++++++++++++---------------------------\n packfile.h         | 32 +++++++++++++++----------------\n 3 files changed, 46 insertions(+), 46 deletions(-)\n\ndiff --git a/odb/source-files.h b/odb/source-files.h\nindex 23a3b4e04b..d7ac3c1c81 100644\n--- a/odb/source-files.h\n+++ b/odb/source-files.h\n@@ -4,7 +4,7 @@\n #include \"odb/source.h\"\n \n struct odb_source_loose;\n-struct packfile_store;\n+struct odb_source_packed;\n \n /*\n  * The files object database source uses a combination of loose objects and\n@@ -13,7 +13,7 @@ struct packfile_store;\n struct odb_source_files {\n \tstruct odb_source base;\n \tstruct odb_source_loose *loose;\n-\tstruct packfile_store *packed;\n+\tstruct odb_source_packed *packed;\n };\n \n /* Allocate and initialize a new object source. */\ndiff --git a/packfile.c b/packfile.c\nindex 89366abfe3..a2d768d0ae 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -859,7 +859,7 @@ struct packed_git *add_packed_git(struct repository *r, const char *path,\n \treturn p;\n }\n \n-void packfile_store_add_pack(struct packfile_store *store,\n+void packfile_store_add_pack(struct odb_source_packed *store,\n \t\t\t     struct packed_git *pack)\n {\n \tif (pack->pack_fd != -1)\n@@ -869,7 +869,7 @@ void packfile_store_add_pack(struct packfile_store *store,\n \tstrmap_put(&store->packs_by_path, pack->pack_name, pack);\n }\n \n-struct packed_git *packfile_store_load_pack(struct packfile_store *store,\n+struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \t\t\t\t\t    const char *idx_path, int local)\n {\n \tstruct strbuf key = STRBUF_INIT;\n@@ -1068,7 +1068,7 @@ static int sort_pack(const struct packfile_list_entry *a,\n \treturn -1;\n }\n \n-void packfile_store_prepare(struct packfile_store *store)\n+void packfile_store_prepare(struct odb_source_packed *store)\n {\n \tif (store->initialized)\n \t\treturn;\n@@ -1084,13 +1084,13 @@ void packfile_store_prepare(struct packfile_store *store)\n \tstore->initialized = true;\n }\n \n-void packfile_store_reprepare(struct packfile_store *store)\n+void packfile_store_reprepare(struct odb_source_packed *store)\n {\n \tstore->initialized = false;\n \tpackfile_store_prepare(store);\n }\n \n-struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store)\n+struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store)\n {\n \tpackfile_store_prepare(store);\n \n@@ -1103,7 +1103,7 @@ struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *stor\n \treturn store->packs.head;\n }\n \n-int packfile_store_count_objects(struct packfile_store *store,\n+int packfile_store_count_objects(struct odb_source_packed *store,\n \t\t\t\t enum odb_count_objects_flags flags UNUSED,\n \t\t\t\t unsigned long *out)\n {\n@@ -2160,7 +2160,7 @@ static int fill_pack_entry(const struct object_id *oid,\n \treturn 1;\n }\n \n-static int find_pack_entry(struct packfile_store *store,\n+static int find_pack_entry(struct odb_source_packed *store,\n \t\t\t   const struct object_id *oid,\n \t\t\t   struct pack_entry *e)\n {\n@@ -2183,7 +2183,7 @@ static int find_pack_entry(struct packfile_store *store,\n \treturn 0;\n }\n \n-int packfile_store_freshen_object(struct packfile_store *store,\n+int packfile_store_freshen_object(struct odb_source_packed *store,\n \t\t\t\t  const struct object_id *oid)\n {\n \tstruct pack_entry e;\n@@ -2199,7 +2199,7 @@ int packfile_store_freshen_object(struct packfile_store *store,\n \treturn 1;\n }\n \n-int packfile_store_read_object_info(struct packfile_store *store,\n+int packfile_store_read_object_info(struct odb_source_packed *store,\n \t\t\t\t    const struct object_id *oid,\n \t\t\t\t    struct object_info *oi,\n \t\t\t\t    enum object_info_flags flags)\n@@ -2234,7 +2234,7 @@ int packfile_store_read_object_info(struct packfile_store *store,\n \treturn 0;\n }\n \n-static void maybe_invalidate_kept_pack_cache(struct packfile_store *store,\n+static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t     unsigned flags)\n {\n \tif (!store->kept_cache.packs)\n@@ -2245,7 +2245,7 @@ static void maybe_invalidate_kept_pack_cache(struct packfile_store *store,\n \tstore->kept_cache.flags = 0;\n }\n \n-struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store,\n+struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t\t       unsigned flags)\n {\n \tmaybe_invalidate_kept_pack_cache(store, flags);\n@@ -2365,8 +2365,8 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-struct packfile_store_for_each_object_wrapper_data {\n-\tstruct packfile_store *store;\n+struct odb_source_packed_for_each_object_wrapper_data {\n+\tstruct odb_source_packed *store;\n \tconst struct object_info *request;\n \todb_for_each_object_cb cb;\n \tvoid *cb_data;\n@@ -2377,7 +2377,7 @@ static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n \t\t\t\t\t\t  uint32_t index_pos,\n \t\t\t\t\t\t  void *cb_data)\n {\n-\tstruct packfile_store_for_each_object_wrapper_data *data = cb_data;\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data = cb_data;\n \n \tif (data->request) {\n \t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n@@ -2411,10 +2411,10 @@ static int match_hash(unsigned len, const unsigned char *a, const unsigned char\n }\n \n static int for_each_prefixed_object_in_midx(\n-\tstruct packfile_store *store,\n+\tstruct odb_source_packed *store,\n \tstruct multi_pack_index *m,\n \tconst struct odb_for_each_object_options *opts,\n-\tstruct packfile_store_for_each_object_wrapper_data *data)\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n {\n \tint ret;\n \n@@ -2470,10 +2470,10 @@ static int for_each_prefixed_object_in_midx(\n }\n \n static int for_each_prefixed_object_in_pack(\n-\tstruct packfile_store *store,\n+\tstruct odb_source_packed *store,\n \tstruct packed_git *p,\n \tconst struct odb_for_each_object_options *opts,\n-\tstruct packfile_store_for_each_object_wrapper_data *data)\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n {\n \tuint32_t num, i, first = 0;\n \tint len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ?\n@@ -2519,9 +2519,9 @@ static int for_each_prefixed_object_in_pack(\n }\n \n static int packfile_store_for_each_prefixed_object(\n-\tstruct packfile_store *store,\n+\tstruct odb_source_packed *store,\n \tconst struct odb_for_each_object_options *opts,\n-\tstruct packfile_store_for_each_object_wrapper_data *data)\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n {\n \tstruct packfile_list_entry *e;\n \tstruct multi_pack_index *m;\n@@ -2566,13 +2566,13 @@ static int packfile_store_for_each_prefixed_object(\n \treturn ret;\n }\n \n-int packfile_store_for_each_object(struct packfile_store *store,\n+int packfile_store_for_each_object(struct odb_source_packed *store,\n \t\t\t\t   const struct object_info *request,\n \t\t\t\t   odb_for_each_object_cb cb,\n \t\t\t\t   void *cb_data,\n \t\t\t\t   const struct odb_for_each_object_options *opts)\n {\n-\tstruct packfile_store_for_each_object_wrapper_data data = {\n+\tstruct odb_source_packed_for_each_object_wrapper_data data = {\n \t\t.store = store,\n \t\t.request = request,\n \t\t.cb = cb,\n@@ -2707,7 +2707,7 @@ static void find_abbrev_len_for_pack(struct packed_git *p,\n \t*out = len;\n }\n \n-int packfile_store_find_abbrev_len(struct packfile_store *store,\n+int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \t\t\t\t   const struct object_id *oid,\n \t\t\t\t   unsigned min_len,\n \t\t\t\t   unsigned *out)\n@@ -2832,16 +2832,16 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-struct packfile_store *packfile_store_new(struct odb_source *source)\n+struct odb_source_packed *packfile_store_new(struct odb_source *source)\n {\n-\tstruct packfile_store *store;\n+\tstruct odb_source_packed *store;\n \tCALLOC_ARRAY(store, 1);\n \tstore->source = source;\n \tstrmap_init(&store->packs_by_path);\n \treturn store;\n }\n \n-void packfile_store_free(struct packfile_store *store)\n+void packfile_store_free(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n \t\tfree(e->pack);\n@@ -2851,7 +2851,7 @@ void packfile_store_free(struct packfile_store *store)\n \tfree(store);\n }\n \n-void packfile_store_close(struct packfile_store *store)\n+void packfile_store_close(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {\n \t\tif (e->pack->do_not_close)\n@@ -2988,7 +2988,7 @@ int packfile_read_object_stream(struct odb_read_stream **out,\n }\n \n int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct packfile_store *store,\n+\t\t\t\t      struct odb_source_packed *store,\n \t\t\t\t      const struct object_id *oid)\n {\n \tstruct pack_entry e;\ndiff --git a/packfile.h b/packfile.h\nindex 49d6bdecf6..9cec15bc50 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -79,7 +79,7 @@ struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n /*\n  * A store that manages packfiles for a given object database.\n  */\n-struct packfile_store {\n+struct odb_source_packed {\n \tstruct odb_source *source;\n \n \t/*\n@@ -138,19 +138,19 @@ struct packfile_store {\n  * Allocate and initialize a new empty packfile store for the given object\n  * database source.\n  */\n-struct packfile_store *packfile_store_new(struct odb_source *source);\n+struct odb_source_packed *packfile_store_new(struct odb_source *source);\n \n /*\n  * Free the packfile store and all its associated state. All packfiles\n  * tracked by the store will be closed.\n  */\n-void packfile_store_free(struct packfile_store *store);\n+void packfile_store_free(struct odb_source_packed *store);\n \n /*\n  * Close all packfiles associated with this store. The packfiles won't be\n  * free'd, so they can be re-opened at a later point in time.\n  */\n-void packfile_store_close(struct packfile_store *store);\n+void packfile_store_close(struct odb_source_packed *store);\n \n /*\n  * Prepare the packfile store by loading packfiles and multi-pack indices for\n@@ -159,7 +159,7 @@ void packfile_store_close(struct packfile_store *store);\n  * It shouldn't typically be necessary to call this function directly, as\n  * functions that access the store know to prepare it.\n  */\n-void packfile_store_prepare(struct packfile_store *store);\n+void packfile_store_prepare(struct odb_source_packed *store);\n \n /*\n  * Clear the packfile caches and try to look up any new packfiles that have\n@@ -167,20 +167,20 @@ void packfile_store_prepare(struct packfile_store *store);\n  *\n  * This function must be called under the `odb_read_lock()`.\n  */\n-void packfile_store_reprepare(struct packfile_store *store);\n+void packfile_store_reprepare(struct odb_source_packed *store);\n \n /*\n  * Add the pack to the store so that contained objects become accessible via\n  * the store. This moves ownership into the store.\n  */\n-void packfile_store_add_pack(struct packfile_store *store,\n+void packfile_store_add_pack(struct odb_source_packed *store,\n \t\t\t     struct packed_git *pack);\n \n /*\n  * Get all packs managed by the given store, including packfiles that are\n  * referenced by multi-pack indices.\n  */\n-struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store);\n+struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store);\n \n struct repo_for_each_pack_data {\n \tstruct odb_source *source;\n@@ -239,7 +239,7 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *\n \t     repo_for_each_pack_data_next(&eack_pack_data))\n \n int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct packfile_store *store,\n+\t\t\t\t      struct odb_source_packed *store,\n \t\t\t\t      const struct object_id *oid);\n \n /*\n@@ -248,7 +248,7 @@ int packfile_store_read_object_stream(struct odb_read_stream **out,\n  * not found, 0 if it was and read successfully, and a negative error code in\n  * case the object was corrupted.\n  */\n-int packfile_store_read_object_info(struct packfile_store *store,\n+int packfile_store_read_object_info(struct odb_source_packed *store,\n \t\t\t\t    const struct object_id *oid,\n \t\t\t\t    struct object_info *oi,\n \t\t\t\t    enum object_info_flags flags);\n@@ -258,10 +258,10 @@ int packfile_store_read_object_info(struct packfile_store *store,\n  * either the newly opened packfile or the preexisting packfile. Returns a\n  * `NULL` pointer in case the packfile could not be opened.\n  */\n-struct packed_git *packfile_store_load_pack(struct packfile_store *store,\n+struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \t\t\t\t\t    const char *idx_path, int local);\n \n-int packfile_store_freshen_object(struct packfile_store *store,\n+int packfile_store_freshen_object(struct odb_source_packed *store,\n \t\t\t\t  const struct object_id *oid);\n \n enum kept_pack_type {\n@@ -276,7 +276,7 @@ enum kept_pack_type {\n  *\n  * Return 0 on success, a negative error code otherwise.\n  */\n-int packfile_store_count_objects(struct packfile_store *store,\n+int packfile_store_count_objects(struct odb_source_packed *store,\n \t\t\t\t enum odb_count_objects_flags flags,\n \t\t\t\t unsigned long *out);\n \n@@ -285,7 +285,7 @@ int packfile_store_count_objects(struct packfile_store *store,\n  * combination of `kept_pack_type` flags. The cache is computed on demand and\n  * will be recomputed whenever the flags change.\n  */\n-struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store,\n+struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t\t       unsigned flags);\n \n struct pack_window {\n@@ -365,13 +365,13 @@ int for_each_object_in_pack(struct packed_git *p,\n  *\n  * The flags parameter is a combination of `odb_for_each_object_flags`.\n  */\n-int packfile_store_for_each_object(struct packfile_store *store,\n+int packfile_store_for_each_object(struct odb_source_packed *store,\n \t\t\t\t   const struct object_info *request,\n \t\t\t\t   odb_for_each_object_cb cb,\n \t\t\t\t   void *cb_data,\n \t\t\t\t   const struct odb_for_each_object_options *opts);\n \n-int packfile_store_find_abbrev_len(struct packfile_store *store,\n+int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \t\t\t\t   const struct object_id *oid,\n \t\t\t\t   unsigned min_len,\n \t\t\t\t   unsigned *out);\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544727","messageId":"20260604-pks-odb-source-packed-v1-2-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 02/16] packfile: move packed source into \"odb/\" subsystem","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:29Z","receivedAt":"2026-06-04T11:25:46Z","isPatch":true,"body":"In subsequent patches we'll be turning `struct odb_source_packed` into a\nproper `struct odb_source`. As a first step towards this goal, move its\nstruct out of \"packfile.{c,h}\" and into \"odb/source-packed.{c,h}\".\n\nThis detaches the implementation of the packfile object source from the\ngeneric packfile code, following the same convention already used by the\n\"files\" and \"in-memory\" sources.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Makefile            |  1 +\n meson.build         |  1 +\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 11 ++++++++\n odb/source-packed.h | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.c          |  9 ------\n packfile.h          | 75 +------------------------------------------------\n 7 files changed, 95 insertions(+), 84 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 0976a69b4c..e3381b8857 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1218,6 +1218,7 @@ LIB_OBJS += odb/source.o\n LIB_OBJS += odb/source-files.o\n LIB_OBJS += odb/source-inmemory.o\n LIB_OBJS += odb/source-loose.o\n+LIB_OBJS += odb/source-packed.o\n LIB_OBJS += odb/streaming.o\n LIB_OBJS += odb/transaction.o\n LIB_OBJS += oid-array.o\ndiff --git a/meson.build b/meson.build\nindex 3247697f74..1976024008 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -406,6 +406,7 @@ libgit_sources = [\n   'odb/source-files.c',\n   'odb/source-inmemory.c',\n   'odb/source-loose.c',\n+  'odb/source-packed.c',\n   'odb/streaming.c',\n   'odb/transaction.c',\n   'oid-array.c',\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 5bdd042922..191562f316 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tCALLOC_ARRAY(files, 1);\n \todb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);\n \tfiles->loose = odb_source_loose_new(odb, path, local);\n-\tfiles->packed = packfile_store_new(&files->base);\n+\tfiles->packed = odb_source_packed_new(&files->base);\n \n \tfiles->base.free = odb_source_files_free;\n \tfiles->base.close = odb_source_files_close;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nnew file mode 100644\nindex 0000000000..1e94b47ea0\n--- /dev/null\n+++ b/odb/source-packed.c\n@@ -0,0 +1,11 @@\n+#include \"git-compat-util.h\"\n+#include \"odb/source-packed.h\"\n+\n+struct odb_source_packed *odb_source_packed_new(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *store;\n+\tCALLOC_ARRAY(store, 1);\n+\tstore->source = source;\n+\tstrmap_init(&store->packs_by_path);\n+\treturn store;\n+}\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nnew file mode 100644\nindex 0000000000..c17068a4f1\n--- /dev/null\n+++ b/odb/source-packed.h\n@@ -0,0 +1,80 @@\n+#ifndef ODB_SOURCE_PACKED_H\n+#define ODB_SOURCE_PACKED_H\n+\n+#include \"odb/source.h\"\n+#include \"strmap.h\"\n+\n+struct packfile_list {\n+\tstruct packfile_list_entry *head, *tail;\n+};\n+\n+struct packfile_list_entry {\n+\tstruct packfile_list_entry *next;\n+\tstruct packed_git *pack;\n+};\n+\n+/*\n+ * A store that manages packfiles for a given object database.\n+ */\n+struct odb_source_packed {\n+\tstruct odb_source *source;\n+\n+\t/*\n+\t * The list of packfiles in the order in which they have been most\n+\t * recently used.\n+\t */\n+\tstruct packfile_list packs;\n+\n+\t/*\n+\t * Cache of packfiles which are marked as \"kept\", either because there\n+\t * is an on-disk \".keep\" file or because they are marked as \"kept\" in\n+\t * memory.\n+\t *\n+\t * Should not be accessed directly, but via\n+\t * `packfile_store_get_kept_pack_cache()`. The list of packs gets\n+\t * invalidated when the stored flags and the flags passed to\n+\t * `packfile_store_get_kept_pack_cache()` mismatch.\n+\t */\n+\tstruct {\n+\t\tstruct packed_git **packs;\n+\t\tunsigned flags;\n+\t} kept_cache;\n+\n+\t/* The multi-pack index that belongs to this specific packfile store. */\n+\tstruct multi_pack_index *midx;\n+\n+\t/*\n+\t * A map of packfile names to packed_git structs for tracking which\n+\t * packs have been loaded already.\n+\t */\n+\tstruct strmap packs_by_path;\n+\n+\t/*\n+\t * Whether packfiles have already been populated with this store's\n+\t * packs.\n+\t */\n+\tbool initialized;\n+\n+\t/*\n+\t * Usually, packfiles will be reordered to the front of the `packs`\n+\t * list whenever an object is looked up via them. This has the effect\n+\t * that packs that contain a lot of accessed objects will be located\n+\t * towards the front.\n+\t *\n+\t * This is usually desireable, but there are exceptions. One exception\n+\t * is when the looking up multiple objects in a loop for each packfile.\n+\t * In that case, we may easily end up with an infinite loop as the\n+\t * packfiles get reordered to the front repeatedly.\n+\t *\n+\t * Setting this field to `true` thus disables these reorderings.\n+\t */\n+\tbool skip_mru_updates;\n+};\n+\n+/*\n+ * Allocate and initialize a new empty packfile store for the given object\n+ * database source.\n+ */\n+struct odb_source_packed *odb_source_packed_new(struct odb_source *source);\n+\n+#endif\ndiff --git a/packfile.c b/packfile.c\nindex a2d768d0ae..f2b27df66e 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2832,15 +2832,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-struct odb_source_packed *packfile_store_new(struct odb_source *source)\n-{\n-\tstruct odb_source_packed *store;\n-\tCALLOC_ARRAY(store, 1);\n-\tstore->source = source;\n-\tstrmap_init(&store->packs_by_path);\n-\treturn store;\n-}\n-\n void packfile_store_free(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\ndiff --git a/packfile.h b/packfile.h\nindex 9cec15bc50..6f76df3589 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -5,9 +5,9 @@\n #include \"object.h\"\n #include \"odb.h\"\n #include \"odb/source-files.h\"\n+#include \"odb/source-packed.h\"\n #include \"oidset.h\"\n #include \"repository.h\"\n-#include \"strmap.h\"\n \n /* in odb.h */\n struct object_info;\n@@ -54,15 +54,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-struct packfile_list {\n-\tstruct packfile_list_entry *head, *tail;\n-};\n-\n-struct packfile_list_entry {\n-\tstruct packfile_list_entry *next;\n-\tstruct packed_git *pack;\n-};\n-\n void packfile_list_clear(struct packfile_list *list);\n void packfile_list_remove(struct packfile_list *list, struct packed_git *pack);\n void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack);\n@@ -76,70 +67,6 @@ void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n \t\t\t\t\t  const struct object_id *oid);\n \n-/*\n- * A store that manages packfiles for a given object database.\n- */\n-struct odb_source_packed {\n-\tstruct odb_source *source;\n-\n-\t/*\n-\t * The list of packfiles in the order in which they have been most\n-\t * recently used.\n-\t */\n-\tstruct packfile_list packs;\n-\n-\t/*\n-\t * Cache of packfiles which are marked as \"kept\", either because there\n-\t * is an on-disk \".keep\" file or because they are marked as \"kept\" in\n-\t * memory.\n-\t *\n-\t * Should not be accessed directly, but via\n-\t * `packfile_store_get_kept_pack_cache()`. The list of packs gets\n-\t * invalidated when the stored flags and the flags passed to\n-\t * `packfile_store_get_kept_pack_cache()` mismatch.\n-\t */\n-\tstruct {\n-\t\tstruct packed_git **packs;\n-\t\tunsigned flags;\n-\t} kept_cache;\n-\n-\t/* The multi-pack index that belongs to this specific packfile store. */\n-\tstruct multi_pack_index *midx;\n-\n-\t/*\n-\t * A map of packfile names to packed_git structs for tracking which\n-\t * packs have been loaded already.\n-\t */\n-\tstruct strmap packs_by_path;\n-\n-\t/*\n-\t * Whether packfiles have already been populated with this store's\n-\t * packs.\n-\t */\n-\tbool initialized;\n-\n-\t/*\n-\t * Usually, packfiles will be reordered to the front of the `packs`\n-\t * list whenever an object is looked up via them. This has the effect\n-\t * that packs that contain a lot of accessed objects will be located\n-\t * towards the front.\n-\t *\n-\t * This is usually desireable, but there are exceptions. One exception\n-\t * is when the looking up multiple objects in a loop for each packfile.\n-\t * In that case, we may easily end up with an infinite loop as the\n-\t * packfiles get reordered to the front repeatedly.\n-\t *\n-\t * Setting this field to `true` thus disables these reorderings.\n-\t */\n-\tbool skip_mru_updates;\n-};\n-\n-/*\n- * Allocate and initialize a new empty packfile store for the given object\n- * database source.\n- */\n-struct odb_source_packed *packfile_store_new(struct odb_source *source);\n-\n /*\n  * Free the packfile store and all its associated state. All packfiles\n  * tracked by the store will be closed.\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544728","messageId":"20260604-pks-odb-source-packed-v1-3-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 03/16] odb/source-packed: store pointer to \"files\" instead of generic source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:30Z","receivedAt":"2026-06-04T11:25:48Z","isPatch":true,"body":"The `struct odb_source_packed` holds a pointer to its owning parent\nsource. The way that Git is currently structured, this parent is always\nthe \"files\" source. In subsequent commits we're going to detangle that\nso that the \"packed\" source doesn't have any owning parent source at\nall, which makes it usable as a completely standalone source.\n\nDetangling this mess is somewhat intricate though, and is made even more\nintricate because it's not always clear which kind of source one is\nholding at a specific point in time -- either the parent \"files\" source,\nor the child \"packed\" source.\n\nMake this relationship more explicit by storing a pointer to the \"files\"\nsource instead of storing a pointer to a generic `struct odb_source`.\nThis will help make subsequent steps a bit clearer.\n\nNote that this is a temporary step, only. At the end of this series\nwe will have dropped the parent pointer completely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c |  4 ++--\n odb/source-packed.h |  4 ++--\n packfile.c          | 12 ++++++------\n 4 files changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 191562f316..e04525fb08 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tCALLOC_ARRAY(files, 1);\n \todb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);\n \tfiles->loose = odb_source_loose_new(odb, path, local);\n-\tfiles->packed = odb_source_packed_new(&files->base);\n+\tfiles->packed = odb_source_packed_new(files);\n \n \tfiles->base.free = odb_source_files_free;\n \tfiles->base.close = odb_source_files_close;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 1e94b47ea0..12e785be48 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,11 +1,11 @@\n #include \"git-compat-util.h\"\n #include \"odb/source-packed.h\"\n \n-struct odb_source_packed *odb_source_packed_new(struct odb_source *source)\n+struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n {\n \tstruct odb_source_packed *store;\n \tCALLOC_ARRAY(store, 1);\n-\tstore->source = source;\n+\tstore->files = parent;\n \tstrmap_init(&store->packs_by_path);\n \treturn store;\n }\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex c17068a4f1..6e5c79a77a 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -17,7 +17,7 @@ struct packfile_list_entry {\n  * A store that manages packfiles for a given object database.\n  */\n struct odb_source_packed {\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *files;\n \n \t/*\n \t * The list of packfiles in the order in which they have been most\n@@ -75,6 +75,6 @@ struct odb_source_packed {\n  * Allocate and initialize a new empty packfile store for the given object\n  * database source.\n  */\n-struct odb_source_packed *odb_source_packed_new(struct odb_source *source);\n+struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent);\n \n #endif\ndiff --git a/packfile.c b/packfile.c\nindex f2b27df66e..2af07f5e1a 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -885,7 +885,7 @@ struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \n \tp = strmap_get(&store->packs_by_path, key.buf);\n \tif (!p) {\n-\t\tp = add_packed_git(store->source->odb->repo, idx_path,\n+\t\tp = add_packed_git(store->files->base.odb->repo, idx_path,\n \t\t\t\t   strlen(idx_path), local);\n \t\tif (p)\n \t\t\tpackfile_store_add_pack(store, p);\n@@ -1073,8 +1073,8 @@ void packfile_store_prepare(struct odb_source_packed *store)\n \tif (store->initialized)\n \t\treturn;\n \n-\tprepare_multi_pack_index_one(store->source);\n-\tprepare_packed_git_one(store->source);\n+\tprepare_multi_pack_index_one(&store->files->base);\n+\tprepare_packed_git_one(&store->files->base);\n \n \tsort_packs(&store->packs.head, sort_pack);\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n@@ -1112,7 +1112,7 @@ int packfile_store_count_objects(struct odb_source_packed *store,\n \tunsigned long count = 0;\n \tint ret;\n \n-\tm = get_multi_pack_index(store->source);\n+\tm = get_multi_pack_index(&store->files->base);\n \tif (m)\n \t\tcount += m->num_objects + m->num_objects_in_base;\n \n@@ -2533,7 +2533,7 @@ static int packfile_store_for_each_prefixed_object(\n \n \tstore->skip_mru_updates = true;\n \n-\tm = get_multi_pack_index(store->source);\n+\tm = get_multi_pack_index(&store->files->base);\n \tif (m) {\n \t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n \t\tif (ret)\n@@ -2715,7 +2715,7 @@ int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \tstruct packfile_list_entry *e;\n \tstruct multi_pack_index *m;\n \n-\tm = get_multi_pack_index(store->source);\n+\tm = get_multi_pack_index(&store->files->base);\n \tif (m)\n \t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n \n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544729","messageId":"20260604-pks-odb-source-packed-v1-4-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 04/16] odb/source-packed: start converting to a proper `struct odb_source`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:31Z","receivedAt":"2026-06-04T11:25:50Z","isPatch":true,"body":"Start converting `struct odb_source_packed` into a proper pluggable\n`struct odb_source` by embedding the base struct and assigning it the\nnew `ODB_SOURCE_PACKED` type. Furthermore, wire up lifecycle management\nof this source by implementing the `free` callback and taking ownership\nof the chdir notifications.\n\nNote that the packed source is not yet functional as a standalone `struct\nodb_source`, as it's missing all of the callback implementations. These\nwill be wired up in subsequent commits.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 49 ++++++++++++++++++++++++++++++++++++++++++++-----\n odb/source-packed.h | 12 ++++++++++++\n odb/source.h        |  3 +++\n packfile.c          | 10 ----------\n packfile.h          |  6 ------\n 6 files changed, 60 insertions(+), 22 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex e04525fb08..3608808e7c 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -29,7 +29,7 @@ static void odb_source_files_free(struct odb_source *source)\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \tchdir_notify_unregister(NULL, odb_source_files_reparent, files);\n \todb_source_free(&files->loose->base);\n-\tpackfile_store_free(files->packed);\n+\todb_source_free(&files->packed->base);\n \todb_source_release(&files->base);\n \tfree(files);\n }\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 12e785be48..f81a990cbd 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,11 +1,50 @@\n #include \"git-compat-util.h\"\n+#include \"abspath.h\"\n+#include \"chdir-notify.h\"\n #include \"odb/source-packed.h\"\n+#include \"packfile.h\"\n+\n+static void odb_source_packed_reparent(const char *name UNUSED,\n+\t\t\t\t       const char *old_cwd,\n+\t\t\t\t       const char *new_cwd,\n+\t\t\t\t       void *cb_data)\n+{\n+\tstruct odb_source_packed *packed = cb_data;\n+\tchar *path = reparent_relative_path(old_cwd, new_cwd,\n+\t\t\t\t\t    packed->base.path);\n+\tfree(packed->base.path);\n+\tpacked->base.path = path;\n+}\n+\n+static void odb_source_packed_free(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\n+\tchdir_notify_unregister(NULL, odb_source_packed_reparent, packed);\n+\n+\tfor (struct packfile_list_entry *e = packed->packs.head; e; e = e->next)\n+\t\tfree(e->pack);\n+\tpackfile_list_clear(&packed->packs);\n+\n+\tstrmap_clear(&packed->packs_by_path, 0);\n+\todb_source_release(&packed->base);\n+\tfree(packed);\n+}\n \n struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n {\n-\tstruct odb_source_packed *store;\n-\tCALLOC_ARRAY(store, 1);\n-\tstore->files = parent;\n-\tstrmap_init(&store->packs_by_path);\n-\treturn store;\n+\tstruct odb_source_packed *packed;\n+\n+\tCALLOC_ARRAY(packed, 1);\n+\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n+\t\t\tparent->base.path, parent->base.local);\n+\tpacked->files = parent;\n+\tstrmap_init(&packed->packs_by_path);\n+\n+\tpacked->base.free = odb_source_packed_free;\n+\n+\tif (!is_absolute_path(parent->base.path))\n+\t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n+\n+\treturn packed;\n }\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 6e5c79a77a..abe3310069 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -17,6 +17,7 @@ struct packfile_list_entry {\n  * A store that manages packfiles for a given object database.\n  */\n struct odb_source_packed {\n+\tstruct odb_source base;\n \tstruct odb_source_files *files;\n \n \t/*\n@@ -77,4 +78,15 @@ struct odb_source_packed {\n  */\n struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent);\n \n+/*\n+ * Cast the given object database source to the packed backend. This will cause\n+ * a BUG in case the source doesn't use this backend.\n+ */\n+static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_source *source)\n+{\n+\tif (source->type != ODB_SOURCE_PACKED)\n+\t\tBUG(\"trying to downcast source of type '%d' to packed\", source->type);\n+\treturn container_of(source, struct odb_source_packed, base);\n+}\n+\n #endif\ndiff --git a/odb/source.h b/odb/source.h\nindex 8bcb67787e..6865e1f71a 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -17,6 +17,9 @@ enum odb_source_type {\n \t/* The \"loose\" backend that uses loose objects, only. */\n \tODB_SOURCE_LOOSE,\n \n+\t/* The \"packed\" backend that uses packfiles. */\n+\tODB_SOURCE_PACKED,\n+\n \t/* The \"in-memory\" backend that stores objects in memory. */\n \tODB_SOURCE_INMEMORY,\n };\ndiff --git a/packfile.c b/packfile.c\nindex 2af07f5e1a..8fefc4358e 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2832,16 +2832,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-void packfile_store_free(struct odb_source_packed *store)\n-{\n-\tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n-\t\tfree(e->pack);\n-\tpackfile_list_clear(&store->packs);\n-\n-\tstrmap_clear(&store->packs_by_path, 0);\n-\tfree(store);\n-}\n-\n void packfile_store_close(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {\ndiff --git a/packfile.h b/packfile.h\nindex 6f76df3589..d6814b564e 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -67,12 +67,6 @@ void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n \t\t\t\t\t  const struct object_id *oid);\n \n-/*\n- * Free the packfile store and all its associated state. All packfiles\n- * tracked by the store will be closed.\n- */\n-void packfile_store_free(struct odb_source_packed *store);\n-\n /*\n  * Close all packfiles associated with this store. The packfiles won't be\n  * free'd, so they can be re-opened at a later point in time.\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544730","messageId":"20260604-pks-odb-source-packed-v1-5-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 05/16] odb/source-packed: wire up `close()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:32Z","receivedAt":"2026-06-04T11:25:53Z","isPatch":true,"body":"Wire up a new `close()` callback for the packed source and call it from\nthe \"files\" source via the generic `odb_source_close()` interface.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 16 ++++++++++++++++\n packfile.c          | 12 ------------\n packfile.h          |  6 ------\n 4 files changed, 17 insertions(+), 19 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 3608808e7c..9b0fa9ccdc 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -38,7 +38,7 @@ static void odb_source_files_close(struct odb_source *source)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \todb_source_close(&files->loose->base);\n-\tpackfile_store_close(files->packed);\n+\todb_source_close(&files->packed->base);\n }\n \n static void odb_source_files_reprepare(struct odb_source *source)\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex f81a990cbd..74805be1dd 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n+#include \"midx.h\"\n #include \"odb/source-packed.h\"\n #include \"packfile.h\"\n \n@@ -16,6 +17,20 @@ static void odb_source_packed_reparent(const char *name UNUSED,\n \tpacked->base.path = path;\n }\n \n+static void odb_source_packed_close(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\n+\tfor (struct packfile_list_entry *e = packed->packs.head; e; e = e->next) {\n+\t\tif (e->pack->do_not_close)\n+\t\t\tBUG(\"want to close pack marked 'do-not-close'\");\n+\t\tclose_pack(e->pack);\n+\t}\n+\tif (packed->midx)\n+\t\tclose_midx(packed->midx);\n+\tpacked->midx = NULL;\n+}\n+\n static void odb_source_packed_free(struct odb_source *source)\n {\n \tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n@@ -42,6 +57,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tstrmap_init(&packed->packs_by_path);\n \n \tpacked->base.free = odb_source_packed_free;\n+\tpacked->base.close = odb_source_packed_close;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 8fefc4358e..3cff3b8ffa 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2832,18 +2832,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-void packfile_store_close(struct odb_source_packed *store)\n-{\n-\tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {\n-\t\tif (e->pack->do_not_close)\n-\t\t\tBUG(\"want to close pack marked 'do-not-close'\");\n-\t\tclose_pack(e->pack);\n-\t}\n-\tif (store->midx)\n-\t\tclose_midx(store->midx);\n-\tstore->midx = NULL;\n-}\n-\n struct odb_packed_read_stream {\n \tstruct odb_read_stream base;\n \tstruct packed_git *pack;\ndiff --git a/packfile.h b/packfile.h\nindex d6814b564e..23af47f0c1 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -67,12 +67,6 @@ void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n \t\t\t\t\t  const struct object_id *oid);\n \n-/*\n- * Close all packfiles associated with this store. The packfiles won't be\n- * free'd, so they can be re-opened at a later point in time.\n- */\n-void packfile_store_close(struct odb_source_packed *store);\n-\n /*\n  * Prepare the packfile store by loading packfiles and multi-pack indices for\n  * all alternates. This becomes a no-op if the store is already prepared.\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544731","messageId":"20260604-pks-odb-source-packed-v1-6-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 06/16] odb/source-packed: wire up `reprepare()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:33Z","receivedAt":"2026-06-04T11:25:55Z","isPatch":true,"body":"Move the logic to prepare and reprepare the \"packed\" source into\n\"odb/source-packed.c\" and wire it up as the `reprepare()` callback.\n\nNote that \"preparing\" a source is not yet generic. Eventually, it would\nprobably make sense to turn the existing `reprepare()` callback into a\n`prepare()` callback with an optional flag to force re-preparing. But\nthis step will be handled in a separate patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c      |   2 +-\n midx.c              |   2 +-\n odb/source-files.c  |   2 +-\n odb/source-packed.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++\n odb/source-packed.h |   9 +++\n packfile.c          | 160 +---------------------------------------------------\n packfile.h          |  17 ------\n 7 files changed, 172 insertions(+), 177 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 6a09571903..8080d1bf5e 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -1363,7 +1363,7 @@ int cmd_grep(int argc,\n \t\t\todb_prepare_alternates(the_repository->objects);\n \t\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n \t\t\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\t\t\tpackfile_store_prepare(files->packed);\n+\t\t\t\todb_source_packed_prepare(files->packed);\n \t\t\t}\n \t\t}\n \ndiff --git a/midx.c b/midx.c\nindex efbfbb13f4..00bbd137b2 100644\n--- a/midx.c\n+++ b/midx.c\n@@ -102,7 +102,7 @@ static int midx_read_object_offsets(const unsigned char *chunk_start,\n struct multi_pack_index *get_multi_pack_index(struct odb_source *source)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tpackfile_store_prepare(files->packed);\n+\todb_source_packed_prepare(files->packed);\n \treturn files->packed->midx;\n }\n \ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 9b0fa9ccdc..7b1e0ac565 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -45,7 +45,7 @@ static void odb_source_files_reprepare(struct odb_source *source)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \todb_source_reprepare(&files->loose->base);\n-\tpackfile_store_reprepare(files->packed);\n+\todb_source_reprepare(&files->packed->base);\n }\n \n static int odb_source_files_read_object_info(struct odb_source *source,\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 74805be1dd..e8e2e5bb48 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,10 +1,166 @@\n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n+#include \"dir.h\"\n+#include \"mergesort.h\"\n #include \"midx.h\"\n #include \"odb/source-packed.h\"\n #include \"packfile.h\"\n \n+void (*report_garbage)(unsigned seen_bits, const char *path);\n+\n+static void report_helper(const struct string_list *list,\n+\t\t\t  int seen_bits, int first, int last)\n+{\n+\tif (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX))\n+\t\treturn;\n+\n+\tfor (; first < last; first++)\n+\t\treport_garbage(seen_bits, list->items[first].string);\n+}\n+\n+static void report_pack_garbage(struct string_list *list)\n+{\n+\tint baselen = -1, first = 0, seen_bits = 0;\n+\n+\tif (!report_garbage)\n+\t\treturn;\n+\n+\tstring_list_sort(list);\n+\n+\tfor (size_t i = 0; i < list->nr; i++) {\n+\t\tconst char *path = list->items[i].string;\n+\t\tif (baselen != -1 &&\n+\t\t    strncmp(path, list->items[first].string, baselen)) {\n+\t\t\treport_helper(list, seen_bits, first, i);\n+\t\t\tbaselen = -1;\n+\t\t\tseen_bits = 0;\n+\t\t}\n+\t\tif (baselen == -1) {\n+\t\t\tconst char *dot = strrchr(path, '.');\n+\t\t\tif (!dot) {\n+\t\t\t\treport_garbage(PACKDIR_FILE_GARBAGE, path);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tbaselen = dot - path + 1;\n+\t\t\tfirst = i;\n+\t\t}\n+\t\tif (!strcmp(path + baselen, \"pack\"))\n+\t\t\tseen_bits |= 1;\n+\t\telse if (!strcmp(path + baselen, \"idx\"))\n+\t\t\tseen_bits |= 2;\n+\t}\n+\treport_helper(list, seen_bits, first, list->nr);\n+}\n+\n+struct prepare_pack_data {\n+\tstruct odb_source *source;\n+\tstruct string_list *garbage;\n+};\n+\n+static void prepare_pack(const char *full_name, size_t full_name_len,\n+\t\t\t const char *file_name, void *_data)\n+{\n+\tstruct prepare_pack_data *data = (struct prepare_pack_data *)_data;\n+\tstruct odb_source_files *files = odb_source_files_downcast(data->source);\n+\tsize_t base_len = full_name_len;\n+\n+\tif (strip_suffix_mem(full_name, &base_len, \".idx\") &&\n+\t    !(files->packed->midx &&\n+\t      midx_contains_pack(files->packed->midx, file_name))) {\n+\t\tchar *trimmed_path = xstrndup(full_name, full_name_len);\n+\t\tpackfile_store_load_pack(files->packed,\n+\t\t\t\t\t trimmed_path, data->source->local);\n+\t\tfree(trimmed_path);\n+\t}\n+\n+\tif (!report_garbage)\n+\t\treturn;\n+\n+\tif (!strcmp(file_name, \"multi-pack-index\") ||\n+\t    !strcmp(file_name, \"multi-pack-index.d\"))\n+\t\treturn;\n+\tif (starts_with(file_name, \"multi-pack-index\") &&\n+\t    (ends_with(file_name, \".bitmap\") || ends_with(file_name, \".rev\")))\n+\t\treturn;\n+\tif (ends_with(file_name, \".idx\") ||\n+\t    ends_with(file_name, \".rev\") ||\n+\t    ends_with(file_name, \".pack\") ||\n+\t    ends_with(file_name, \".bitmap\") ||\n+\t    ends_with(file_name, \".keep\") ||\n+\t    ends_with(file_name, \".promisor\") ||\n+\t    ends_with(file_name, \".mtimes\"))\n+\t\tstring_list_append(data->garbage, full_name);\n+\telse\n+\t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n+}\n+\n+static void prepare_packed_git_one(struct odb_source *source)\n+{\n+\tstruct string_list garbage = STRING_LIST_INIT_DUP;\n+\tstruct prepare_pack_data data = {\n+\t\t.source = source,\n+\t\t.garbage = &garbage,\n+\t};\n+\n+\tfor_each_file_in_pack_dir(source->path, prepare_pack, &data);\n+\n+\treport_pack_garbage(data.garbage);\n+\tstring_list_clear(data.garbage, 0);\n+}\n+\n+DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next);\n+\n+static int sort_pack(const struct packfile_list_entry *a,\n+\t\t     const struct packfile_list_entry *b)\n+{\n+\tint st;\n+\n+\t/*\n+\t * Local packs tend to contain objects specific to our\n+\t * variant of the project than remote ones.  In addition,\n+\t * remote ones could be on a network mounted filesystem.\n+\t * Favor local ones for these reasons.\n+\t */\n+\tst = a->pack->pack_local - b->pack->pack_local;\n+\tif (st)\n+\t\treturn -st;\n+\n+\t/*\n+\t * Younger packs tend to contain more recent objects,\n+\t * and more recent objects tend to get accessed more\n+\t * often.\n+\t */\n+\tif (a->pack->mtime < b->pack->mtime)\n+\t\treturn 1;\n+\telse if (a->pack->mtime == b->pack->mtime)\n+\t\treturn 0;\n+\treturn -1;\n+}\n+\n+void odb_source_packed_prepare(struct odb_source_packed *source)\n+{\n+\tif (source->initialized)\n+\t\treturn;\n+\n+\tprepare_multi_pack_index_one(&source->files->base);\n+\tprepare_packed_git_one(&source->files->base);\n+\n+\tsort_packs(&source->packs.head, sort_pack);\n+\tfor (struct packfile_list_entry *e = source->packs.head; e; e = e->next)\n+\t\tif (!e->next)\n+\t\t\tsource->packs.tail = e;\n+\n+\tsource->initialized = true;\n+}\n+\n+static void odb_source_packed_reprepare(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tpacked->initialized = false;\n+\todb_source_packed_prepare(packed);\n+}\n+\n static void odb_source_packed_reparent(const char *name UNUSED,\n \t\t\t\t       const char *old_cwd,\n \t\t\t\t       const char *new_cwd,\n@@ -58,6 +214,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \n \tpacked->base.free = odb_source_packed_free;\n \tpacked->base.close = odb_source_packed_close;\n+\tpacked->base.reprepare = odb_source_packed_reprepare;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex abe3310069..6645f4f943 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -89,4 +89,13 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so\n \treturn container_of(source, struct odb_source_packed, base);\n }\n \n+/*\n+ * Prepare the source by loading packfiles and multi-pack indices for\n+ * all alternates. This becomes a no-op if the source is already prepared.\n+ *\n+ * It shouldn't typically be necessary to call this function directly, as\n+ * functions that access the source know to prepare it.\n+ */\n+void odb_source_packed_prepare(struct odb_source_packed *source);\n+\n #endif\ndiff --git a/packfile.c b/packfile.c\nindex 3cff3b8ffa..902b7f70f2 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -8,7 +8,6 @@\n #include \"pack.h\"\n #include \"repository.h\"\n #include \"dir.h\"\n-#include \"mergesort.h\"\n #include \"packfile.h\"\n #include \"delta.h\"\n #include \"hash-lookup.h\"\n@@ -895,52 +894,6 @@ struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \treturn p;\n }\n \n-void (*report_garbage)(unsigned seen_bits, const char *path);\n-\n-static void report_helper(const struct string_list *list,\n-\t\t\t  int seen_bits, int first, int last)\n-{\n-\tif (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX))\n-\t\treturn;\n-\n-\tfor (; first < last; first++)\n-\t\treport_garbage(seen_bits, list->items[first].string);\n-}\n-\n-static void report_pack_garbage(struct string_list *list)\n-{\n-\tint i, baselen = -1, first = 0, seen_bits = 0;\n-\n-\tif (!report_garbage)\n-\t\treturn;\n-\n-\tstring_list_sort(list);\n-\n-\tfor (i = 0; i < list->nr; i++) {\n-\t\tconst char *path = list->items[i].string;\n-\t\tif (baselen != -1 &&\n-\t\t    strncmp(path, list->items[first].string, baselen)) {\n-\t\t\treport_helper(list, seen_bits, first, i);\n-\t\t\tbaselen = -1;\n-\t\t\tseen_bits = 0;\n-\t\t}\n-\t\tif (baselen == -1) {\n-\t\t\tconst char *dot = strrchr(path, '.');\n-\t\t\tif (!dot) {\n-\t\t\t\treport_garbage(PACKDIR_FILE_GARBAGE, path);\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t\tbaselen = dot - path + 1;\n-\t\t\tfirst = i;\n-\t\t}\n-\t\tif (!strcmp(path + baselen, \"pack\"))\n-\t\t\tseen_bits |= 1;\n-\t\telse if (!strcmp(path + baselen, \"idx\"))\n-\t\t\tseen_bits |= 2;\n-\t}\n-\treport_helper(list, seen_bits, first, list->nr);\n-}\n-\n void for_each_file_in_pack_subdir(const char *objdir,\n \t\t\t\t  const char *subdir,\n \t\t\t\t  each_file_in_pack_dir_fn fn,\n@@ -983,116 +936,9 @@ void for_each_file_in_pack_dir(const char *objdir,\n \tfor_each_file_in_pack_subdir(objdir, NULL, fn, data);\n }\n \n-struct prepare_pack_data {\n-\tstruct odb_source *source;\n-\tstruct string_list *garbage;\n-};\n-\n-static void prepare_pack(const char *full_name, size_t full_name_len,\n-\t\t\t const char *file_name, void *_data)\n-{\n-\tstruct prepare_pack_data *data = (struct prepare_pack_data *)_data;\n-\tstruct odb_source_files *files = odb_source_files_downcast(data->source);\n-\tsize_t base_len = full_name_len;\n-\n-\tif (strip_suffix_mem(full_name, &base_len, \".idx\") &&\n-\t    !(files->packed->midx &&\n-\t      midx_contains_pack(files->packed->midx, file_name))) {\n-\t\tchar *trimmed_path = xstrndup(full_name, full_name_len);\n-\t\tpackfile_store_load_pack(files->packed,\n-\t\t\t\t\t trimmed_path, data->source->local);\n-\t\tfree(trimmed_path);\n-\t}\n-\n-\tif (!report_garbage)\n-\t\treturn;\n-\n-\tif (!strcmp(file_name, \"multi-pack-index\") ||\n-\t    !strcmp(file_name, \"multi-pack-index.d\"))\n-\t\treturn;\n-\tif (starts_with(file_name, \"multi-pack-index\") &&\n-\t    (ends_with(file_name, \".bitmap\") || ends_with(file_name, \".rev\")))\n-\t\treturn;\n-\tif (ends_with(file_name, \".idx\") ||\n-\t    ends_with(file_name, \".rev\") ||\n-\t    ends_with(file_name, \".pack\") ||\n-\t    ends_with(file_name, \".bitmap\") ||\n-\t    ends_with(file_name, \".keep\") ||\n-\t    ends_with(file_name, \".promisor\") ||\n-\t    ends_with(file_name, \".mtimes\"))\n-\t\tstring_list_append(data->garbage, full_name);\n-\telse\n-\t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n-}\n-\n-static void prepare_packed_git_one(struct odb_source *source)\n-{\n-\tstruct string_list garbage = STRING_LIST_INIT_DUP;\n-\tstruct prepare_pack_data data = {\n-\t\t.source = source,\n-\t\t.garbage = &garbage,\n-\t};\n-\n-\tfor_each_file_in_pack_dir(source->path, prepare_pack, &data);\n-\n-\treport_pack_garbage(data.garbage);\n-\tstring_list_clear(data.garbage, 0);\n-}\n-\n-DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next);\n-\n-static int sort_pack(const struct packfile_list_entry *a,\n-\t\t     const struct packfile_list_entry *b)\n-{\n-\tint st;\n-\n-\t/*\n-\t * Local packs tend to contain objects specific to our\n-\t * variant of the project than remote ones.  In addition,\n-\t * remote ones could be on a network mounted filesystem.\n-\t * Favor local ones for these reasons.\n-\t */\n-\tst = a->pack->pack_local - b->pack->pack_local;\n-\tif (st)\n-\t\treturn -st;\n-\n-\t/*\n-\t * Younger packs tend to contain more recent objects,\n-\t * and more recent objects tend to get accessed more\n-\t * often.\n-\t */\n-\tif (a->pack->mtime < b->pack->mtime)\n-\t\treturn 1;\n-\telse if (a->pack->mtime == b->pack->mtime)\n-\t\treturn 0;\n-\treturn -1;\n-}\n-\n-void packfile_store_prepare(struct odb_source_packed *store)\n-{\n-\tif (store->initialized)\n-\t\treturn;\n-\n-\tprepare_multi_pack_index_one(&store->files->base);\n-\tprepare_packed_git_one(&store->files->base);\n-\n-\tsort_packs(&store->packs.head, sort_pack);\n-\tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n-\t\tif (!e->next)\n-\t\t\tstore->packs.tail = e;\n-\n-\tstore->initialized = true;\n-}\n-\n-void packfile_store_reprepare(struct odb_source_packed *store)\n-{\n-\tstore->initialized = false;\n-\tpackfile_store_prepare(store);\n-}\n-\n struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store)\n {\n-\tpackfile_store_prepare(store);\n+\todb_source_packed_prepare(store);\n \n \tif (store->midx) {\n \t\tstruct multi_pack_index *m = store->midx;\n@@ -2166,7 +2012,7 @@ static int find_pack_entry(struct odb_source_packed *store,\n {\n \tstruct packfile_list_entry *l;\n \n-\tpackfile_store_prepare(store);\n+\todb_source_packed_prepare(store);\n \tif (store->midx && fill_midx_entry(store->midx, oid, e))\n \t\treturn 1;\n \n@@ -2213,7 +2059,7 @@ int packfile_store_read_object_info(struct odb_source_packed *store,\n \t * been added since the last time we have prepared the packfile store.\n \t */\n \tif (flags & OBJECT_INFO_SECOND_READ)\n-\t\tpackfile_store_reprepare(store);\n+\t\todb_source_reprepare(&store->base);\n \n \tif (!find_pack_entry(store, oid, &e))\n \t\treturn 1;\ndiff --git a/packfile.h b/packfile.h\nindex 23af47f0c1..1bce805f74 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -67,23 +67,6 @@ void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n \t\t\t\t\t  const struct object_id *oid);\n \n-/*\n- * Prepare the packfile store by loading packfiles and multi-pack indices for\n- * all alternates. This becomes a no-op if the store is already prepared.\n- *\n- * It shouldn't typically be necessary to call this function directly, as\n- * functions that access the store know to prepare it.\n- */\n-void packfile_store_prepare(struct odb_source_packed *store);\n-\n-/*\n- * Clear the packfile caches and try to look up any new packfiles that have\n- * appeared since last preparing the packfiles store.\n- *\n- * This function must be called under the `odb_read_lock()`.\n- */\n-void packfile_store_reprepare(struct odb_source_packed *store);\n-\n /*\n  * Add the pack to the store so that contained objects become accessible via\n  * the store. This moves ownership into the store.\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544732","messageId":"20260604-pks-odb-source-packed-v1-7-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 07/16] packfile: use higher-level interface to implement `has_object_pack()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:34Z","receivedAt":"2026-06-04T11:25:58Z","isPatch":true,"body":"In `has_object_pack()` we're checking whether a specific object exists\nas part of a packfile. This is done by calling the low-level function\n`find_pack_entry()`, but this function will eventually be moved into\n\"odb/source-packed.c\" and made file-local.\n\nRefactor the code to use `packfile_store_read_object_info()` instead.\nThis refactoring is functionally equivalent as that function will call\n`find_pack_entry()` itself and then return immediately when it ain't got\nno object info pointer as parameter.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 6 ++----\n 1 file changed, 2 insertions(+), 4 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex 902b7f70f2..3ee71d7f71 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2132,14 +2132,12 @@ struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed\n int has_object_pack(struct repository *r, const struct object_id *oid)\n {\n \tstruct odb_source *source;\n-\tstruct pack_entry e;\n \n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n \t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tint ret = find_pack_entry(files->packed, oid, &e);\n-\t\tif (ret)\n-\t\t\treturn ret;\n+\t\tif (!packfile_store_read_object_info(files->packed, oid, NULL, 0))\n+\t\t\treturn 1;\n \t}\n \n \treturn 0;\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544733","messageId":"20260604-pks-odb-source-packed-v1-8-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 08/16] odb/source-packed: wire up `read_object_info()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:35Z","receivedAt":"2026-06-04T11:26:00Z","isPatch":true,"body":"Move the logic to read object info from a \"packed\" source into\n\"odb/source-packed.c\" and wire it up as the `read_object_info()`\ncallback.\n\nNote that we also move around the supporting `find_pack_entry()`, but we\nstill have to expose it to other callers that exist in \"packfile.c\".\nThis will be fixed in subsequent commits though, where all callers in\n\"packfile.c\" will have been moved into \"odb/source-packed.c\", and at\nthat point we'll be able to make `find_pack_entry()` file-local again.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 60 +++++++++++++++++++++++++++++++++++++++++++\n odb/source-packed.h |  6 +++++\n packfile.c          | 74 ++++++-----------------------------------------------\n packfile.h          | 15 +++--------\n 5 files changed, 79 insertions(+), 78 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 7b1e0ac565..8cae35d25e 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -55,7 +55,7 @@ static int odb_source_files_read_object_info(struct odb_source *source,\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \n-\tif (!packfile_store_read_object_info(files->packed, oid, oi, flags) ||\n+\tif (!odb_source_read_object_info(&files->packed->base, oid, oi, flags) ||\n \t    !odb_source_read_object_info(&files->loose->base, oid, oi, flags))\n \t\treturn 0;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex e8e2e5bb48..f71a194739 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -7,6 +7,65 @@\n #include \"odb/source-packed.h\"\n #include \"packfile.h\"\n \n+int find_pack_entry(struct odb_source_packed *store,\n+\t\t    const struct object_id *oid,\n+\t\t    struct pack_entry *e)\n+{\n+\tstruct packfile_list_entry *l;\n+\n+\todb_source_packed_prepare(store);\n+\tif (store->midx && fill_midx_entry(store->midx, oid, e))\n+\t\treturn 1;\n+\n+\tfor (l = store->packs.head; l; l = l->next) {\n+\t\tstruct packed_git *p = l->pack;\n+\n+\t\tif (!p->multi_pack_index && packfile_fill_entry(p, oid, e)) {\n+\t\t\tif (!store->skip_mru_updates)\n+\t\t\t\tpackfile_list_prepend(&store->packs, p);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int odb_source_packed_read_object_info(struct odb_source *source,\n+\t\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t\t      struct object_info *oi,\n+\t\t\t\t\t      enum object_info_flags flags)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct pack_entry e;\n+\tint ret;\n+\n+\t/*\n+\t * In case the first read didn't surface the object, we have to reload\n+\t * packfiles. This may cause us to discover new packfiles that have\n+\t * been added since the last time we have prepared the packfile store.\n+\t */\n+\tif (flags & OBJECT_INFO_SECOND_READ)\n+\t\todb_source_reprepare(source);\n+\n+\tif (!find_pack_entry(packed, oid, &e))\n+\t\treturn 1;\n+\n+\t/*\n+\t * We know that the caller doesn't actually need the\n+\t * information below, so return early.\n+\t */\n+\tif (!oi)\n+\t\treturn 0;\n+\n+\tret = packed_object_info(e.p, e.offset, oi);\n+\tif (ret < 0) {\n+\t\tmark_bad_packed_object(e.p, oid);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -215,6 +274,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.free = odb_source_packed_free;\n \tpacked->base.close = odb_source_packed_close;\n \tpacked->base.reprepare = odb_source_packed_reprepare;\n+\tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 6645f4f943..aa2e80281b 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -98,4 +98,10 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so\n  */\n void odb_source_packed_prepare(struct odb_source_packed *source);\n \n+struct pack_entry;\n+\n+int find_pack_entry(struct odb_source_packed *store,\n+\t\t    const struct object_id *oid,\n+\t\t    struct pack_entry *e);\n+\n #endif\ndiff --git a/packfile.c b/packfile.c\nindex 3ee71d7f71..565186e9c7 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1978,9 +1978,9 @@ int is_pack_valid(struct packed_git *p)\n \treturn !open_packed_git(p);\n }\n \n-static int fill_pack_entry(const struct object_id *oid,\n-\t\t\t   struct pack_entry *e,\n-\t\t\t   struct packed_git *p)\n+int packfile_fill_entry(struct packed_git *p,\n+\t\t\tconst struct object_id *oid,\n+\t\t\tstruct pack_entry *e)\n {\n \toff_t offset;\n \n@@ -2006,29 +2006,6 @@ static int fill_pack_entry(const struct object_id *oid,\n \treturn 1;\n }\n \n-static int find_pack_entry(struct odb_source_packed *store,\n-\t\t\t   const struct object_id *oid,\n-\t\t\t   struct pack_entry *e)\n-{\n-\tstruct packfile_list_entry *l;\n-\n-\todb_source_packed_prepare(store);\n-\tif (store->midx && fill_midx_entry(store->midx, oid, e))\n-\t\treturn 1;\n-\n-\tfor (l = store->packs.head; l; l = l->next) {\n-\t\tstruct packed_git *p = l->pack;\n-\n-\t\tif (!p->multi_pack_index && fill_pack_entry(oid, e, p)) {\n-\t\t\tif (!store->skip_mru_updates)\n-\t\t\t\tpackfile_list_prepend(&store->packs, p);\n-\t\t\treturn 1;\n-\t\t}\n-\t}\n-\n-\treturn 0;\n-}\n-\n int packfile_store_freshen_object(struct odb_source_packed *store,\n \t\t\t\t  const struct object_id *oid)\n {\n@@ -2045,41 +2022,6 @@ int packfile_store_freshen_object(struct odb_source_packed *store,\n \treturn 1;\n }\n \n-int packfile_store_read_object_info(struct odb_source_packed *store,\n-\t\t\t\t    const struct object_id *oid,\n-\t\t\t\t    struct object_info *oi,\n-\t\t\t\t    enum object_info_flags flags)\n-{\n-\tstruct pack_entry e;\n-\tint ret;\n-\n-\t/*\n-\t * In case the first read didn't surface the object, we have to reload\n-\t * packfiles. This may cause us to discover new packfiles that have\n-\t * been added since the last time we have prepared the packfile store.\n-\t */\n-\tif (flags & OBJECT_INFO_SECOND_READ)\n-\t\todb_source_reprepare(&store->base);\n-\n-\tif (!find_pack_entry(store, oid, &e))\n-\t\treturn 1;\n-\n-\t/*\n-\t * We know that the caller doesn't actually need the\n-\t * information below, so return early.\n-\t */\n-\tif (!oi)\n-\t\treturn 0;\n-\n-\tret = packed_object_info(e.p, e.offset, oi);\n-\tif (ret < 0) {\n-\t\tmark_bad_packed_object(e.p, oid);\n-\t\treturn -1;\n-\t}\n-\n-\treturn 0;\n-}\n-\n static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t     unsigned flags)\n {\n@@ -2136,7 +2078,7 @@ int has_object_pack(struct repository *r, const struct object_id *oid)\n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n \t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tif (!packfile_store_read_object_info(files->packed, oid, NULL, 0))\n+\t\tif (!odb_source_read_object_info(&files->packed->base, oid, NULL, 0))\n \t\t\treturn 1;\n \t}\n \n@@ -2157,7 +2099,7 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n \t\tfor (; *cache; cache++) {\n \t\t\tstruct packed_git *p = *cache;\n-\t\t\tif (fill_pack_entry(oid, &e, p))\n+\t\t\tif (packfile_fill_entry(p, oid, &e))\n \t\t\t\treturn 1;\n \t\t}\n \t}\n@@ -2291,8 +2233,8 @@ static int for_each_prefixed_object_in_midx(\n \t\t\tif (data->request) {\n \t\t\t\tstruct object_info oi = *data->request;\n \n-\t\t\t\tret = packfile_store_read_object_info(store, current,\n-\t\t\t\t\t\t\t\t      &oi, 0);\n+\t\t\t\tret = odb_source_read_object_info(&store->base, current,\n+\t\t\t\t\t\t\t\t  &oi, 0);\n \t\t\t\tif (ret)\n \t\t\t\t\tgoto out;\n \n@@ -2342,7 +2284,7 @@ static int for_each_prefixed_object_in_pack(\n \t\tif (data->request) {\n \t\t\tstruct object_info oi = *data->request;\n \n-\t\t\tret = packfile_store_read_object_info(store, &oid, &oi, 0);\n+\t\t\tret = odb_source_read_object_info(&store->base, &oid, &oi, 0);\n \t\t\tif (ret)\n \t\t\t\tgoto out;\n \ndiff --git a/packfile.h b/packfile.h\nindex 1bce805f74..2bb6656c3b 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -140,17 +140,6 @@ int packfile_store_read_object_stream(struct odb_read_stream **out,\n \t\t\t\t      struct odb_source_packed *store,\n \t\t\t\t      const struct object_id *oid);\n \n-/*\n- * Try to read the object identified by its ID from the object store and\n- * populate the object info with its data. Returns 1 in case the object was\n- * not found, 0 if it was and read successfully, and a negative error code in\n- * case the object was corrupted.\n- */\n-int packfile_store_read_object_info(struct odb_source_packed *store,\n-\t\t\t\t    const struct object_id *oid,\n-\t\t\t\t    struct object_info *oi,\n-\t\t\t\t    enum object_info_flags flags);\n-\n /*\n  * Open the packfile and add it to the store if it isn't yet known. Returns\n  * either the newly opened packfile or the preexisting packfile. Returns a\n@@ -352,6 +341,10 @@ off_t nth_packed_object_offset(const struct packed_git *, uint32_t n);\n  */\n off_t find_pack_entry_one(const struct object_id *oid, struct packed_git *);\n \n+int packfile_fill_entry(struct packed_git *p,\n+\t\t\tconst struct object_id *oid,\n+\t\t\tstruct pack_entry *e);\n+\n int is_pack_valid(struct packed_git *);\n void *unpack_entry(struct repository *r, struct packed_git *, off_t, enum object_type *, unsigned long *);\n unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, size_t *sizep);\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544734","messageId":"20260604-pks-odb-source-packed-v1-9-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 09/16] odb/source-packed: wire up `read_object_stream()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:36Z","receivedAt":"2026-06-04T11:26:03Z","isPatch":true,"body":"Wire up the `read_object_stream()` callback for the packed source and\ncall it in the \"files\" source via the `odb_source_read_object_stream()`\ninterface.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 16 ++++++++++++++++\n packfile.c          | 12 ------------\n packfile.h          |  4 ----\n 4 files changed, 17 insertions(+), 17 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 8cae35d25e..dff69d0e4e 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -67,7 +67,7 @@ static int odb_source_files_read_object_stream(struct odb_read_stream **out,\n \t\t\t\t\t       const struct object_id *oid)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tif (!packfile_store_read_object_stream(out, files->packed, oid) ||\n+\tif (!odb_source_read_object_stream(out, &files->packed->base, oid) ||\n \t    !odb_source_read_object_stream(out, &files->loose->base, oid))\n \t\treturn 0;\n \treturn -1;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex f71a194739..23d7149fe3 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -2,9 +2,11 @@\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n #include \"dir.h\"\n+#include \"git-zlib.h\"\n #include \"mergesort.h\"\n #include \"midx.h\"\n #include \"odb/source-packed.h\"\n+#include \"odb/streaming.h\"\n #include \"packfile.h\"\n \n int find_pack_entry(struct odb_source_packed *store,\n@@ -66,6 +68,19 @@ static int odb_source_packed_read_object_info(struct odb_source *source,\n \treturn 0;\n }\n \n+static int odb_source_packed_read_object_stream(struct odb_read_stream **out,\n+\t\t\t\t\t\tstruct odb_source *source,\n+\t\t\t\t\t\tconst struct object_id *oid)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct pack_entry e;\n+\n+\tif (!find_pack_entry(packed, oid, &e))\n+\t\treturn -1;\n+\n+\treturn packfile_read_object_stream(out, oid, e.p, e.offset);\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -275,6 +290,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.close = odb_source_packed_close;\n \tpacked->base.reprepare = odb_source_packed_reprepare;\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n+\tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 565186e9c7..bcb4b61608 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2741,15 +2741,3 @@ int packfile_read_object_stream(struct odb_read_stream **out,\n \n \treturn 0;\n }\n-\n-int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct odb_source_packed *store,\n-\t\t\t\t      const struct object_id *oid)\n-{\n-\tstruct pack_entry e;\n-\n-\tif (!find_pack_entry(store, oid, &e))\n-\t\treturn -1;\n-\n-\treturn packfile_read_object_stream(out, oid, e.p, e.offset);\n-}\ndiff --git a/packfile.h b/packfile.h\nindex 2bb6656c3b..e5875e1215 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -136,10 +136,6 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *\n \t     ((p) = (eack_pack_data.entry ? eack_pack_data.entry->pack : NULL)); \\\n \t     repo_for_each_pack_data_next(&eack_pack_data))\n \n-int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct odb_source_packed *store,\n-\t\t\t\t      const struct object_id *oid);\n-\n /*\n  * Open the packfile and add it to the store if it isn't yet known. Returns\n  * either the newly opened packfile or the preexisting packfile. Returns a\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544735","messageId":"20260604-pks-odb-source-packed-v1-10-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 10/16] odb/source-packed: wire up `for_each_object()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:37Z","receivedAt":"2026-06-04T11:26:05Z","isPatch":true,"body":"Move `packfile_store_for_each_object()` and its associated helpers from\n\"packfile.c\" into \"odb/source-packed.c\" and wire it up as the\n`for_each_object()` callback of the \"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     |   4 +-\n builtin/pack-objects.c |   4 +-\n commit-graph.c         |   4 +-\n odb/source-files.c     |   2 +-\n odb/source-packed.c    | 258 ++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.c             | 260 +------------------------------------------------\n packfile.h             |  17 +---\n 7 files changed, 269 insertions(+), 280 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 04b64006a5..d997011531 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -916,8 +916,8 @@ static void batch_each_object(struct batch_options *opt,\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);\n-\t\t\tint ret = packfile_store_for_each_object(files->packed, &oi,\n-\t\t\t\t\t\t\t\t batch_one_object_oi, &payload, &opts);\n+\t\t\tint ret = odb_source_for_each_object(&files->packed->base, &oi,\n+\t\t\t\t\t\t\t     batch_one_object_oi, &payload, &opts);\n \t\t\tif (ret)\n \t\t\t\tbreak;\n \t\t}\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 50675481e1..5e94805478 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4503,8 +4503,8 @@ static void add_objects_in_unpacked_packs(void)\n \t\tif (!source->local)\n \t\t\tcontinue;\n \n-\t\tif (packfile_store_for_each_object(files->packed, &oi,\n-\t\t\t\t\t\t   add_object_in_unpacked_pack, NULL, &opts))\n+\t\tif (odb_source_for_each_object(&files->packed->base, &oi,\n+\t\t\t\t\t       add_object_in_unpacked_pack, NULL, &opts))\n \t\t\tdie(_(\"cannot open pack index\"));\n \t}\n }\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 9abe62bd5a..1e4038baf3 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -2016,8 +2016,8 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n \todb_prepare_alternates(ctx->r->objects);\n \tfor (source = ctx->r->objects->sources; source; source = source->next) {\n \t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tpackfile_store_for_each_object(files->packed, &oi, add_packed_commits_oi,\n-\t\t\t\t\t       ctx, &opts);\n+\t\todb_source_for_each_object(&files->packed->base, &oi, add_packed_commits_oi,\n+\t\t\t\t\t   ctx, &opts);\n \t}\n \n \tif (ctx->progress_done < ctx->approx_nr_objects)\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex dff69d0e4e..c73a7e5f90 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -88,7 +88,7 @@ static int odb_source_files_for_each_object(struct odb_source *source,\n \t\t\treturn ret;\n \t}\n \n-\tret = packfile_store_for_each_object(files->packed, request, cb, cb_data, opts);\n+\tret = odb_source_for_each_object(&files->packed->base, request, cb, cb_data, opts);\n \tif (ret)\n \t\treturn ret;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 23d7149fe3..a61c809c8c 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -81,6 +81,263 @@ static int odb_source_packed_read_object_stream(struct odb_read_stream **out,\n \treturn packfile_read_object_stream(out, oid, e.p, e.offset);\n }\n \n+struct odb_source_packed_for_each_object_wrapper_data {\n+\tstruct odb_source_packed *store;\n+\tconst struct object_info *request;\n+\todb_for_each_object_cb cb;\n+\tvoid *cb_data;\n+};\n+\n+static int odb_source_packed_for_each_object_wrapper(const struct object_id *oid,\n+\t\t\t\t\t\t     struct packed_git *pack,\n+\t\t\t\t\t\t     uint32_t index_pos,\n+\t\t\t\t\t\t     void *cb_data)\n+{\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data = cb_data;\n+\n+\tif (data->request) {\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\t\t\t\t\t      &index_pos, &oi) < 0) {\n+\t\t\tmark_bad_packed_object(pack, oid);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\treturn data->cb(oid, &oi, data->cb_data);\n+\t} else {\n+\t\treturn data->cb(oid, NULL, data->cb_data);\n+\t}\n+}\n+\n+static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b)\n+{\n+\tdo {\n+\t\tif (*a != *b)\n+\t\t\treturn 0;\n+\t\ta++;\n+\t\tb++;\n+\t\tlen -= 2;\n+\t} while (len > 1);\n+\tif (len)\n+\t\tif ((*a ^ *b) & 0xf0)\n+\t\t\treturn 0;\n+\treturn 1;\n+}\n+\n+static int for_each_prefixed_object_in_midx(\n+\tstruct odb_source_packed *store,\n+\tstruct multi_pack_index *m,\n+\tconst struct odb_for_each_object_options *opts,\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n+{\n+\tint ret;\n+\n+\tfor (; m; m = m->base_midx) {\n+\t\tuint32_t num, i, first = 0;\n+\t\tint len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ?\n+\t\t\tm->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n+\n+\t\tif (!m->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tnum = m->num_objects + m->num_objects_in_base;\n+\n+\t\tbsearch_one_midx(opts->prefix, m, &first);\n+\n+\t\t/*\n+\t\t * At this point, \"first\" is the location of the lowest\n+\t\t * object with an object name that could match \"opts->prefix\".\n+\t\t * See if we have 0, 1 or more objects that actually match(es).\n+\t\t */\n+\t\tfor (i = first; i < num; i++) {\n+\t\t\tconst struct object_id *current = NULL;\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tcurrent = nth_midxed_object_oid(&oid, m, i);\n+\n+\t\t\tif (!match_hash(len, opts->prefix->hash, current->hash))\n+\t\t\t\tbreak;\n+\n+\t\t\tif (data->request) {\n+\t\t\t\tstruct object_info oi = *data->request;\n+\n+\t\t\t\tret = odb_source_read_object_info(&store->base, current,\n+\t\t\t\t\t\t\t\t  &oi, 0);\n+\t\t\t\tif (ret)\n+\t\t\t\t\tgoto out;\n+\n+\t\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n+\t\t\t\tif (ret)\n+\t\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n+\t\t\t\tif (ret)\n+\t\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\treturn ret;\n+}\n+\n+static int for_each_prefixed_object_in_pack(\n+\tstruct odb_source_packed *store,\n+\tstruct packed_git *p,\n+\tconst struct odb_for_each_object_options *opts,\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n+{\n+\tuint32_t num, i, first = 0;\n+\tint len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ?\n+\t\tp->repo->hash_algo->hexsz : opts->prefix_hex_len;\n+\tint ret;\n+\n+\tnum = p->num_objects;\n+\tbsearch_pack(opts->prefix, p, &first);\n+\n+\t/*\n+\t * At this point, \"first\" is the location of the lowest object\n+\t * with an object name that could match \"bin_pfx\".  See if we have\n+\t * 0, 1 or more objects that actually match(es).\n+\t */\n+\tfor (i = first; i < num; i++) {\n+\t\tstruct object_id oid;\n+\n+\t\tnth_packed_object_id(&oid, p, i);\n+\t\tif (!match_hash(len, opts->prefix->hash, oid.hash))\n+\t\t\tbreak;\n+\n+\t\tif (data->request) {\n+\t\t\tstruct object_info oi = *data->request;\n+\n+\t\t\tret = odb_source_read_object_info(&store->base, &oid, &oi, 0);\n+\t\t\tif (ret)\n+\t\t\t\tgoto out;\n+\n+\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n+\t\t\tif (ret)\n+\t\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n+\t\t\tif (ret)\n+\t\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\treturn ret;\n+}\n+\n+static int odb_source_packed_for_each_prefixed_object(\n+\tstruct odb_source_packed *store,\n+\tconst struct odb_for_each_object_options *opts,\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n+{\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n+\tbool pack_errors = false;\n+\tint ret;\n+\n+\tif (opts->flags)\n+\t\tBUG(\"flags unsupported\");\n+\n+\tstore->skip_mru_updates = true;\n+\n+\tm = get_multi_pack_index(&store->files->base);\n+\tif (m) {\n+\t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n+\t\tif (e->pack->multi_pack_index)\n+\t\t\tcontinue;\n+\n+\t\tif (open_pack_index(e->pack)) {\n+\t\t\tpack_errors = true;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!e->pack->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tret = for_each_prefixed_object_in_pack(store, e->pack, opts, data);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\tstore->skip_mru_updates = false;\n+\tif (!ret && pack_errors)\n+\t\tret = -1;\n+\treturn ret;\n+}\n+\n+static int odb_source_packed_for_each_object(struct odb_source *source,\n+\t\t\t\t\t     const struct object_info *request,\n+\t\t\t\t\t     odb_for_each_object_cb cb,\n+\t\t\t\t\t     void *cb_data,\n+\t\t\t\t\t     const struct odb_for_each_object_options *opts)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct odb_source_packed_for_each_object_wrapper_data data = {\n+\t\t.store = packed,\n+\t\t.request = request,\n+\t\t.cb = cb,\n+\t\t.cb_data = cb_data,\n+\t};\n+\tstruct packfile_list_entry *e;\n+\tint pack_errors = 0, ret;\n+\n+\tif (opts->prefix)\n+\t\treturn odb_source_packed_for_each_prefixed_object(packed, opts, &data);\n+\n+\tpacked->skip_mru_updates = true;\n+\n+\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n+\t\tstruct packed_git *p = e->pack;\n+\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n+\t\t    p->pack_keep_in_core)\n+\t\t\tcontinue;\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n+\t\t    p->pack_keep)\n+\t\t\tcontinue;\n+\t\tif (open_pack_index(p)) {\n+\t\t\tpack_errors = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tret = for_each_object_in_pack(p, odb_source_packed_for_each_object_wrapper,\n+\t\t\t\t\t      &data, opts->flags);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\tpacked->skip_mru_updates = false;\n+\n+\tif (!ret && pack_errors)\n+\t\tret = -1;\n+\treturn ret;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -291,6 +548,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.reprepare = odb_source_packed_reprepare;\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n+\tpacked->base.for_each_object = odb_source_packed_for_each_object;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex bcb4b61608..95136b050b 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1445,8 +1445,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-static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n-\t\t\t\t\t     uint32_t *maybe_index_pos, struct object_info *oi)\n+int packed_object_info_with_index_pos(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 \tsize_t size;\n@@ -2151,262 +2151,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-struct odb_source_packed_for_each_object_wrapper_data {\n-\tstruct odb_source_packed *store;\n-\tconst struct object_info *request;\n-\todb_for_each_object_cb cb;\n-\tvoid *cb_data;\n-};\n-\n-static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n-\t\t\t\t\t\t  struct packed_git *pack,\n-\t\t\t\t\t\t  uint32_t index_pos,\n-\t\t\t\t\t\t  void *cb_data)\n-{\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data = cb_data;\n-\n-\tif (data->request) {\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\t\t\t\t\t      &index_pos, &oi) < 0) {\n-\t\t\tmark_bad_packed_object(pack, oid);\n-\t\t\treturn -1;\n-\t\t}\n-\n-\t\treturn data->cb(oid, &oi, data->cb_data);\n-\t} else {\n-\t\treturn data->cb(oid, NULL, data->cb_data);\n-\t}\n-}\n-\n-static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b)\n-{\n-\tdo {\n-\t\tif (*a != *b)\n-\t\t\treturn 0;\n-\t\ta++;\n-\t\tb++;\n-\t\tlen -= 2;\n-\t} while (len > 1);\n-\tif (len)\n-\t\tif ((*a ^ *b) & 0xf0)\n-\t\t\treturn 0;\n-\treturn 1;\n-}\n-\n-static int for_each_prefixed_object_in_midx(\n-\tstruct odb_source_packed *store,\n-\tstruct multi_pack_index *m,\n-\tconst struct odb_for_each_object_options *opts,\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n-{\n-\tint ret;\n-\n-\tfor (; m; m = m->base_midx) {\n-\t\tuint32_t num, i, first = 0;\n-\t\tint len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ?\n-\t\t\tm->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n-\n-\t\tif (!m->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tnum = m->num_objects + m->num_objects_in_base;\n-\n-\t\tbsearch_one_midx(opts->prefix, m, &first);\n-\n-\t\t/*\n-\t\t * At this point, \"first\" is the location of the lowest\n-\t\t * object with an object name that could match \"opts->prefix\".\n-\t\t * See if we have 0, 1 or more objects that actually match(es).\n-\t\t */\n-\t\tfor (i = first; i < num; i++) {\n-\t\t\tconst struct object_id *current = NULL;\n-\t\t\tstruct object_id oid;\n-\n-\t\t\tcurrent = nth_midxed_object_oid(&oid, m, i);\n-\n-\t\t\tif (!match_hash(len, opts->prefix->hash, current->hash))\n-\t\t\t\tbreak;\n-\n-\t\t\tif (data->request) {\n-\t\t\t\tstruct object_info oi = *data->request;\n-\n-\t\t\t\tret = odb_source_read_object_info(&store->base, current,\n-\t\t\t\t\t\t\t\t  &oi, 0);\n-\t\t\t\tif (ret)\n-\t\t\t\t\tgoto out;\n-\n-\t\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n-\t\t\t\tif (ret)\n-\t\t\t\t\tgoto out;\n-\t\t\t} else {\n-\t\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n-\t\t\t\tif (ret)\n-\t\t\t\t\tgoto out;\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\treturn ret;\n-}\n-\n-static int for_each_prefixed_object_in_pack(\n-\tstruct odb_source_packed *store,\n-\tstruct packed_git *p,\n-\tconst struct odb_for_each_object_options *opts,\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n-{\n-\tuint32_t num, i, first = 0;\n-\tint len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ?\n-\t\tp->repo->hash_algo->hexsz : opts->prefix_hex_len;\n-\tint ret;\n-\n-\tnum = p->num_objects;\n-\tbsearch_pack(opts->prefix, p, &first);\n-\n-\t/*\n-\t * At this point, \"first\" is the location of the lowest object\n-\t * with an object name that could match \"bin_pfx\".  See if we have\n-\t * 0, 1 or more objects that actually match(es).\n-\t */\n-\tfor (i = first; i < num; i++) {\n-\t\tstruct object_id oid;\n-\n-\t\tnth_packed_object_id(&oid, p, i);\n-\t\tif (!match_hash(len, opts->prefix->hash, oid.hash))\n-\t\t\tbreak;\n-\n-\t\tif (data->request) {\n-\t\t\tstruct object_info oi = *data->request;\n-\n-\t\t\tret = odb_source_read_object_info(&store->base, &oid, &oi, 0);\n-\t\t\tif (ret)\n-\t\t\t\tgoto out;\n-\n-\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n-\t\t\tif (ret)\n-\t\t\t\tgoto out;\n-\t\t} else {\n-\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n-\t\t\tif (ret)\n-\t\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\treturn ret;\n-}\n-\n-static int packfile_store_for_each_prefixed_object(\n-\tstruct odb_source_packed *store,\n-\tconst struct odb_for_each_object_options *opts,\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n-{\n-\tstruct packfile_list_entry *e;\n-\tstruct multi_pack_index *m;\n-\tbool pack_errors = false;\n-\tint ret;\n-\n-\tif (opts->flags)\n-\t\tBUG(\"flags unsupported\");\n-\n-\tstore->skip_mru_updates = true;\n-\n-\tm = get_multi_pack_index(&store->files->base);\n-\tif (m) {\n-\t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n-\t\tif (ret)\n-\t\t\tgoto out;\n-\t}\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tif (e->pack->multi_pack_index)\n-\t\t\tcontinue;\n-\n-\t\tif (open_pack_index(e->pack)) {\n-\t\t\tpack_errors = true;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tif (!e->pack->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tret = for_each_prefixed_object_in_pack(store, e->pack, opts, data);\n-\t\tif (ret)\n-\t\t\tgoto out;\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tstore->skip_mru_updates = false;\n-\tif (!ret && pack_errors)\n-\t\tret = -1;\n-\treturn ret;\n-}\n-\n-int packfile_store_for_each_object(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_info *request,\n-\t\t\t\t   odb_for_each_object_cb cb,\n-\t\t\t\t   void *cb_data,\n-\t\t\t\t   const struct odb_for_each_object_options *opts)\n-{\n-\tstruct odb_source_packed_for_each_object_wrapper_data data = {\n-\t\t.store = store,\n-\t\t.request = request,\n-\t\t.cb = cb,\n-\t\t.cb_data = cb_data,\n-\t};\n-\tstruct packfile_list_entry *e;\n-\tint pack_errors = 0, ret;\n-\n-\tif (opts->prefix)\n-\t\treturn packfile_store_for_each_prefixed_object(store, opts, &data);\n-\n-\tstore->skip_mru_updates = true;\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tstruct packed_git *p = e->pack;\n-\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\tcontinue;\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t    !p->pack_promisor)\n-\t\t\tcontinue;\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n-\t\t    p->pack_keep_in_core)\n-\t\t\tcontinue;\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n-\t\t    p->pack_keep)\n-\t\t\tcontinue;\n-\t\tif (open_pack_index(p)) {\n-\t\t\tpack_errors = 1;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tret = for_each_object_in_pack(p, packfile_store_for_each_object_wrapper,\n-\t\t\t\t\t      &data, opts->flags);\n-\t\tif (ret)\n-\t\t\tgoto out;\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tstore->skip_mru_updates = false;\n-\n-\tif (!ret && pack_errors)\n-\t\tret = -1;\n-\treturn ret;\n-}\n-\n static int extend_abbrev_len(const struct object_id *a,\n \t\t\t     const struct object_id *b,\n \t\t\t     unsigned *out)\ndiff --git a/packfile.h b/packfile.h\nindex e5875e1215..210650923f 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -239,21 +239,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn, void *data,\n \t\t\t    enum odb_for_each_object_flags flags);\n \n-/*\n- * Iterate through all packed objects in the given packfile store and invoke\n- * the callback function for each of them. If an object info request is given,\n- * then the object info will be read for every individual object and passed to\n- * the callback as if `packfile_store_read_object_info()` was called for the\n- * object.\n- *\n- * The flags parameter is a combination of `odb_for_each_object_flags`.\n- */\n-int packfile_store_for_each_object(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_info *request,\n-\t\t\t\t   odb_for_each_object_cb cb,\n-\t\t\t\t   void *cb_data,\n-\t\t\t\t   const struct odb_for_each_object_options *opts);\n-\n int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \t\t\t\t   const struct object_id *oid,\n \t\t\t\t   unsigned min_len,\n@@ -366,6 +351,8 @@ extern int do_check_packed_object_crc;\n  */\n int packed_object_info(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+\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 *);\n const struct packed_git *has_packed_and_bad(struct repository *, const struct object_id *);\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544736","messageId":"20260604-pks-odb-source-packed-v1-11-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 11/16] odb/source-packed: wire up `count_objects()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:38Z","receivedAt":"2026-06-04T11:26:08Z","isPatch":true,"body":"Move `packfile_store_count_objects()` from \"packfile.c\" into\n\"odb/source-packed.c\" and wire it up as the `count_objects()` callback\nof the \"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 34 ++++++++++++++++++++++++++++++++++\n packfile.c          | 31 -------------------------------\n packfile.h          | 10 ----------\n 4 files changed, 35 insertions(+), 42 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex c73a7e5f90..274923e0ba 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -103,7 +103,7 @@ static int odb_source_files_count_objects(struct odb_source *source,\n \tunsigned long count;\n \tint ret;\n \n-\tret = packfile_store_count_objects(files->packed, flags, &count);\n+\tret = odb_source_count_objects(&files->packed->base, flags, &count);\n \tif (ret < 0)\n \t\tgoto out;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex a61c809c8c..013d8a50f8 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -338,6 +338,39 @@ static int odb_source_packed_for_each_object(struct odb_source *source,\n \treturn ret;\n }\n \n+static int odb_source_packed_count_objects(struct odb_source *source,\n+\t\t\t\t\t   enum odb_count_objects_flags flags UNUSED,\n+\t\t\t\t\t   unsigned long *out)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n+\tunsigned long count = 0;\n+\tint ret;\n+\n+\tm = get_multi_pack_index(&packed->files->base);\n+\tif (m)\n+\t\tcount += m->num_objects + m->num_objects_in_base;\n+\n+\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n+\t\tif (e->pack->multi_pack_index)\n+\t\t\tcontinue;\n+\t\tif (open_pack_index(e->pack)) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tcount += e->pack->num_objects;\n+\t}\n+\n+\t*out = count;\n+\tret = 0;\n+\n+out:\n+\treturn ret;\n+}\n+\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -549,6 +582,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n+\tpacked->base.count_objects = odb_source_packed_count_objects;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 95136b050b..5cb14b28de 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -949,37 +949,6 @@ struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *s\n \treturn store->packs.head;\n }\n \n-int packfile_store_count_objects(struct odb_source_packed *store,\n-\t\t\t\t enum odb_count_objects_flags flags UNUSED,\n-\t\t\t\t unsigned long *out)\n-{\n-\tstruct packfile_list_entry *e;\n-\tstruct multi_pack_index *m;\n-\tunsigned long count = 0;\n-\tint ret;\n-\n-\tm = get_multi_pack_index(&store->files->base);\n-\tif (m)\n-\t\tcount += m->num_objects + m->num_objects_in_base;\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tif (e->pack->multi_pack_index)\n-\t\t\tcontinue;\n-\t\tif (open_pack_index(e->pack)) {\n-\t\t\tret = -1;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tcount += e->pack->num_objects;\n-\t}\n-\n-\t*out = count;\n-\tret = 0;\n-\n-out:\n-\treturn ret;\n-}\n-\n unsigned long unpack_object_header_buffer(const unsigned char *buf,\n \t\tunsigned long len, enum object_type *type, size_t *sizep)\n {\ndiff --git a/packfile.h b/packfile.h\nindex 210650923f..9f5b5c145a 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -153,16 +153,6 @@ enum kept_pack_type {\n \tKEPT_PACK_IN_CORE_OPEN = (1 << 2),\n };\n \n-/*\n- * Count the number objects contained in the given packfile store. If\n- * successful, the number of objects will be written to the `out` pointer.\n- *\n- * Return 0 on success, a negative error code otherwise.\n- */\n-int packfile_store_count_objects(struct odb_source_packed *store,\n-\t\t\t\t enum odb_count_objects_flags flags,\n-\t\t\t\t unsigned long *out);\n-\n /*\n  * Retrieve the cache of kept packs from the given packfile store. Accepts a\n  * combination of `kept_pack_type` flags. The cache is computed on demand and\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544737","messageId":"20260604-pks-odb-source-packed-v1-12-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 12/16] odb/source-packed: wire up `find_abbrev_len()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:39Z","receivedAt":"2026-06-04T11:26:10Z","isPatch":true,"body":"Move `packfile_store_find_abbrev_len()` and its associated helpers from\n\"packfile.c\" into \"odb/source-packed.c\" and wire it up as the\n`find_abbrev_len()` callback of the \"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |   2 +-\n odb/source-packed.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.c          | 111 ---------------------------------------------------\n packfile.h          |   5 ---\n 4 files changed, 113 insertions(+), 117 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 274923e0ba..8ad782dc7b 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -133,7 +133,7 @@ static int odb_source_files_find_abbrev_len(struct odb_source *source,\n \tunsigned len = min_len;\n \tint ret;\n \n-\tret = packfile_store_find_abbrev_len(files->packed, oid, len, &len);\n+\tret = odb_source_find_abbrev_len(&files->packed->base, oid, len, &len);\n \tif (ret < 0)\n \t\tgoto out;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 013d8a50f8..b801b62023 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -370,6 +370,117 @@ static int odb_source_packed_count_objects(struct odb_source *source,\n \treturn ret;\n }\n \n+static int extend_abbrev_len(const struct object_id *a,\n+\t\t\t     const struct object_id *b,\n+\t\t\t     unsigned *out)\n+{\n+\tunsigned len = oid_common_prefix_hexlen(a, b);\n+\tif (len != hash_algos[a->algo].hexsz && len >= *out)\n+\t\t*out = len + 1;\n+\treturn 0;\n+}\n+\n+static void find_abbrev_len_for_midx(struct multi_pack_index *m,\n+\t\t\t\t     const struct object_id *oid,\n+\t\t\t\t     unsigned min_len,\n+\t\t\t\t     unsigned *out)\n+{\n+\tunsigned len = min_len;\n+\n+\tfor (; m; m = m->base_midx) {\n+\t\tint match = 0;\n+\t\tuint32_t num, first = 0;\n+\t\tstruct object_id found_oid;\n+\n+\t\tif (!m->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tnum = m->num_objects + m->num_objects_in_base;\n+\t\tmatch = bsearch_one_midx(oid, m, &first);\n+\n+\t\t/*\n+\t\t * first is now the position in the packfile where we\n+\t\t * would insert the object ID if it does not exist (or the\n+\t\t * position of the object ID if it does exist). Hence, we\n+\t\t * consider a maximum of two objects nearby for the\n+\t\t * abbreviation length.\n+\t\t */\n+\n+\t\tif (!match) {\n+\t\t\tif (nth_midxed_object_oid(&found_oid, m, first))\n+\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t\t} else if (first < num - 1) {\n+\t\t\tif (nth_midxed_object_oid(&found_oid, m, first + 1))\n+\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t\t}\n+\t\tif (first > 0) {\n+\t\t\tif (nth_midxed_object_oid(&found_oid, m, first - 1))\n+\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t\t}\n+\t}\n+\n+\t*out = len;\n+}\n+\n+static void find_abbrev_len_for_pack(struct packed_git *p,\n+\t\t\t\t     const struct object_id *oid,\n+\t\t\t\t     unsigned min_len,\n+\t\t\t\t     unsigned *out)\n+{\n+\tint match;\n+\tuint32_t num, first = 0;\n+\tstruct object_id found_oid;\n+\tunsigned len = min_len;\n+\n+\tnum = p->num_objects;\n+\tmatch = bsearch_pack(oid, p, &first);\n+\n+\t/*\n+\t * first is now the position in the packfile where we would insert\n+\t * the object ID if it does not exist (or the position of mad->hash if\n+\t * it does exist). Hence, we consider a maximum of two objects\n+\t * nearby for the abbreviation length.\n+\t */\n+\tif (!match) {\n+\t\tif (!nth_packed_object_id(&found_oid, p, first))\n+\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t} else if (first < num - 1) {\n+\t\tif (!nth_packed_object_id(&found_oid, p, first + 1))\n+\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t}\n+\tif (first > 0) {\n+\t\tif (!nth_packed_object_id(&found_oid, p, first - 1))\n+\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t}\n+\n+\t*out = len;\n+}\n+\n+static int odb_source_packed_find_abbrev_len(struct odb_source *source,\n+\t\t\t\t\t     const struct object_id *oid,\n+\t\t\t\t\t     unsigned min_len,\n+\t\t\t\t\t     unsigned *out)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n+\n+\tm = get_multi_pack_index(&packed->files->base);\n+\tif (m)\n+\t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n+\n+\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n+\t\tif (e->pack->multi_pack_index)\n+\t\t\tcontinue;\n+\t\tif (open_pack_index(e->pack) || !e->pack->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tfind_abbrev_len_for_pack(e->pack, oid, min_len, &min_len);\n+\t}\n+\n+\t*out = min_len;\n+\treturn 0;\n+}\n \n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n@@ -583,6 +694,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n \tpacked->base.count_objects = odb_source_packed_count_objects;\n+\tpacked->base.find_abbrev_len = odb_source_packed_find_abbrev_len;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 5cb14b28de..69f6354ed0 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2120,117 +2120,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-static int extend_abbrev_len(const struct object_id *a,\n-\t\t\t     const struct object_id *b,\n-\t\t\t     unsigned *out)\n-{\n-\tunsigned len = oid_common_prefix_hexlen(a, b);\n-\tif (len != hash_algos[a->algo].hexsz && len >= *out)\n-\t\t*out = len + 1;\n-\treturn 0;\n-}\n-\n-static void find_abbrev_len_for_midx(struct multi_pack_index *m,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     unsigned min_len,\n-\t\t\t\t     unsigned *out)\n-{\n-\tunsigned len = min_len;\n-\n-\tfor (; m; m = m->base_midx) {\n-\t\tint match = 0;\n-\t\tuint32_t num, first = 0;\n-\t\tstruct object_id found_oid;\n-\n-\t\tif (!m->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tnum = m->num_objects + m->num_objects_in_base;\n-\t\tmatch = bsearch_one_midx(oid, m, &first);\n-\n-\t\t/*\n-\t\t * first is now the position in the packfile where we\n-\t\t * would insert the object ID if it does not exist (or the\n-\t\t * position of the object ID if it does exist). Hence, we\n-\t\t * consider a maximum of two objects nearby for the\n-\t\t * abbreviation length.\n-\t\t */\n-\n-\t\tif (!match) {\n-\t\t\tif (nth_midxed_object_oid(&found_oid, m, first))\n-\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t\t} else if (first < num - 1) {\n-\t\t\tif (nth_midxed_object_oid(&found_oid, m, first + 1))\n-\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t\t}\n-\t\tif (first > 0) {\n-\t\t\tif (nth_midxed_object_oid(&found_oid, m, first - 1))\n-\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t\t}\n-\t}\n-\n-\t*out = len;\n-}\n-\n-static void find_abbrev_len_for_pack(struct packed_git *p,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     unsigned min_len,\n-\t\t\t\t     unsigned *out)\n-{\n-\tint match;\n-\tuint32_t num, first = 0;\n-\tstruct object_id found_oid;\n-\tunsigned len = min_len;\n-\n-\tnum = p->num_objects;\n-\tmatch = bsearch_pack(oid, p, &first);\n-\n-\t/*\n-\t * first is now the position in the packfile where we would insert\n-\t * the object ID if it does not exist (or the position of mad->hash if\n-\t * it does exist). Hence, we consider a maximum of two objects\n-\t * nearby for the abbreviation length.\n-\t */\n-\tif (!match) {\n-\t\tif (!nth_packed_object_id(&found_oid, p, first))\n-\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t} else if (first < num - 1) {\n-\t\tif (!nth_packed_object_id(&found_oid, p, first + 1))\n-\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t}\n-\tif (first > 0) {\n-\t\tif (!nth_packed_object_id(&found_oid, p, first - 1))\n-\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t}\n-\n-\t*out = len;\n-}\n-\n-int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t   unsigned min_len,\n-\t\t\t\t   unsigned *out)\n-{\n-\tstruct packfile_list_entry *e;\n-\tstruct multi_pack_index *m;\n-\n-\tm = get_multi_pack_index(&store->files->base);\n-\tif (m)\n-\t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tif (e->pack->multi_pack_index)\n-\t\t\tcontinue;\n-\t\tif (open_pack_index(e->pack) || !e->pack->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tfind_abbrev_len_for_pack(e->pack, oid, min_len, &min_len);\n-\t}\n-\n-\t*out = min_len;\n-\treturn 0;\n-}\n-\n struct add_promisor_object_data {\n \tstruct repository *repo;\n \tstruct oidset *set;\ndiff --git a/packfile.h b/packfile.h\nindex 9f5b5c145a..8d23bec644 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -229,11 +229,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn, void *data,\n \t\t\t    enum odb_for_each_object_flags flags);\n \n-int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t   unsigned min_len,\n-\t\t\t\t   unsigned *out);\n-\n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\n #define PACKDIR_FILE_IDX 2\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544738","messageId":"20260604-pks-odb-source-packed-v1-13-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 13/16] odb/source-packed: wire up `freshen_object()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:40Z","receivedAt":"2026-06-04T11:26:12Z","isPatch":true,"body":"Move `packfile_store_freshen_object()` and from \"packfile.c\" into\n\"odb/source-packed.c\" and wire it up as the `freshen_object()` callback\nof the \"packed\" source.\n\nNote that this removes the last external caller of `find_pack_entry()`\nfrom \"packfile.c\", which means that we can now make this function\nstatic.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 26 +++++++++++++++++++++++---\n odb/source-packed.h |  6 ------\n packfile.c          | 16 ----------------\n packfile.h          |  3 ---\n 5 files changed, 24 insertions(+), 29 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 8ad782dc7b..fa2e18e71b 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -152,7 +152,7 @@ static int odb_source_files_freshen_object(struct odb_source *source,\n \t\t\t\t\t   const struct object_id *oid)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tif (packfile_store_freshen_object(files->packed, oid) ||\n+\tif (odb_source_freshen_object(&files->packed->base, oid) ||\n \t    odb_source_freshen_object(&files->loose->base, oid))\n \t\treturn 1;\n \treturn 0;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex b801b62023..e40b52e445 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -9,9 +9,9 @@\n #include \"odb/streaming.h\"\n #include \"packfile.h\"\n \n-int find_pack_entry(struct odb_source_packed *store,\n-\t\t    const struct object_id *oid,\n-\t\t    struct pack_entry *e)\n+static int find_pack_entry(struct odb_source_packed *store,\n+\t\t\t   const struct object_id *oid,\n+\t\t\t   struct pack_entry *e)\n {\n \tstruct packfile_list_entry *l;\n \n@@ -482,6 +482,25 @@ static int odb_source_packed_find_abbrev_len(struct odb_source *source,\n \treturn 0;\n }\n \n+static int odb_source_packed_freshen_object(struct odb_source *source,\n+\t\t\t\t\t    const struct object_id *oid)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct pack_entry e;\n+\n+\tif (!find_pack_entry(packed, oid, &e))\n+\t\treturn 0;\n+\tif (e.p->is_cruft)\n+\t\treturn 0;\n+\tif (e.p->freshened)\n+\t\treturn 1;\n+\tif (utime(e.p->pack_name, NULL))\n+\t\treturn 0;\n+\te.p->freshened = 1;\n+\n+\treturn 1;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -695,6 +714,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n \tpacked->base.count_objects = odb_source_packed_count_objects;\n \tpacked->base.find_abbrev_len = odb_source_packed_find_abbrev_len;\n+\tpacked->base.freshen_object = odb_source_packed_freshen_object;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex aa2e80281b..6645f4f943 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -98,10 +98,4 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so\n  */\n void odb_source_packed_prepare(struct odb_source_packed *source);\n \n-struct pack_entry;\n-\n-int find_pack_entry(struct odb_source_packed *store,\n-\t\t    const struct object_id *oid,\n-\t\t    struct pack_entry *e);\n-\n #endif\ndiff --git a/packfile.c b/packfile.c\nindex 69f6354ed0..d7de0412ff 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1975,22 +1975,6 @@ int packfile_fill_entry(struct packed_git *p,\n \treturn 1;\n }\n \n-int packfile_store_freshen_object(struct odb_source_packed *store,\n-\t\t\t\t  const struct object_id *oid)\n-{\n-\tstruct pack_entry e;\n-\tif (!find_pack_entry(store, oid, &e))\n-\t\treturn 0;\n-\tif (e.p->is_cruft)\n-\t\treturn 0;\n-\tif (e.p->freshened)\n-\t\treturn 1;\n-\tif (utime(e.p->pack_name, NULL))\n-\t\treturn 0;\n-\te.p->freshened = 1;\n-\treturn 1;\n-}\n-\n static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t     unsigned flags)\n {\ndiff --git a/packfile.h b/packfile.h\nindex 8d23bec644..ed49ab7f21 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -144,9 +144,6 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *\n struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \t\t\t\t\t    const char *idx_path, int local);\n \n-int packfile_store_freshen_object(struct odb_source_packed *store,\n-\t\t\t\t  const struct object_id *oid);\n-\n enum kept_pack_type {\n \tKEPT_PACK_ON_DISK = (1 << 0),\n \tKEPT_PACK_IN_CORE = (1 << 1),\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544739","messageId":"20260604-pks-odb-source-packed-v1-14-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 14/16] odb/source-packed: stub out remaining functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:41Z","receivedAt":"2026-06-04T11:26:15Z","isPatch":true,"body":"Stub out remaining functions that we either don't need or that are\nbasically no-ops.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-packed.c | 42 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 42 insertions(+)\n\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex e40b52e445..08a2de9bc5 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -501,6 +501,43 @@ static int odb_source_packed_freshen_object(struct odb_source *source,\n \treturn 1;\n }\n \n+static int odb_source_packed_write_object(struct odb_source *source UNUSED,\n+\t\t\t\t\t  const void *buf UNUSED,\n+\t\t\t\t\t  unsigned long len UNUSED,\n+\t\t\t\t\t  enum object_type type UNUSED,\n+\t\t\t\t\t  struct object_id *oid UNUSED,\n+\t\t\t\t\t  struct object_id *compat_oid UNUSED,\n+\t\t\t\t\t  unsigned flags UNUSED)\n+{\n+\treturn error(\"packed backend cannot write objects\");\n+}\n+\n+static int odb_source_packed_write_object_stream(struct odb_source *source UNUSED,\n+\t\t\t\t\t\t struct odb_write_stream *stream UNUSED,\n+\t\t\t\t\t\t size_t len UNUSED,\n+\t\t\t\t\t\t struct object_id *oid UNUSED)\n+{\n+\treturn error(\"packed backend cannot write object streams\");\n+}\n+\n+static int odb_source_packed_begin_transaction(struct odb_source *source UNUSED,\n+\t\t\t\t\t       struct odb_transaction **out UNUSED)\n+{\n+\treturn error(\"packed backend cannot begin transactions\");\n+}\n+\n+static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,\n+\t\t\t\t\t     struct strvec *out UNUSED)\n+{\n+\treturn 0;\n+}\n+\n+static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,\n+\t\t\t\t\t     const char *alternate UNUSED)\n+{\n+\treturn error(\"packed backend cannot write alternates\");\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -715,6 +752,11 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.count_objects = odb_source_packed_count_objects;\n \tpacked->base.find_abbrev_len = odb_source_packed_find_abbrev_len;\n \tpacked->base.freshen_object = odb_source_packed_freshen_object;\n+\tpacked->base.write_object = odb_source_packed_write_object;\n+\tpacked->base.write_object_stream = odb_source_packed_write_object_stream;\n+\tpacked->base.begin_transaction = odb_source_packed_begin_transaction;\n+\tpacked->base.read_alternates = odb_source_packed_read_alternates;\n+\tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544740","messageId":"20260604-pks-odb-source-packed-v1-15-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 15/16] midx: refactor interfaces to work on \"packed\" source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:42Z","receivedAt":"2026-06-04T11:26:18Z","isPatch":true,"body":"Our interfaces used to interact with MIDXs all work on top of the\ngeneric `struct odb_source`. This doesn't make much sense though: a MIDX\nis strictly tied to the \"packed\" source, so passing in a generic source\ngives the false sense that it may also work with a different type of\nsource.\n\nFix this conceptual weirdness and instead require the caller to pass in\na \"packed\" source explicitly. This also makes the next commit easier to\nimplement, where we drop the pointer to the \"files\" source in the\n\"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/multi-pack-index.c |  29 +++++------\n builtin/pack-objects.c     |   3 +-\n builtin/repack.c           |   8 ++-\n midx-write.c               |  34 ++++++-------\n midx.c                     | 118 ++++++++++++++++++++++-----------------------\n midx.h                     |  30 ++++++------\n odb/source-packed.c        |  12 ++---\n pack-bitmap.c              |   8 +--\n pack-revindex.c            |   6 +--\n repack-geometry.c          |   3 +-\n repack-midx.c              |   9 ++--\n repack.c                   |   6 +--\n t/helper/test-read-midx.c  |   7 ++-\n 13 files changed, 144 insertions(+), 129 deletions(-)\n\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 00ffb36394..6e73c85cde 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -10,6 +10,7 @@\n #include \"trace2.h\"\n #include \"odb.h\"\n #include \"odb/source.h\"\n+#include \"odb/source-files.h\"\n #include \"replace-object.h\"\n #include \"repository.h\"\n \n@@ -85,12 +86,12 @@ static int parse_object_dir(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n-static struct odb_source *handle_object_dir_option(struct repository *repo)\n+static struct odb_source_files *handle_object_dir_option(struct repository *repo)\n {\n \tstruct odb_source *source = odb_find_source(repo->objects, opts.object_dir);\n \tif (!source)\n \t\tsource = odb_add_to_alternates_memory(repo->objects, opts.object_dir);\n-\treturn source;\n+\treturn odb_source_files_downcast(source);\n }\n \n static struct option common_opts[] = {\n@@ -167,7 +168,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \t\t\t     N_(\"refs snapshot for selecting bitmap commits\")),\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \tint ret;\n \n \topts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;\n@@ -211,7 +212,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \n \t\tread_packs_from_stdin(&packs);\n \n-\t\tret = write_midx_file_only(source, &packs,\n+\t\tret = write_midx_file_only(source->packed, &packs,\n \t\t\t\t\t   opts.preferred_pack,\n \t\t\t\t\t   opts.refs_snapshot,\n \t\t\t\t\t   opts.incremental_base, opts.flags);\n@@ -223,7 +224,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \n \t}\n \n-\tret = write_midx_file(source, opts.preferred_pack,\n+\tret = write_midx_file(source->packed, opts.preferred_pack,\n \t\t\t      opts.refs_snapshot, opts.flags);\n \n \tfree(opts.refs_snapshot);\n@@ -237,7 +238,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,\n \tstruct multi_pack_index *m, *cur;\n \tstruct multi_pack_index *from_midx = NULL;\n \tstruct multi_pack_index *to_midx = NULL;\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \tint ret;\n \n \tstruct option *options;\n@@ -282,7 +283,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\tm = get_multi_pack_index(source);\n+\tm = get_multi_pack_index(source->packed);\n \n \tfor (cur = m; cur && !(from_midx && to_midx); cur = cur->base_midx) {\n \t\tconst char *midx_csum = midx_get_checksum_hex(cur);\n@@ -305,7 +306,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,\n \t\t\tdie(_(\"MIDX %s must be an ancestor of %s\"), argv[0], argv[1]);\n \t}\n \n-\tret = write_midx_file_compact(source, from_midx, to_midx,\n+\tret = write_midx_file_compact(source->packed, from_midx, to_midx,\n \t\t\t\t      opts.incremental_base, opts.flags);\n \n \treturn ret;\n@@ -319,7 +320,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,\n \tstatic struct option builtin_multi_pack_index_verify_options[] = {\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \n \toptions = add_common_options(builtin_multi_pack_index_verify_options);\n \n@@ -337,7 +338,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\treturn verify_midx_file(source, opts.flags);\n+\treturn verify_midx_file(source->packed, opts.flags);\n }\n \n static int cmd_multi_pack_index_expire(int argc, const char **argv,\n@@ -348,7 +349,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,\n \tstatic struct option builtin_multi_pack_index_expire_options[] = {\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \n \toptions = add_common_options(builtin_multi_pack_index_expire_options);\n \n@@ -366,7 +367,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\treturn expire_midx_packs(source, opts.flags);\n+\treturn expire_midx_packs(source->packed, opts.flags);\n }\n \n static int cmd_multi_pack_index_repack(int argc, const char **argv,\n@@ -379,7 +380,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,\n \t\t  N_(\"during repack, collect pack-files of smaller size into a batch that is larger than this size\")),\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \n \toptions = add_common_options(builtin_multi_pack_index_repack_options);\n \n@@ -398,7 +399,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\treturn midx_repack(source, (size_t)opts.batch_size, opts.flags);\n+\treturn midx_repack(source->packed, (size_t)opts.batch_size, opts.flags);\n }\n \n int cmd_multi_pack_index(int argc,\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 5e94805478..424c92cc29 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -1775,7 +1775,8 @@ static int want_object_in_pack_mtime(const struct object_id *oid,\n \todb_prepare_alternates(the_repository->objects);\n \n \tfor (source = the_repository->objects->sources; source; source = source->next) {\n-\t\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \t\tstruct pack_entry e;\n \n \t\tif (m && fill_midx_entry(m, oid, &e)) {\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 1524a9c13a..47966a686b 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -458,6 +458,8 @@ int cmd_repack(int argc,\n \t}\n \n \tif (!names.nr) {\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(existing.source);\n+\n \t\tif (!po_args.quiet)\n \t\t\tprintf_ln(_(\"Nothing new to pack.\"));\n \t\t/*\n@@ -473,7 +475,7 @@ int cmd_repack(int argc,\n \t\t * midx_has_unknown_packs() will make the decision for\n \t\t * us.\n \t\t */\n-\t\tif (!get_multi_pack_index(existing.source))\n+\t\tif (!get_multi_pack_index(files->packed))\n \t\t\tmidx_must_contain_cruft = 1;\n \t}\n \n@@ -626,10 +628,12 @@ int cmd_repack(int argc,\n \t\tupdate_server_info(repo, 0);\n \n \tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) {\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(existing.source);\n \t\tunsigned flags = 0;\n+\n \t\tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0))\n \t\t\tflags |= MIDX_WRITE_INCREMENTAL;\n-\t\twrite_midx_file(existing.source, NULL, NULL, flags);\n+\t\twrite_midx_file(files->packed, NULL, NULL, flags);\n \t}\n \n cleanup:\ndiff --git a/midx-write.c b/midx-write.c\nindex 561e9eedc0..7cafc49fb8 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -25,9 +25,9 @@\n #define NO_PREFERRED_PACK (~((uint32_t)0))\n \n extern int midx_checksum_valid(struct multi_pack_index *m);\n-extern void clear_midx_files_ext(struct odb_source *source, const char *ext,\n+extern void clear_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t\t const char *keep_hash);\n-extern void clear_incremental_midx_files_ext(struct odb_source *source,\n+extern void clear_incremental_midx_files_ext(struct odb_source_packed *source,\n \t\t\t\t\t     const char *ext,\n \t\t\t\t\t     const struct strvec *keep_hashes);\n extern int cmp_idx_or_pack_name(const char *idx_or_pack_name,\n@@ -119,7 +119,7 @@ struct write_midx_context {\n \tstruct string_list *to_include;\n \n \tstruct repository *repo;\n-\tstruct odb_source *source;\n+\tstruct odb_source_packed *source;\n };\n \n static uint32_t midx_pack_perm(struct write_midx_context *ctx,\n@@ -1107,7 +1107,7 @@ static int link_midx_to_chain(struct multi_pack_index *m)\n \treturn ret;\n }\n \n-static void clear_midx_files(struct odb_source *source,\n+static void clear_midx_files(struct odb_source_packed *source,\n \t\t\t     const struct strvec *hashes, unsigned incremental)\n {\n \t/*\n@@ -1237,7 +1237,7 @@ static int midx_hashcmp(const struct multi_pack_index *a,\n }\n \n struct write_midx_opts {\n-\tstruct odb_source *source; /* non-optional */\n+\tstruct odb_source_packed *source; /* non-optional */\n \n \tstruct string_list *packs_to_include;\n \tstruct string_list *packs_to_drop;\n@@ -1253,7 +1253,7 @@ struct write_midx_opts {\n \n static int write_midx_internal(struct write_midx_opts *opts)\n {\n-\tstruct repository *r = opts->source->odb->repo;\n+\tstruct repository *r = opts->source->base.odb->repo;\n \tstruct strbuf midx_name = STRBUF_INIT;\n \tunsigned char midx_hash[GIT_MAX_RAWSZ];\n \tuint32_t start_pack;\n@@ -1301,7 +1301,7 @@ static int write_midx_internal(struct write_midx_opts *opts)\n \tif (ctx.incremental)\n \t\tstrbuf_addf(&midx_name,\n \t\t\t    \"%s/pack/multi-pack-index.d/tmp_midx_XXXXXX\",\n-\t\t\t    opts->source->path);\n+\t\t\t    opts->source->base.path);\n \telse\n \t\tget_midx_filename(opts->source, &midx_name);\n \tif (safe_create_leading_directories(r, midx_name.buf))\n@@ -1396,7 +1396,7 @@ static int write_midx_internal(struct write_midx_opts *opts)\n \t\tfill_packs_from_midx_range(&ctx, bitmap_order);\n \t} else {\n \t\tctx.to_include = opts->packs_to_include;\n-\t\tfor_each_file_in_pack_dir(opts->source->path, add_pack_to_midx, &ctx);\n+\t\tfor_each_file_in_pack_dir(opts->source->base.path, add_pack_to_midx, &ctx);\n \t}\n \tstop_progress(&ctx.progress);\n \n@@ -1847,7 +1847,7 @@ static int write_midx_internal(struct write_midx_opts *opts)\n \treturn result;\n }\n \n-int write_midx_file(struct odb_source *source,\n+int write_midx_file(struct odb_source_packed *source,\n \t\t    const char *preferred_pack_name,\n \t\t    const char *refs_snapshot,\n \t\t    unsigned flags)\n@@ -1862,7 +1862,7 @@ int write_midx_file(struct odb_source *source,\n \treturn write_midx_internal(&opts);\n }\n \n-int write_midx_file_only(struct odb_source *source,\n+int write_midx_file_only(struct odb_source_packed *source,\n \t\t\t struct string_list *packs_to_include,\n \t\t\t const char *preferred_pack_name,\n \t\t\t const char *refs_snapshot,\n@@ -1881,7 +1881,7 @@ int write_midx_file_only(struct odb_source *source,\n \treturn write_midx_internal(&opts);\n }\n \n-int write_midx_file_compact(struct odb_source *source,\n+int write_midx_file_compact(struct odb_source_packed *source,\n \t\t\t    struct multi_pack_index *from,\n \t\t\t    struct multi_pack_index *to,\n \t\t\t    const char *incremental_base,\n@@ -1898,7 +1898,7 @@ int write_midx_file_compact(struct odb_source *source,\n \treturn write_midx_internal(&opts);\n }\n \n-int expire_midx_packs(struct odb_source *source, unsigned flags)\n+int expire_midx_packs(struct odb_source_packed *source, unsigned flags)\n {\n \tuint32_t i, *count, result = 0;\n \tstruct string_list packs_to_drop = STRING_LIST_INIT_DUP;\n@@ -1915,7 +1915,7 @@ int expire_midx_packs(struct odb_source *source, unsigned flags)\n \n \tif (flags & MIDX_PROGRESS)\n \t\tprogress = start_delayed_progress(\n-\t\t\t\t\t  source->odb->repo,\n+\t\t\t\t\t  source->base.odb->repo,\n \t\t\t\t\t  _(\"Counting referenced objects\"),\n \t\t\t\t\t  m->num_objects);\n \tfor (i = 0; i < m->num_objects; i++) {\n@@ -1927,7 +1927,7 @@ int expire_midx_packs(struct odb_source *source, unsigned flags)\n \n \tif (flags & MIDX_PROGRESS)\n \t\tprogress = start_delayed_progress(\n-\t\t\t\t\t  source->odb->repo,\n+\t\t\t\t\t  source->base.odb->repo,\n \t\t\t\t\t  _(\"Finding and deleting unreferenced packfiles\"),\n \t\t\t\t\t  m->num_packs);\n \tfor (i = 0; i < m->num_packs; i++) {\n@@ -2085,9 +2085,9 @@ static void fill_included_packs_batch(struct repository *r,\n \tfree(pack_info);\n }\n \n-int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags)\n+int midx_repack(struct odb_source_packed *source, size_t batch_size, unsigned flags)\n {\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \tint result = 0;\n \tuint32_t i, packs_to_repack = 0;\n \tunsigned char *include_pack;\n@@ -2131,7 +2131,7 @@ int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags)\n \n \tstrvec_push(&cmd.args, \"pack-objects\");\n \n-\tstrvec_pushf(&cmd.args, \"%s/pack/pack\", source->path);\n+\tstrvec_pushf(&cmd.args, \"%s/pack/pack\", source->base.path);\n \n \tif (delta_base_offset)\n \t\tstrvec_push(&cmd.args, \"--delta-base-offset\");\ndiff --git a/midx.c b/midx.c\nindex 00bbd137b2..cc6b94f9dd 100644\n--- a/midx.c\n+++ b/midx.c\n@@ -17,9 +17,9 @@\n #define MIDX_PACK_ERROR ((void *)(intptr_t)-1)\n \n int midx_checksum_valid(struct multi_pack_index *m);\n-void clear_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t  const char *keep_hash);\n-void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t\t      const struct strvec *keep_hashes);\n int cmp_idx_or_pack_name(const char *idx_or_pack_name,\n \t\t\t const char *idx_name);\n@@ -27,25 +27,25 @@ int cmp_idx_or_pack_name(const char *idx_or_pack_name,\n const char *midx_get_checksum_hex(const struct multi_pack_index *m)\n {\n \treturn hash_to_hex_algop(midx_get_checksum_hash(m),\n-\t\t\t\t m->source->odb->repo->hash_algo);\n+\t\t\t\t m->source->base.odb->repo->hash_algo);\n }\n \n const unsigned char *midx_get_checksum_hash(const struct multi_pack_index *m)\n {\n-\treturn m->data + m->data_len - m->source->odb->repo->hash_algo->rawsz;\n+\treturn m->data + m->data_len - m->source->base.odb->repo->hash_algo->rawsz;\n }\n \n-void get_midx_filename(struct odb_source *source, struct strbuf *out)\n+void get_midx_filename(struct odb_source_packed *source, struct strbuf *out)\n {\n \tget_midx_filename_ext(source, out, NULL, NULL);\n }\n \n-void get_midx_filename_ext(struct odb_source *source, struct strbuf *out,\n+void get_midx_filename_ext(struct odb_source_packed *source, struct strbuf *out,\n \t\t\t   const unsigned char *hash, const char *ext)\n {\n-\tstrbuf_addf(out, \"%s/pack/multi-pack-index\", source->path);\n+\tstrbuf_addf(out, \"%s/pack/multi-pack-index\", source->base.path);\n \tif (ext)\n-\t\tstrbuf_addf(out, \"-%s.%s\", hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext);\n+\t\tstrbuf_addf(out, \"-%s.%s\", hash_to_hex_algop(hash, source->base.odb->repo->hash_algo), ext);\n }\n \n static int midx_read_oid_fanout(const unsigned char *chunk_start,\n@@ -99,17 +99,16 @@ static int midx_read_object_offsets(const unsigned char *chunk_start,\n \treturn 0;\n }\n \n-struct multi_pack_index *get_multi_pack_index(struct odb_source *source)\n+struct multi_pack_index *get_multi_pack_index(struct odb_source_packed *source)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\todb_source_packed_prepare(files->packed);\n-\treturn files->packed->midx;\n+\todb_source_packed_prepare(source);\n+\treturn source->midx;\n }\n \n-static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *source,\n+static struct multi_pack_index *load_multi_pack_index_one(struct odb_source_packed *source,\n \t\t\t\t\t\t\t  const char *midx_name)\n {\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \tstruct multi_pack_index *m = NULL;\n \tint fd;\n \tstruct stat st;\n@@ -234,23 +233,23 @@ static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *sou\n \treturn NULL;\n }\n \n-void get_midx_chain_dirname(struct odb_source *source, struct strbuf *buf)\n+void get_midx_chain_dirname(struct odb_source_packed *source, struct strbuf *buf)\n {\n-\tstrbuf_addf(buf, \"%s/pack/multi-pack-index.d\", source->path);\n+\tstrbuf_addf(buf, \"%s/pack/multi-pack-index.d\", source->base.path);\n }\n \n-void get_midx_chain_filename(struct odb_source *source, struct strbuf *buf)\n+void get_midx_chain_filename(struct odb_source_packed *source, struct strbuf *buf)\n {\n \tget_midx_chain_dirname(source, buf);\n \tstrbuf_addstr(buf, \"/multi-pack-index-chain\");\n }\n \n-void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf,\n+void get_split_midx_filename_ext(struct odb_source_packed *source, struct strbuf *buf,\n \t\t\t\t const unsigned char *hash, const char *ext)\n {\n \tget_midx_chain_dirname(source, buf);\n \tstrbuf_addf(buf, \"/multi-pack-index-%s.%s\",\n-\t\t    hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext);\n+\t\t    hash_to_hex_algop(hash, source->base.odb->repo->hash_algo), ext);\n }\n \n static int open_multi_pack_index_chain(const struct git_hash_algo *hash_algo,\n@@ -306,11 +305,11 @@ static int add_midx_to_chain(struct multi_pack_index *midx,\n \treturn 1;\n }\n \n-static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source,\n+static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source_packed *source,\n \t\t\t\t\t\t      int fd, struct stat *st,\n \t\t\t\t\t\t      int *incomplete_chain)\n {\n-\tconst struct git_hash_algo *hash_algo = source->odb->repo->hash_algo;\n+\tconst struct git_hash_algo *hash_algo = source->base.odb->repo->hash_algo;\n \tstruct multi_pack_index *midx_chain = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint valid = 1;\n@@ -362,7 +361,7 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source,\n \treturn midx_chain;\n }\n \n-static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *source)\n+static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source_packed *source)\n {\n \tstruct strbuf chain_file = STRBUF_INIT;\n \tstruct stat st;\n@@ -370,7 +369,8 @@ static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *s\n \tstruct multi_pack_index *m = NULL;\n \n \tget_midx_chain_filename(source, &chain_file);\n-\tif (open_multi_pack_index_chain(source->odb->repo->hash_algo, chain_file.buf, &fd, &st)) {\n+\tif (open_multi_pack_index_chain(source->base.odb->repo->hash_algo,\n+\t\t\t\t\tchain_file.buf, &fd, &st)) {\n \t\tint incomplete;\n \t\t/* ownership of fd is taken over by load function */\n \t\tm = load_midx_chain_fd_st(source, fd, &st, &incomplete);\n@@ -380,7 +380,7 @@ static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *s\n \treturn m;\n }\n \n-struct multi_pack_index *load_multi_pack_index(struct odb_source *source)\n+struct multi_pack_index *load_multi_pack_index(struct odb_source_packed *source)\n {\n \tstruct strbuf midx_name = STRBUF_INIT;\n \tstruct multi_pack_index *m;\n@@ -456,7 +456,7 @@ static uint32_t midx_for_pack(struct multi_pack_index **_m,\n int prepare_midx_pack(struct multi_pack_index *m,\n \t\t      uint32_t pack_int_id)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(m->source);\n+\tstruct odb_source_packed *packed = m->source;\n \tstruct strbuf pack_name = STRBUF_INIT;\n \tstruct packed_git *p;\n \n@@ -467,10 +467,10 @@ int prepare_midx_pack(struct multi_pack_index *m,\n \tif (m->packs[pack_int_id])\n \t\treturn 0;\n \n-\tstrbuf_addf(&pack_name, \"%s/pack/%s\", files->base.path,\n+\tstrbuf_addf(&pack_name, \"%s/pack/%s\", packed->base.path,\n \t\t    m->pack_names[pack_int_id]);\n-\tp = packfile_store_load_pack(files->packed,\n-\t\t\t\t     pack_name.buf, files->base.local);\n+\tp = packfile_store_load_pack(packed,\n+\t\t\t\t     pack_name.buf, packed->base.local);\n \tstrbuf_release(&pack_name);\n \n \tif (!p) {\n@@ -523,7 +523,7 @@ int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m,\n {\n \tint ret = bsearch_hash(oid->hash, m->chunk_oid_fanout,\n \t\t\t       m->chunk_oid_lookup,\n-\t\t\t       m->source->odb->repo->hash_algo->rawsz,\n+\t\t\t       m->source->base.odb->repo->hash_algo->rawsz,\n \t\t\t       result);\n \tif (result)\n \t\t*result += m->num_objects_in_base;\n@@ -554,7 +554,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,\n \tn = midx_for_object(&m, n);\n \n \toidread(oid, m->chunk_oid_lookup + st_mult(m->hash_len, n),\n-\t\tm->source->odb->repo->hash_algo);\n+\t\tm->source->base.odb->repo->hash_algo);\n \treturn oid;\n }\n \n@@ -734,26 +734,25 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id)\n \treturn 0;\n }\n \n-int prepare_multi_pack_index_one(struct odb_source *source)\n+int prepare_multi_pack_index_one(struct odb_source_packed *source)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \n \tprepare_repo_settings(r);\n \tif (!r->settings.core_multi_pack_index)\n \t\treturn 0;\n \n-\tif (files->packed->midx)\n+\tif (source->midx)\n \t\treturn 1;\n \n-\tfiles->packed->midx = load_multi_pack_index(source);\n+\tsource->midx = load_multi_pack_index(source);\n \n-\treturn !!files->packed->midx;\n+\treturn !!source->midx;\n }\n \n int midx_checksum_valid(struct multi_pack_index *m)\n {\n-\treturn hashfile_checksum_valid(m->source->odb->repo->hash_algo,\n+\treturn hashfile_checksum_valid(m->source->base.odb->repo->hash_algo,\n \t\t\t\t       m->data, m->data_len);\n }\n \n@@ -776,7 +775,7 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len UNUS\n \t\tdie_errno(_(\"failed to remove %s\"), full_path);\n }\n \n-void clear_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t  const char *keep_hash)\n {\n \tstruct clear_midx_data data = {\n@@ -793,12 +792,12 @@ void clear_midx_files_ext(struct odb_source *source, const char *ext,\n \t\tstrbuf_release(&buf);\n \t}\n \n-\tfor_each_file_in_pack_dir(source->path, clear_midx_file_ext, &data);\n+\tfor_each_file_in_pack_dir(source->base.path, clear_midx_file_ext, &data);\n \n \tstrset_clear(&data.keep);\n }\n \n-void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t\t      const struct strvec *keep_hashes)\n {\n \tstruct clear_midx_data data = {\n@@ -817,7 +816,7 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext\n \t\t}\n \t}\n \n-\tfor_each_file_in_pack_subdir(source->path, \"multi-pack-index.d\",\n+\tfor_each_file_in_pack_subdir(source->base.path, \"multi-pack-index.d\",\n \t\t\t\t     clear_midx_file_ext, &data);\n \n \tstrbuf_release(&buf);\n@@ -826,26 +825,28 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext\n \n void clear_midx_file(struct repository *r)\n {\n+\tstruct odb_source_files *files;\n \tstruct strbuf midx = STRBUF_INIT;\n \n-\tget_midx_filename(r->objects->sources, &midx);\n-\n \tif (r->objects) {\n \t\tstruct odb_source *source;\n \n \t\tfor (source = r->objects->sources; source; source = source->next) {\n-\t\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\t\tfiles = odb_source_files_downcast(source);\n \t\t\tif (files->packed->midx)\n \t\t\t\tclose_midx(files->packed->midx);\n \t\t\tfiles->packed->midx = NULL;\n \t\t}\n \t}\n \n+\tfiles = odb_source_files_downcast(r->objects->sources);\n+\tget_midx_filename(files->packed, &midx);\n+\n \tif (remove_path(midx.buf))\n \t\tdie(_(\"failed to clear multi-pack-index at %s\"), midx.buf);\n \n-\tclear_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP, NULL);\n-\tclear_midx_files_ext(r->objects->sources, MIDX_EXT_REV, NULL);\n+\tclear_midx_files_ext(files->packed, MIDX_EXT_BITMAP, NULL);\n+\tclear_midx_files_ext(files->packed, MIDX_EXT_REV, NULL);\n \n \tstrbuf_release(&midx);\n }\n@@ -853,28 +854,27 @@ void clear_midx_file(struct repository *r)\n void clear_incremental_midx_files(struct repository *r,\n \t\t\t\t  const struct strvec *keep_hashes)\n {\n-\tstruct odb_source *source = r->objects->sources;\n+\tstruct odb_source_files *files;\n+\tstruct odb_source *source;\n \tstruct strbuf chain = STRBUF_INIT;\n \n-\tget_midx_chain_filename(source, &chain);\n-\n-\tfor (; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\tfor (source = r->objects->sources; source; source = source->next) {\n+\t\tfiles = odb_source_files_downcast(source);\n \t\tif (files->packed->midx)\n \t\t\tclose_midx(files->packed->midx);\n \t\tfiles->packed->midx = NULL;\n \t}\n \n+\tfiles = odb_source_files_downcast(r->objects->sources);\n+\tget_midx_chain_filename(files->packed, &chain);\n+\n \tif (!keep_hashes && remove_path(chain.buf))\n \t\tdie(_(\"failed to clear multi-pack-index chain at %s\"),\n \t\t    chain.buf);\n \n-\tclear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP,\n-\t\t\t\t\t keep_hashes);\n-\tclear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_REV,\n-\t\t\t\t\t keep_hashes);\n-\tclear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_MIDX,\n-\t\t\t\t\t keep_hashes);\n+\tclear_incremental_midx_files_ext(files->packed, MIDX_EXT_BITMAP, keep_hashes);\n+\tclear_incremental_midx_files_ext(files->packed, MIDX_EXT_REV, keep_hashes);\n+\tclear_incremental_midx_files_ext(files->packed, MIDX_EXT_MIDX, keep_hashes);\n \n \tstrbuf_release(&chain);\n }\n@@ -918,9 +918,9 @@ static int compare_pair_pos_vs_id(const void *_a, const void *_b)\n \t\t\tdisplay_progress(progress, _n); \\\n \t} while (0)\n \n-int verify_midx_file(struct odb_source *source, unsigned flags)\n+int verify_midx_file(struct odb_source_packed *source, unsigned flags)\n {\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \tstruct pair_pos_vs_id *pairs = NULL;\n \tuint32_t i;\n \tstruct progress *progress = NULL;\ndiff --git a/midx.h b/midx.h\nindex 63853a03a4..939c18e588 100644\n--- a/midx.h\n+++ b/midx.h\n@@ -37,7 +37,7 @@ struct strvec;\n \t\"GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL\"\n \n struct multi_pack_index {\n-\tstruct odb_source *source;\n+\tstruct odb_source_packed *source;\n \n \tconst unsigned char *data;\n \tsize_t data_len;\n@@ -92,16 +92,16 @@ struct multi_pack_index {\n \n const char *midx_get_checksum_hex(const struct multi_pack_index *m) /* static buffer */;\n const unsigned char *midx_get_checksum_hash(const struct multi_pack_index *m);\n-void get_midx_filename(struct odb_source *source, struct strbuf *out);\n-void get_midx_filename_ext(struct odb_source *source, struct strbuf *out,\n+void get_midx_filename(struct odb_source_packed *source, struct strbuf *out);\n+void get_midx_filename_ext(struct odb_source_packed *source, struct strbuf *out,\n \t\t\t   const unsigned char *hash, const char *ext);\n-void get_midx_chain_dirname(struct odb_source *source, struct strbuf *out);\n-void get_midx_chain_filename(struct odb_source *source, struct strbuf *out);\n-void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf,\n+void get_midx_chain_dirname(struct odb_source_packed *source, struct strbuf *out);\n+void get_midx_chain_filename(struct odb_source_packed *source, struct strbuf *out);\n+void get_split_midx_filename_ext(struct odb_source_packed *source, struct strbuf *buf,\n \t\t\t\t const unsigned char *hash, const char *ext);\n \n-struct multi_pack_index *get_multi_pack_index(struct odb_source *source);\n-struct multi_pack_index *load_multi_pack_index(struct odb_source *source);\n+struct multi_pack_index *get_multi_pack_index(struct odb_source_packed *source);\n+struct multi_pack_index *load_multi_pack_index(struct odb_source_packed *source);\n int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id);\n struct packed_git *nth_midxed_pack(struct multi_pack_index *m,\n \t\t\t\t   uint32_t pack_int_id);\n@@ -123,22 +123,22 @@ int midx_contains_pack(struct multi_pack_index *m,\n int midx_layer_contains_pack(struct multi_pack_index *m,\n \t\t\t     const char *idx_or_pack_name);\n int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id);\n-int prepare_multi_pack_index_one(struct odb_source *source);\n+int prepare_multi_pack_index_one(struct odb_source_packed *source);\n \n /*\n  * Variant of write_midx_file which writes a MIDX containing only the packs\n  * specified in packs_to_include.\n  */\n-int write_midx_file(struct odb_source *source,\n+int write_midx_file(struct odb_source_packed *source,\n \t\t    const char *preferred_pack_name, const char *refs_snapshot,\n \t\t    unsigned flags);\n-int write_midx_file_only(struct odb_source *source,\n+int write_midx_file_only(struct odb_source_packed *source,\n \t\t\t struct string_list *packs_to_include,\n \t\t\t const char *preferred_pack_name,\n \t\t\t const char *refs_snapshot,\n \t\t\t const char *incremental_base,\n \t\t\t unsigned flags);\n-int write_midx_file_compact(struct odb_source *source,\n+int write_midx_file_compact(struct odb_source_packed *source,\n \t\t\t    struct multi_pack_index *from,\n \t\t\t    struct multi_pack_index *to,\n \t\t\t    const char *incremental_base,\n@@ -146,9 +146,9 @@ int write_midx_file_compact(struct odb_source *source,\n void clear_midx_file(struct repository *r);\n void clear_incremental_midx_files(struct repository *r,\n \t\t\t\t  const struct strvec *keep_hashes);\n-int verify_midx_file(struct odb_source *source, unsigned flags);\n-int expire_midx_packs(struct odb_source *source, unsigned flags);\n-int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags);\n+int verify_midx_file(struct odb_source_packed *source, unsigned flags);\n+int expire_midx_packs(struct odb_source_packed *source, unsigned flags);\n+int midx_repack(struct odb_source_packed *source, size_t batch_size, unsigned flags);\n \n void close_midx(struct multi_pack_index *m);\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 08a2de9bc5..d513b3efc3 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -136,8 +136,8 @@ static int for_each_prefixed_object_in_midx(\n \n \tfor (; m; m = m->base_midx) {\n \t\tuint32_t num, i, first = 0;\n-\t\tint len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ?\n-\t\t\tm->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n+\t\tint len = opts->prefix_hex_len > m->source->base.odb->repo->hash_algo->hexsz ?\n+\t\t\tm->source->base.odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n \n \t\tif (!m->num_objects)\n \t\t\tcontinue;\n@@ -249,7 +249,7 @@ static int odb_source_packed_for_each_prefixed_object(\n \n \tstore->skip_mru_updates = true;\n \n-\tm = get_multi_pack_index(&store->files->base);\n+\tm = get_multi_pack_index(store);\n \tif (m) {\n \t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n \t\tif (ret)\n@@ -348,7 +348,7 @@ static int odb_source_packed_count_objects(struct odb_source *source,\n \tunsigned long count = 0;\n \tint ret;\n \n-\tm = get_multi_pack_index(&packed->files->base);\n+\tm = get_multi_pack_index(packed);\n \tif (m)\n \t\tcount += m->num_objects + m->num_objects_in_base;\n \n@@ -465,7 +465,7 @@ static int odb_source_packed_find_abbrev_len(struct odb_source *source,\n \tstruct packfile_list_entry *e;\n \tstruct multi_pack_index *m;\n \n-\tm = get_multi_pack_index(&packed->files->base);\n+\tm = get_multi_pack_index(packed);\n \tif (m)\n \t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n \n@@ -674,7 +674,7 @@ void odb_source_packed_prepare(struct odb_source_packed *source)\n \tif (source->initialized)\n \t\treturn;\n \n-\tprepare_multi_pack_index_one(&source->files->base);\n+\tprepare_multi_pack_index_one(source);\n \tprepare_packed_git_one(&source->files->base);\n \n \tsort_packs(&source->packs.head, sort_pack);\ndiff --git a/pack-bitmap.c b/pack-bitmap.c\nindex f9af8a96bd..6bfcbc8ce6 100644\n--- a/pack-bitmap.c\n+++ b/pack-bitmap.c\n@@ -238,7 +238,7 @@ static uint32_t bitmap_name_hash(struct bitmap_index *index, uint32_t pos)\n static struct repository *bitmap_repo(struct bitmap_index *bitmap_git)\n {\n \tif (bitmap_is_midx(bitmap_git))\n-\t\treturn bitmap_git->midx->source->odb->repo;\n+\t\treturn bitmap_git->midx->source->base.odb->repo;\n \treturn bitmap_git->pack->repo;\n }\n \n@@ -711,7 +711,8 @@ static int open_midx_bitmap(struct repository *r,\n \n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct multi_pack_index *midx = get_multi_pack_index(source);\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct multi_pack_index *midx = get_multi_pack_index(files->packed);\n \t\tif (midx && !open_midx_bitmap_1(bitmap_git, midx))\n \t\t\tret = 0;\n \t}\n@@ -3399,7 +3400,8 @@ int verify_bitmap_files(struct repository *r)\n \n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \t\tchar *midx_bitmap_name;\n \n \t\tif (!m)\ndiff --git a/pack-revindex.c b/pack-revindex.c\nindex 1b67863606..62387ae632 100644\n--- a/pack-revindex.c\n+++ b/pack-revindex.c\n@@ -383,13 +383,13 @@ int load_midx_revindex(struct multi_pack_index *m)\n \t\t * not want to accidentally call munmap() in the middle of the\n \t\t * MIDX.\n \t\t */\n-\t\ttrace2_data_string(\"load_midx_revindex\", m->source->odb->repo,\n+\t\ttrace2_data_string(\"load_midx_revindex\", m->source->base.odb->repo,\n \t\t\t\t   \"source\", \"midx\");\n \t\tm->revindex_data = (const uint32_t *)m->chunk_revindex;\n \t\treturn 0;\n \t}\n \n-\ttrace2_data_string(\"load_midx_revindex\", m->source->odb->repo,\n+\ttrace2_data_string(\"load_midx_revindex\", m->source->base.odb->repo,\n \t\t\t   \"source\", \"rev\");\n \n \tif (m->has_chain)\n@@ -401,7 +401,7 @@ int load_midx_revindex(struct multi_pack_index *m)\n \t\t\t\t      midx_get_checksum_hash(m),\n \t\t\t\t      MIDX_EXT_REV);\n \n-\tret = load_revindex_from_disk(m->source->odb->repo->hash_algo,\n+\tret = load_revindex_from_disk(m->source->base.odb->repo->hash_algo,\n \t\t\t\t      revindex_name.buf,\n \t\t\t\t      m->num_objects,\n \t\t\t\t      &m->revindex_map,\ndiff --git a/repack-geometry.c b/repack-geometry.c\nindex 2064683dcf..15b3412950 100644\n--- a/repack-geometry.c\n+++ b/repack-geometry.c\n@@ -32,7 +32,8 @@ void pack_geometry_init(struct pack_geometry *geometry,\n {\n \tstruct packed_git *p;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct multi_pack_index *m = get_multi_pack_index(existing->source);\n+\tstruct odb_source_files *files = odb_source_files_downcast(existing->source);\n+\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \n \trepo_for_each_pack(existing->repo, p) {\n \t\tif (geometry->midx_layer_threshold_set && m &&\ndiff --git a/repack-midx.c b/repack-midx.c\nindex b6b1de7180..7c7c3620e5 100644\n--- a/repack-midx.c\n+++ b/repack-midx.c\n@@ -557,13 +557,14 @@ static void repack_make_midx_append_plan(struct repack_write_midx_opts *opts,\n \t\t\t\t\t struct midx_compaction_step **steps_p,\n \t\t\t\t\t size_t *steps_nr_p)\n {\n+\tstruct odb_source_files *files = odb_source_files_downcast(opts->existing->source);\n \tstruct multi_pack_index *m;\n \tstruct midx_compaction_step *steps = NULL;\n \tstruct midx_compaction_step *step;\n \tsize_t steps_nr = 0, steps_alloc = 0;\n \n \todb_reprepare(opts->existing->repo->objects);\n-\tm = get_multi_pack_index(opts->existing->source);\n+\tm = get_multi_pack_index(files->packed);\n \n \tif (opts->names->nr) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n@@ -606,6 +607,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,\n \t\t\t\t\t    struct midx_compaction_step **steps_p,\n \t\t\t\t\t    size_t *steps_nr_p)\n {\n+\tstruct odb_source_files *files = odb_source_files_downcast(opts->existing->source);\n \tstruct multi_pack_index *m;\n \tstruct midx_compaction_step *steps = NULL;\n \tstruct midx_compaction_step step = { 0 };\n@@ -618,7 +620,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,\n \t\t\t    opts->existing->repo);\n \n \todb_reprepare(opts->existing->repo->objects);\n-\tm = get_multi_pack_index(opts->existing->source);\n+\tm = get_multi_pack_index(files->packed);\n \n \tfor (i = 0; m && i < m->num_packs + m->num_packs_in_base; i++) {\n \t\tif (prepare_midx_pack(m, i)) {\n@@ -938,6 +940,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,\n \n static int write_midx_incremental(struct repack_write_midx_opts *opts)\n {\n+\tstruct odb_source_files *files = odb_source_files_downcast(opts->existing->source);\n \tstruct midx_compaction_step *steps = NULL;\n \tstruct strbuf lock_name = STRBUF_INIT;\n \tstruct lock_file lf;\n@@ -946,7 +949,7 @@ static int write_midx_incremental(struct repack_write_midx_opts *opts)\n \tsize_t i;\n \tint ret = 0;\n \n-\tget_midx_chain_filename(opts->existing->source, &lock_name);\n+\tget_midx_chain_filename(files->packed, &lock_name);\n \tif (safe_create_leading_directories(opts->existing->repo,\n \t\t\t\t\t    lock_name.buf))\n \t\tdie_errno(_(\"unable to create leading directories of %s\"),\ndiff --git a/repack.c b/repack.c\nindex 571dabb665..d2aa58e134 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -59,10 +59,10 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \t\t\t\t  bool wrote_incremental_midx)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct odb_source *source = repo->objects->sources;\n-\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\tstruct odb_source_files *files = odb_source_files_downcast(repo->objects->sources);\n+\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \tstrbuf_addf(&buf, \"%s.pack\", base_name);\n-\tif (m && source->local && midx_contains_pack(m, buf.buf)) {\n+\tif (m && files->base.local && midx_contains_pack(m, buf.buf)) {\n \t\tclear_midx_file(repo);\n \t\tif (!wrote_incremental_midx)\n \t\t\tclear_incremental_midx_files(repo, NULL);\ndiff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c\nindex 790000fb26..fb16ec0176 100644\n--- a/t/helper/test-read-midx.c\n+++ b/t/helper/test-read-midx.c\n@@ -13,13 +13,16 @@\n \n static struct multi_pack_index *setup_midx(const char *object_dir)\n {\n+\tstruct odb_source_files *files;\n \tstruct odb_source *source;\n \tsetup_git_directory(the_repository);\n \tsource = odb_find_source(the_repository->objects, object_dir);\n \tif (!source)\n \t\tsource = odb_add_to_alternates_memory(the_repository->objects,\n \t\t\t\t\t\t      object_dir);\n-\treturn load_multi_pack_index(source);\n+\tfiles = odb_source_files_downcast(source);\n+\n+\treturn load_multi_pack_index(files->packed);\n }\n \n static int read_midx_file(const char *object_dir, const char *checksum,\n@@ -70,7 +73,7 @@ static int read_midx_file(const char *object_dir, const char *checksum,\n \tfor (i = 0; i < m->num_packs; i++)\n \t\tprintf(\"%s\\n\", m->pack_names[i]);\n \n-\tprintf(\"object-dir: %s\\n\", m->source->path);\n+\tprintf(\"object-dir: %s\\n\", m->source->base.path);\n \n \tif (show_objects) {\n \t\tstruct object_id oid;\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544741","messageId":"20260604-pks-odb-source-packed-v1-16-2e7ab31b4b5c@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH 16/16] odb/source-packed: drop pointer to \"files\" parent source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T11:25:43Z","receivedAt":"2026-06-04T11:26:20Z","isPatch":true,"body":"Over the last commits we have turned the packfile store into a proper\nobject database source that can be used as a standalone backend. As\nsuch, it is no longer necessary to have it coupled to the \"files\" parent\nsource.\n\nRemove the pointer to the owning \"files\" source so that the \"packed\"\nsource can be used as a standalone entity.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 27 +++++++++++++--------------\n odb/source-packed.h |  7 ++++---\n packfile.c          |  2 +-\n 4 files changed, 19 insertions(+), 19 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex fa2e18e71b..3bc6419dd7 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tCALLOC_ARRAY(files, 1);\n \todb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);\n \tfiles->loose = odb_source_loose_new(odb, path, local);\n-\tfiles->packed = odb_source_packed_new(files);\n+\tfiles->packed = odb_source_packed_new(odb, path, local);\n \n \tfiles->base.free = odb_source_files_free;\n \tfiles->base.close = odb_source_files_close;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex d513b3efc3..42c28fba0e 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -585,7 +585,7 @@ static void report_pack_garbage(struct string_list *list)\n }\n \n struct prepare_pack_data {\n-\tstruct odb_source *source;\n+\tstruct odb_source_packed *source;\n \tstruct string_list *garbage;\n };\n \n@@ -593,15 +593,14 @@ static void prepare_pack(const char *full_name, size_t full_name_len,\n \t\t\t const char *file_name, void *_data)\n {\n \tstruct prepare_pack_data *data = (struct prepare_pack_data *)_data;\n-\tstruct odb_source_files *files = odb_source_files_downcast(data->source);\n \tsize_t base_len = full_name_len;\n \n \tif (strip_suffix_mem(full_name, &base_len, \".idx\") &&\n-\t    !(files->packed->midx &&\n-\t      midx_contains_pack(files->packed->midx, file_name))) {\n+\t    !(data->source->midx &&\n+\t      midx_contains_pack(data->source->midx, file_name))) {\n \t\tchar *trimmed_path = xstrndup(full_name, full_name_len);\n-\t\tpackfile_store_load_pack(files->packed,\n-\t\t\t\t\t trimmed_path, data->source->local);\n+\t\tpackfile_store_load_pack(data->source,\n+\t\t\t\t\t trimmed_path, data->source->base.local);\n \t\tfree(trimmed_path);\n \t}\n \n@@ -626,7 +625,7 @@ static void prepare_pack(const char *full_name, size_t full_name_len,\n \t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n }\n \n-static void prepare_packed_git_one(struct odb_source *source)\n+static void prepare_packed_git_one(struct odb_source_packed *source)\n {\n \tstruct string_list garbage = STRING_LIST_INIT_DUP;\n \tstruct prepare_pack_data data = {\n@@ -634,7 +633,7 @@ static void prepare_packed_git_one(struct odb_source *source)\n \t\t.garbage = &garbage,\n \t};\n \n-\tfor_each_file_in_pack_dir(source->path, prepare_pack, &data);\n+\tfor_each_file_in_pack_dir(source->base.path, prepare_pack, &data);\n \n \treport_pack_garbage(data.garbage);\n \tstring_list_clear(data.garbage, 0);\n@@ -675,7 +674,7 @@ void odb_source_packed_prepare(struct odb_source_packed *source)\n \t\treturn;\n \n \tprepare_multi_pack_index_one(source);\n-\tprepare_packed_git_one(&source->files->base);\n+\tprepare_packed_git_one(source);\n \n \tsort_packs(&source->packs.head, sort_pack);\n \tfor (struct packfile_list_entry *e = source->packs.head; e; e = e->next)\n@@ -733,14 +732,14 @@ static void odb_source_packed_free(struct odb_source *source)\n \tfree(packed);\n }\n \n-struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n+struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n+\t\t\t\t\t\tconst char *path,\n+\t\t\t\t\t\tbool local)\n {\n \tstruct odb_source_packed *packed;\n \n \tCALLOC_ARRAY(packed, 1);\n-\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n-\t\t\tparent->base.path, parent->base.local);\n-\tpacked->files = parent;\n+\todb_source_init(&packed->base, odb, ODB_SOURCE_PACKED, path, local);\n \tstrmap_init(&packed->packs_by_path);\n \n \tpacked->base.free = odb_source_packed_free;\n@@ -758,7 +757,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.read_alternates = odb_source_packed_read_alternates;\n \tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n-\tif (!is_absolute_path(parent->base.path))\n+\tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n \n \treturn packed;\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 6645f4f943..ef5a10b224 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -18,7 +18,6 @@ struct packfile_list_entry {\n  */\n struct odb_source_packed {\n \tstruct odb_source base;\n-\tstruct odb_source_files *files;\n \n \t/*\n \t * The list of packfiles in the order in which they have been most\n@@ -74,9 +73,11 @@ struct odb_source_packed {\n \n /*\n  * Allocate and initialize a new empty packfile store for the given object\n- * database source.\n+ * database.\n  */\n-struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent);\n+struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n+\t\t\t\t\t\tconst char *path,\n+\t\t\t\t\t\tbool local);\n \n /*\n  * Cast the given object database source to the packed backend. This will cause\ndiff --git a/packfile.c b/packfile.c\nindex d7de0412ff..478f00ce02 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -884,7 +884,7 @@ struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \n \tp = strmap_get(&store->packs_by_path, key.buf);\n \tif (!p) {\n-\t\tp = add_packed_git(store->files->base.odb->repo, idx_path,\n+\t\tp = add_packed_git(store->base.odb->repo, idx_path,\n \t\t\t\t   strlen(idx_path), local);\n \t\tif (p)\n \t\t\tpackfile_store_add_pack(store, p);\n\n-- \n2.54.0.1064.gd145956f57.dirty\n\n"},{"id":"544792","messageId":"CAOLa=ZQhGbjFbn_cpOmhYYN0xNjp1K8_Pj2mi34WzS25DG4ZEA@mail.gmail.com","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-1-2e7ab31b4b5c@pks.im","subject":"Re: [PATCH 01/16] packfile: rename `struct packfile_store` to `odb_source_packed`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-05T14:25:31Z","receivedAt":"2026-06-05T14:25:35Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Not too long ago, we have introduced the packfile store in b7983adb51\n> (packfile: introduce a new `struct packfile_store`, 2025-09-23). This\n> struct is responsible for managing all of our access to packfiles and is\n> used as one of the two sources of objects for the \"files\" source.\n>\n> Back when I introduced this structure I didn't have the clear vision yet\n> that it will eventually also turn into a proper object database source,\n> and how exactly that infrastructure will look like. Now though it's\n> becoming increasingly clear that it does make sense to treat it just the\n> same as any of our other ODB sources.\n>\n> The consequence is that the naming is now a bit out-of-date: it's just\n> another source and will be turned into a proper `struct odb_source` over\n> the next couple of commits, but it's not named accordingly.\n>\n> Rename the structure to `odb_source_packed` to align it with this goal\n> and to bring it in line with the other sources we already have.\n>\n\nLooks good, I'm assuming we'll also rename drop some of the\n`packfile_store_*` functions as things get cleaned up in the following\ncommits.\n"},{"id":"544860","messageId":"aiZf-_o9bH_EOtwe@pks.im","threadId":"65752","inReplyTo":"CAOLa=ZQhGbjFbn_cpOmhYYN0xNjp1K8_Pj2mi34WzS25DG4ZEA@mail.gmail.com","subject":"Re: [PATCH 01/16] packfile: rename `struct packfile_store` to `odb_source_packed`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-08T06:23:55Z","receivedAt":"2026-06-08T06:24:01Z","isPatch":true,"body":"On Fri, Jun 05, 2026 at 07:25:31AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Not too long ago, we have introduced the packfile store in b7983adb51\n> > (packfile: introduce a new `struct packfile_store`, 2025-09-23). This\n> > struct is responsible for managing all of our access to packfiles and is\n> > used as one of the two sources of objects for the \"files\" source.\n> >\n> > Back when I introduced this structure I didn't have the clear vision yet\n> > that it will eventually also turn into a proper object database source,\n> > and how exactly that infrastructure will look like. Now though it's\n> > becoming increasingly clear that it does make sense to treat it just the\n> > same as any of our other ODB sources.\n> >\n> > The consequence is that the naming is now a bit out-of-date: it's just\n> > another source and will be turned into a proper `struct odb_source` over\n> > the next couple of commits, but it's not named accordingly.\n> >\n> > Rename the structure to `odb_source_packed` to align it with this goal\n> > and to bring it in line with the other sources we already have.\n> >\n> \n> Looks good, I'm assuming we'll also rename drop some of the\n> `packfile_store_*` functions as things get cleaned up in the following\n> commits.\n\nIn fact not all of them, mostly because I wanted to not make the patch\nseries even bigger than it already is. I'll clean up stragglers in a\nsubsequent patch series though.\n\nPatrick\n"},{"id":"544936","messageId":"CAOLa=ZQ8K53yyopSOp4_Gc-Gpq6ULA0xW6gH5OCWdWNHEyRysw@mail.gmail.com","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-2-2e7ab31b4b5c@pks.im","subject":"Re: [PATCH 02/16] packfile: move packed source into \"odb/\" subsystem","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-08T15:09:06Z","receivedAt":"2026-06-08T15:09:09Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> In subsequent patches we'll be turning `struct odb_source_packed` into a\n> proper `struct odb_source`. As a first step towards this goal, move its\n> struct out of \"packfile.{c,h}\" and into \"odb/source-packed.{c,h}\".\n>\n> This detaches the implementation of the packfile object source from the\n> generic packfile code, following the same convention already used by the\n> \"files\" and \"in-memory\" sources.\n>\n\n[snip]\n\n> diff --git a/odb/source-packed.h b/odb/source-packed.h\n> new file mode 100644\n> index 0000000000..c17068a4f1\n> --- /dev/null\n> +++ b/odb/source-packed.h\n> @@ -0,0 +1,80 @@\n> +#ifndef ODB_SOURCE_PACKED_H\n> +#define ODB_SOURCE_PACKED_H\n> +\n> +#include \"odb/source.h\"\n> +#include \"strmap.h\"\n> +\n> +struct packfile_list {\n> +\tstruct packfile_list_entry *head, *tail;\n> +};\n> +\n> +struct packfile_list_entry {\n> +\tstruct packfile_list_entry *next;\n> +\tstruct packed_git *pack;\n> +};\n> +\n\nSo this is exposed, because outside of the odb, we also use packfiles in\nthe transport layer. That makes me wonder if these two structures are\nbetter kept alonsigde `struct packed_git` in 'packfile.h'.\n\n[snip]\n\nThe rest of the patch looks good.\n"},{"id":"544938","messageId":"CAOLa=ZQst6ucwvtVOfXC6g1ZcP9_UZAwRyAXfQdjL7WcJ6ZzxQ@mail.gmail.com","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-4-2e7ab31b4b5c@pks.im","subject":"Re: [PATCH 04/16] odb/source-packed: start converting to a proper `struct odb_source`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-08T15:29:04Z","receivedAt":"2026-06-08T15:29:06Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Start converting `struct odb_source_packed` into a proper pluggable\n> `struct odb_source` by embedding the base struct and assigning it the\n> new `ODB_SOURCE_PACKED` type. Furthermore, wire up lifecycle management\n> of this source by implementing the `free` callback and taking ownership\n> of the chdir notifications.\n>\n> Note that the packed source is not yet functional as a standalone `struct\n> odb_source`, as it's missing all of the callback implementations. These\n> will be wired up in subsequent commits.\n\nOkay, so individual commits going on will implement the callbacks.\n\n[snip]\n\n> diff --git a/odb/source-packed.c b/odb/source-packed.c\n> index 12e785be48..f81a990cbd 100644\n> --- a/odb/source-packed.c\n> +++ b/odb/source-packed.c\n> @@ -1,11 +1,50 @@\n>  #include \"git-compat-util.h\"\n> +#include \"abspath.h\"\n> +#include \"chdir-notify.h\"\n>  #include \"odb/source-packed.h\"\n> +#include \"packfile.h\"\n> +\n> +static void odb_source_packed_reparent(const char *name UNUSED,\n> +\t\t\t\t       const char *old_cwd,\n> +\t\t\t\t       const char *new_cwd,\n> +\t\t\t\t       void *cb_data)\n> +{\n> +\tstruct odb_source_packed *packed = cb_data;\n> +\tchar *path = reparent_relative_path(old_cwd, new_cwd,\n> +\t\t\t\t\t    packed->base.path);\n> +\tfree(packed->base.path);\n> +\tpacked->base.path = path;\n> +}\n> +\n> +static void odb_source_packed_free(struct odb_source *source)\n> +{\n> +\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n> +\n> +\tchdir_notify_unregister(NULL, odb_source_packed_reparent, packed);\n> +\n> +\tfor (struct packfile_list_entry *e = packed->packs.head; e; e = e->next)\n> +\t\tfree(e->pack);\n> +\tpackfile_list_clear(&packed->packs);\n> +\n> +\tstrmap_clear(&packed->packs_by_path, 0);\n> +\todb_source_release(&packed->base);\n> +\tfree(packed);\n> +}\n>\n>  struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n>  {\n> -\tstruct odb_source_packed *store;\n> -\tCALLOC_ARRAY(store, 1);\n> -\tstore->files = parent;\n> -\tstrmap_init(&store->packs_by_path);\n> -\treturn store;\n> +\tstruct odb_source_packed *packed;\n> +\n\nNit: we could've had a better diff if we used `struct odb_source_packed\n*packed` from the start. But its tiny and doesn't bother me.\n\n> +\tCALLOC_ARRAY(packed, 1);\n> +\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n> +\t\t\tparent->base.path, parent->base.local);\n> +\tpacked->files = parent;\n> +\tstrmap_init(&packed->packs_by_path);\n> +\n> +\tpacked->base.free = odb_source_packed_free;\n> +\n> +\tif (!is_absolute_path(parent->base.path))\n> +\t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n> +\n\nTangent: seems like no one sets the 'name' field within\n`chdir_notify_register()`. It is meant for tracing purposes, but if no\none is using it, we might as well remove it...? Perhaps #leftoverbits\n\n[snip]\n"},{"id":"544939","messageId":"CAOLa=ZTJf2x8AJ-oOOO5j3mSMbd2uTttoVCnSikNq37F_mKT=A@mail.gmail.com","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-5-2e7ab31b4b5c@pks.im","subject":"Re: [PATCH 05/16] odb/source-packed: wire up `close()` callback","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-08T15:31:22Z","receivedAt":"2026-06-08T15:31:24Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Wire up a new `close()` callback for the packed source and call it from\n> the \"files\" source via the generic `odb_source_close()` interface.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  odb/source-files.c  |  2 +-\n>  odb/source-packed.c | 16 ++++++++++++++++\n>  packfile.c          | 12 ------------\n>  packfile.h          |  6 ------\n>  4 files changed, 17 insertions(+), 19 deletions(-)\n>\n> diff --git a/odb/source-files.c b/odb/source-files.c\n> index 3608808e7c..9b0fa9ccdc 100644\n> --- a/odb/source-files.c\n> +++ b/odb/source-files.c\n> @@ -38,7 +38,7 @@ static void odb_source_files_close(struct odb_source *source)\n>  {\n>  \tstruct odb_source_files *files = odb_source_files_downcast(source);\n>  \todb_source_close(&files->loose->base);\n> -\tpackfile_store_close(files->packed);\n> +\todb_source_close(&files->packed->base);\n>  }\n>\n>  static void odb_source_files_reprepare(struct odb_source *source)\n> diff --git a/odb/source-packed.c b/odb/source-packed.c\n> index f81a990cbd..74805be1dd 100644\n> --- a/odb/source-packed.c\n> +++ b/odb/source-packed.c\n> @@ -1,6 +1,7 @@\n>  #include \"git-compat-util.h\"\n>  #include \"abspath.h\"\n>  #include \"chdir-notify.h\"\n> +#include \"midx.h\"\n>  #include \"odb/source-packed.h\"\n>  #include \"packfile.h\"\n>\n> @@ -16,6 +17,20 @@ static void odb_source_packed_reparent(const char *name UNUSED,\n>  \tpacked->base.path = path;\n>  }\n>\n> +static void odb_source_packed_close(struct odb_source *source)\n> +{\n> +\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n> +\n> +\tfor (struct packfile_list_entry *e = packed->packs.head; e; e = e->next) {\n> +\t\tif (e->pack->do_not_close)\n> +\t\t\tBUG(\"want to close pack marked 'do-not-close'\");\n> +\t\tclose_pack(e->pack);\n> +\t}\n> +\tif (packed->midx)\n> +\t\tclose_midx(packed->midx);\n> +\tpacked->midx = NULL;\n> +}\n> +\n>\n\nMost of my ODB understandings is coming from reviewing your patches. But\nI really like how we can map the current workings to the ODB interface.\n\n>  static void odb_source_packed_free(struct odb_source *source)\n>  {\n>  \tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n> @@ -42,6 +57,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n>  \tstrmap_init(&packed->packs_by_path);\n>\n>  \tpacked->base.free = odb_source_packed_free;\n> +\tpacked->base.close = odb_source_packed_close;\n>\n\nThis is what I mean :)\n\n[snip]\n"},{"id":"544942","messageId":"CAOLa=ZR_nq64=HUPYdk9SXYi=Y6FpbTD8P7R+Bm5QmDCRgnF1w@mail.gmail.com","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-7-2e7ab31b4b5c@pks.im","subject":"Re: [PATCH 07/16] packfile: use higher-level interface to implement `has_object_pack()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-08T16:07:25Z","receivedAt":"2026-06-08T16:07:28Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> In `has_object_pack()` we're checking whether a specific object exists\n> as part of a packfile. This is done by calling the low-level function\n> `find_pack_entry()`, but this function will eventually be moved into\n> \"odb/source-packed.c\" and made file-local.\n>\n> Refactor the code to use `packfile_store_read_object_info()` instead.\n> This refactoring is functionally equivalent as that function will call\n> `find_pack_entry()` itself and then return immediately when it ain't got\n> no object info pointer as parameter.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  packfile.c | 6 ++----\n>  1 file changed, 2 insertions(+), 4 deletions(-)\n>\n> diff --git a/packfile.c b/packfile.c\n> index 902b7f70f2..3ee71d7f71 100644\n> --- a/packfile.c\n> +++ b/packfile.c\n> @@ -2132,14 +2132,12 @@ struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed\n>  int has_object_pack(struct repository *r, const struct object_id *oid)\n>  {\n>  \tstruct odb_source *source;\n> -\tstruct pack_entry e;\n>\n>  \todb_prepare_alternates(r->objects);\n>  \tfor (source = r->objects->sources; source; source = source->next) {\n>  \t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n> -\t\tint ret = find_pack_entry(files->packed, oid, &e);\n> -\t\tif (ret)\n> -\t\t\treturn ret;\n> +\t\tif (!packfile_store_read_object_info(files->packed, oid, NULL, 0))\n> +\t\t\treturn 1;\n>  \t}\n>\n\nI was wondering if there would be an added cost of actually obtaining\nthe object-info. Seems like there isn't, because we pass `NULL` for the\n`struct object_info *oi`, which means it will return before reading the\nobject info.\n\n>  \treturn 0;\n>\n> --\n> 2.54.0.1064.gd145956f57.dirty\n"},{"id":"544943","messageId":"CAOLa=ZQdGMo83KggkmeeKYMR475TFqLn=o-nJz4QEUX2njgaOA@mail.gmail.com","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-11-2e7ab31b4b5c@pks.im","subject":"Re: [PATCH 11/16] odb/source-packed: wire up `count_objects()` callback","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-08T16:12:06Z","receivedAt":"2026-06-08T16:12:09Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n[snip]\n\n> diff --git a/odb/source-packed.c b/odb/source-packed.c\n> index a61c809c8c..013d8a50f8 100644\n> --- a/odb/source-packed.c\n> +++ b/odb/source-packed.c\n> @@ -338,6 +338,39 @@ static int odb_source_packed_for_each_object(struct odb_source *source,\n>  \treturn ret;\n>  }\n>\n> +static int odb_source_packed_count_objects(struct odb_source *source,\n> +\t\t\t\t\t   enum odb_count_objects_flags flags UNUSED,\n> +\t\t\t\t\t   unsigned long *out)\n> +{\n> +\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n> +\tstruct packfile_list_entry *e;\n> +\tstruct multi_pack_index *m;\n> +\tunsigned long count = 0;\n> +\tint ret;\n> +\n> +\tm = get_multi_pack_index(&packed->files->base);\n> +\tif (m)\n> +\t\tcount += m->num_objects + m->num_objects_in_base;\n> +\n> +\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n> +\t\tif (e->pack->multi_pack_index)\n> +\t\t\tcontinue;\n> +\t\tif (open_pack_index(e->pack)) {\n> +\t\t\tret = -1;\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tcount += e->pack->num_objects;\n> +\t}\n> +\n> +\t*out = count;\n> +\tret = 0;\n> +\n> +out:\n> +\treturn ret;\n> +}\n> +\n> +\n\nNit: extra newline.\n\n>  void (*report_garbage)(unsigned seen_bits, const char *path);\n>\n>  static void report_helper(const struct string_list *list,\n> @@ -549,6 +582,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n>  \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n>  \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n>  \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n> +\tpacked->base.count_objects = odb_source_packed_count_objects;\n>\n>  \tif (!is_absolute_path(parent->base.path))\n>  \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n\n[snip]\n"},{"id":"544945","messageId":"CAOLa=ZT9PLFeVpyKph=jQOz_BHXhYgKO=-3SV_VP6p4oXLxZpg@mail.gmail.com","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"Re: [PATCH 00/16] odb: make packed object source a proper `struct odb_source`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-06-08T16:15:09Z","receivedAt":"2026-06-08T16:15:11Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> this patch series converts the \"packed\" source into a proper `struct\n> odb_source`. It's thus the equivalent to [1], which did the same thing\n> for the \"loose\" source.\n>\n> This series here is unfortunately a bit bigger, mostly because I'm also\n> renaming `struct packfile_store` to `struct odb_source_packed`. Back\n> when I introduced the packfile store I didn't yet have the full vision\n> of how the final layout will look like, so I didn't have the foresight\n> yet to call it `struct odb_source_packed`. But now that the layout has\n> materialized I think it's sensible to adjust its naming to match all the\n> other sources that we have.\n>\n> Also: I don't have anything else in the pipeline anymore that moves\n> around large pieces of our code in the vicinity of the object database.\n> So after this series got merged, subsequent changes should be of a more\n> incremental nature.\n>\n> This series is built on top of 9ac3f193c0 (The 11th batch, 2026-06-02)\n> with ps/odb-source-loose at ef4778bcba (odb/source-loose: drop pointer\n> to the \"files\" source, 2026-06-01) merged into it.\n\nThis was a good read. The commits towards the end are mostly simple code\nmovements. Overall the series looks to be in good shape.\n\n[snip]\n"},{"id":"545016","messageId":"aifAVpxanV31KUpC@pks.im","threadId":"65752","inReplyTo":"CAOLa=ZQst6ucwvtVOfXC6g1ZcP9_UZAwRyAXfQdjL7WcJ6ZzxQ@mail.gmail.com","subject":"Re: [PATCH 04/16] odb/source-packed: start converting to a proper `struct odb_source`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T07:27:18Z","receivedAt":"2026-06-09T07:27:30Z","isPatch":true,"body":"On Mon, Jun 08, 2026 at 08:29:04AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/odb/source-packed.c b/odb/source-packed.c\n> > index 12e785be48..f81a990cbd 100644\n> > --- a/odb/source-packed.c\n> > +++ b/odb/source-packed.c\n> > +\tCALLOC_ARRAY(packed, 1);\n> > +\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n> > +\t\t\tparent->base.path, parent->base.local);\n> > +\tpacked->files = parent;\n> > +\tstrmap_init(&packed->packs_by_path);\n> > +\n> > +\tpacked->base.free = odb_source_packed_free;\n> > +\n> > +\tif (!is_absolute_path(parent->base.path))\n> > +\t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n> > +\n> \n> Tangent: seems like no one sets the 'name' field within\n> `chdir_notify_register()`. It is meant for tracing purposes, but if no\n> one is using it, we might as well remove it...? Perhaps #leftoverbits\n\nThere are some callers: `chdir_notify_reparent()` calls\n`chdir_notify_register()` with a name, and the reference backends call\nthat function with names.\n\nUltimately though I think that this infrastructure is somewhat misguided\noverall: we use this to update relative paths after chdir(3p), but if we\nstored absolute paths in the first place then we wouldn't have to care\nabout the paths changing at all.\n\nI plan to revisit this infra for the object database going forward: we\nexpose and use `struct odb_source::path` in various other subsystems,\nincluding user-facing ones. This is inherently wrong though, as there\nmay be sources that don't even have an on-disk path. So there's a need\nto drop that field and make it an internal implementation detail of the\nsource's backend. And once we've done that, we can just as well start to\nstore absolute paths.\n\nFor the reference backends we can already do that refactoring now-ish.\nI'll send a patch series later today.\n\nPatrick\n"},{"id":"545017","messageId":"aifAYcq4lQEO_yWq@pks.im","threadId":"65752","inReplyTo":"CAOLa=ZQdGMo83KggkmeeKYMR475TFqLn=o-nJz4QEUX2njgaOA@mail.gmail.com","subject":"Re: [PATCH 11/16] odb/source-packed: wire up `count_objects()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T07:27:29Z","receivedAt":"2026-06-09T07:27:39Z","isPatch":true,"body":"On Mon, Jun 08, 2026 at 09:12:06AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> [snip]\n> \n> > diff --git a/odb/source-packed.c b/odb/source-packed.c\n> > index a61c809c8c..013d8a50f8 100644\n> > --- a/odb/source-packed.c\n> > +++ b/odb/source-packed.c\n> > @@ -338,6 +338,39 @@ static int odb_source_packed_for_each_object(struct odb_source *source,\n> >  \treturn ret;\n> >  }\n> >\n> > +static int odb_source_packed_count_objects(struct odb_source *source,\n> > +\t\t\t\t\t   enum odb_count_objects_flags flags UNUSED,\n> > +\t\t\t\t\t   unsigned long *out)\n> > +{\n> > +\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n> > +\tstruct packfile_list_entry *e;\n> > +\tstruct multi_pack_index *m;\n> > +\tunsigned long count = 0;\n> > +\tint ret;\n> > +\n> > +\tm = get_multi_pack_index(&packed->files->base);\n> > +\tif (m)\n> > +\t\tcount += m->num_objects + m->num_objects_in_base;\n> > +\n> > +\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n> > +\t\tif (e->pack->multi_pack_index)\n> > +\t\t\tcontinue;\n> > +\t\tif (open_pack_index(e->pack)) {\n> > +\t\t\tret = -1;\n> > +\t\t\tgoto out;\n> > +\t\t}\n> > +\n> > +\t\tcount += e->pack->num_objects;\n> > +\t}\n> > +\n> > +\t*out = count;\n> > +\tret = 0;\n> > +\n> > +out:\n> > +\treturn ret;\n> > +}\n> > +\n> > +\n> \n> Nit: extra newline.\n\nGood catch, fixed locally.\n\nPatrick\n"},{"id":"545018","messageId":"aifAbwJCPe4Wwklk@pks.im","threadId":"65752","inReplyTo":"CAOLa=ZQ8K53yyopSOp4_Gc-Gpq6ULA0xW6gH5OCWdWNHEyRysw@mail.gmail.com","subject":"Re: [PATCH 02/16] packfile: move packed source into \"odb/\" subsystem","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T07:27:43Z","receivedAt":"2026-06-09T07:27:47Z","isPatch":true,"body":"On Mon, Jun 08, 2026 at 08:09:06AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/odb/source-packed.h b/odb/source-packed.h\n> > new file mode 100644\n> > index 0000000000..c17068a4f1\n> > --- /dev/null\n> > +++ b/odb/source-packed.h\n> > @@ -0,0 +1,80 @@\n> > +#ifndef ODB_SOURCE_PACKED_H\n> > +#define ODB_SOURCE_PACKED_H\n> > +\n> > +#include \"odb/source.h\"\n> > +#include \"strmap.h\"\n> > +\n> > +struct packfile_list {\n> > +\tstruct packfile_list_entry *head, *tail;\n> > +};\n> > +\n> > +struct packfile_list_entry {\n> > +\tstruct packfile_list_entry *next;\n> > +\tstruct packed_git *pack;\n> > +};\n> > +\n> \n> So this is exposed, because outside of the odb, we also use packfiles in\n> the transport layer. That makes me wonder if these two structures are\n> better kept alonsigde `struct packed_git` in 'packfile.h'.\n\nYeah, this is quite awkward indeed as the struct and function\ndeclarations are now split up across \"packfile.h\" and\n\"odb/source-packed.h\". The reason though is that there's a cyclic\ndependency between the two headers, so we have to move the code around.\n\nArguably though, the better fix would be to move it into a standalone\nfile \"packfile-list.{c,h}\". Will adapt the code accordingly.\n\nPatrick\n"},{"id":"545019","messageId":"aifAf-ostt7-H9wV@pks.im","threadId":"65752","inReplyTo":"CAOLa=ZT9PLFeVpyKph=jQOz_BHXhYgKO=-3SV_VP6p4oXLxZpg@mail.gmail.com","subject":"Re: [PATCH 00/16] odb: make packed object source a proper `struct odb_source`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T07:27:59Z","receivedAt":"2026-06-09T07:28:05Z","isPatch":true,"body":"On Mon, Jun 08, 2026 at 09:15:09AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Hi,\n> >\n> > this patch series converts the \"packed\" source into a proper `struct\n> > odb_source`. It's thus the equivalent to [1], which did the same thing\n> > for the \"loose\" source.\n> >\n> > This series here is unfortunately a bit bigger, mostly because I'm also\n> > renaming `struct packfile_store` to `struct odb_source_packed`. Back\n> > when I introduced the packfile store I didn't yet have the full vision\n> > of how the final layout will look like, so I didn't have the foresight\n> > yet to call it `struct odb_source_packed`. But now that the layout has\n> > materialized I think it's sensible to adjust its naming to match all the\n> > other sources that we have.\n> >\n> > Also: I don't have anything else in the pipeline anymore that moves\n> > around large pieces of our code in the vicinity of the object database.\n> > So after this series got merged, subsequent changes should be of a more\n> > incremental nature.\n> >\n> > This series is built on top of 9ac3f193c0 (The 11th batch, 2026-06-02)\n> > with ps/odb-source-loose at ef4778bcba (odb/source-loose: drop pointer\n> > to the \"files\" source, 2026-06-01) merged into it.\n> \n> This was a good read. The commits towards the end are mostly simple code\n> movements. Overall the series looks to be in good shape.\n\nThanks for your review! Will send a new version later today.\n\nPatrick\n"},{"id":"545023","messageId":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH v2 00/17] odb: make packed object source a proper `struct odb_source`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:50:53Z","receivedAt":"2026-06-09T08:51:10Z","isPatch":true,"body":"Hi,\n\nthis patch series converts the \"packed\" source into a proper `struct\nodb_source`. It's thus the equivalent to [1], which did the same thing\nfor the \"loose\" source.\n\nThis series here is unfortunately a bit bigger, mostly because I'm also\nrenaming `struct packfile_store` to `struct odb_source_packed`. Back\nwhen I introduced the packfile store I didn't yet have the full vision\nof how the final layout will look like, so I didn't have the foresight\nyet to call it `struct odb_source_packed`. But now that the layout has\nmaterialized I think it's sensible to adjust its naming to match all the\nother sources that we have.\n\nAlso: I don't have anything else in the pipeline anymore that moves\naround large pieces of our code in the vicinity of the object database.\nSo after this series got merged, subsequent changes should be of a more\nincremental nature.\n\nThis series is built on top of 9ac3f193c0 (The 11th batch, 2026-06-02)\nwith ps/odb-source-loose at ef4778bcba (odb/source-loose: drop pointer\nto the \"files\" source, 2026-06-01) merged into it.\n\nChanges in v2:\n  - Split out `struct packfile_list` into a separate code unit to fix a\n    cyclic dependency between \"packfile.h\" and \"odb/souurce-packed.h\".\n  - Fix an extraneous newline.\n  - Link to v1: https://patch.msgid.link/20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im\n\nThanks!\n\nPatrick\n\n[1]: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>\n\n---\nPatrick Steinhardt (17):\n      packfile: rename `struct packfile_store` to `odb_source_packed`\n      packfile: split out packfile list logic\n      packfile: move packed source into \"odb/\" subsystem\n      odb/source-packed: store pointer to \"files\" instead of generic source\n      odb/source-packed: start converting to a proper `struct odb_source`\n      odb/source-packed: wire up `close()` callback\n      odb/source-packed: wire up `reprepare()` callback\n      packfile: use higher-level interface to implement `has_object_pack()`\n      odb/source-packed: wire up `read_object_info()` callback\n      odb/source-packed: wire up `read_object_stream()` callback\n      odb/source-packed: wire up `for_each_object()` callback\n      odb/source-packed: wire up `count_objects()` callback\n      odb/source-packed: wire up `find_abbrev_len()` callback\n      odb/source-packed: wire up `freshen_object()` callback\n      odb/source-packed: stub out remaining functions\n      midx: refactor interfaces to work on \"packed\" source\n      odb/source-packed: drop pointer to \"files\" parent source\n\n Makefile                   |   2 +\n builtin/cat-file.c         |   4 +-\n builtin/grep.c             |   2 +-\n builtin/multi-pack-index.c |  29 +-\n builtin/pack-objects.c     |   7 +-\n builtin/repack.c           |   8 +-\n commit-graph.c             |   4 +-\n meson.build                |   2 +\n midx-write.c               |  34 +-\n midx.c                     | 118 +++----\n midx.h                     |  30 +-\n odb/source-files.c         |  20 +-\n odb/source-files.h         |   4 +-\n odb/source-packed.c        | 764 +++++++++++++++++++++++++++++++++++++++++++\n odb/source-packed.h        |  94 ++++++\n odb/source.h               |   3 +\n pack-bitmap.c              |   8 +-\n pack-revindex.c            |   6 +-\n packfile-list.c            |  86 +++++\n packfile-list.h            |  28 ++\n packfile.c                 | 784 +--------------------------------------------\n packfile.h                 | 180 +----------\n repack-geometry.c          |   3 +-\n repack-midx.c              |   9 +-\n repack.c                   |   6 +-\n t/helper/test-read-midx.c  |   7 +-\n 26 files changed, 1163 insertions(+), 1079 deletions(-)\n\nRange-diff versus v1:\n\n 1:  5fb0dcfef9 =  1:  8a5e5f5473 packfile: rename `struct packfile_store` to `odb_source_packed`\n -:  ---------- >  2:  179416a017 packfile: split out packfile list logic\n 2:  a29ca59090 !  3:  6e54f9f918 packfile: move packed source into \"odb/\" subsystem\n    @@ odb/source-packed.h (new)\n     +#define ODB_SOURCE_PACKED_H\n     +\n     +#include \"odb/source.h\"\n    ++#include \"packfile-list.h\"\n     +#include \"strmap.h\"\n     +\n    -+struct packfile_list {\n    -+\tstruct packfile_list_entry *head, *tail;\n    -+};\n    -+\n    -+struct packfile_list_entry {\n    -+\tstruct packfile_list_entry *next;\n    -+\tstruct packed_git *pack;\n    -+};\n    -+\n     +/*\n     + * A store that manages packfiles for a given object database.\n     + */\n    @@ packfile.h\n      #include \"odb/source-files.h\"\n     +#include \"odb/source-packed.h\"\n      #include \"oidset.h\"\n    + #include \"packfile-list.h\"\n      #include \"repository.h\"\n     -#include \"strmap.h\"\n      \n    @@ packfile.h: struct packed_git {\n      \tchar pack_name[FLEX_ARRAY]; /* more */\n      };\n      \n    --struct packfile_list {\n    --\tstruct packfile_list_entry *head, *tail;\n    --};\n    --\n    --struct packfile_list_entry {\n    --\tstruct packfile_list_entry *next;\n    --\tstruct packed_git *pack;\n    --};\n    --\n    - void packfile_list_clear(struct packfile_list *list);\n    - void packfile_list_remove(struct packfile_list *list, struct packed_git *pack);\n    - void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack);\n    -@@ packfile.h: void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n    - struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n    - \t\t\t\t\t  const struct object_id *oid);\n    - \n     -/*\n     - * A store that manages packfiles for a given object database.\n     - */\n 3:  d0402d115b !  4:  85ebdcb253 odb/source-packed: store pointer to \"files\" instead of generic source\n    @@ odb/source-packed.c\n      }\n     \n      ## odb/source-packed.h ##\n    -@@ odb/source-packed.h: struct packfile_list_entry {\n    +@@\n       * A store that manages packfiles for a given object database.\n       */\n      struct odb_source_packed {\n 4:  d92302b497 !  5:  7d5605b9c7 odb/source-packed: start converting to a proper `struct odb_source`\n    @@ odb/source-packed.c\n      }\n     \n      ## odb/source-packed.h ##\n    -@@ odb/source-packed.h: struct packfile_list_entry {\n    +@@\n       * A store that manages packfiles for a given object database.\n       */\n      struct odb_source_packed {\n    @@ packfile.c: int parse_pack_header_option(const char *in, unsigned char *out, uns\n      \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {\n     \n      ## packfile.h ##\n    -@@ packfile.h: void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n    - struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n    - \t\t\t\t\t  const struct object_id *oid);\n    +@@ packfile.h: struct packed_git {\n    + \tchar pack_name[FLEX_ARRAY]; /* more */\n    + };\n      \n     -/*\n     - * Free the packfile store and all its associated state. All packfiles\n 5:  9a51dac274 !  6:  736dc22977 odb/source-packed: wire up `close()` callback\n    @@ packfile.c: int parse_pack_header_option(const char *in, unsigned char *out, uns\n      \tstruct packed_git *pack;\n     \n      ## packfile.h ##\n    -@@ packfile.h: void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n    - struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n    - \t\t\t\t\t  const struct object_id *oid);\n    +@@ packfile.h: struct packed_git {\n    + \tchar pack_name[FLEX_ARRAY]; /* more */\n    + };\n      \n     -/*\n     - * Close all packfiles associated with this store. The packfiles won't be\n 6:  b9571c28f1 !  7:  f2ec21206d odb/source-packed: wire up `reprepare()` callback\n    @@ packfile.c: int packfile_store_read_object_info(struct odb_source_packed *store,\n      \t\treturn 1;\n     \n      ## packfile.h ##\n    -@@ packfile.h: void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n    - struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n    - \t\t\t\t\t  const struct object_id *oid);\n    +@@ packfile.h: struct packed_git {\n    + \tchar pack_name[FLEX_ARRAY]; /* more */\n    + };\n      \n     -/*\n     - * Prepare the packfile store by loading packfiles and multi-pack indices for\n 7:  703181be7e =  8:  a41dbcf7d9 packfile: use higher-level interface to implement `has_object_pack()`\n 8:  f1f1cb2044 =  9:  3fcdfc2686 odb/source-packed: wire up `read_object_info()` callback\n 9:  91fad0e9ad = 10:  7b301ce67d odb/source-packed: wire up `read_object_stream()` callback\n10:  0dc3c1b836 = 11:  5298ddece5 odb/source-packed: wire up `for_each_object()` callback\n11:  461be09a17 ! 12:  929d7ad5af odb/source-packed: wire up `count_objects()` callback\n    @@ odb/source-packed.c: static int odb_source_packed_for_each_object(struct odb_sou\n     +out:\n     +\treturn ret;\n     +}\n    -+\n     +\n      void (*report_garbage)(unsigned seen_bits, const char *path);\n      \n12:  c206556286 ! 13:  5de59e451d odb/source-packed: wire up `find_abbrev_len()` callback\n    @@ odb/source-packed.c: static int odb_source_packed_count_objects(struct odb_sourc\n     +\t*out = min_len;\n     +\treturn 0;\n     +}\n    - \n    ++\n      void (*report_garbage)(unsigned seen_bits, const char *path);\n      \n    + static void report_helper(const struct string_list *list,\n     @@ odb/source-packed.c: struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n      \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n      \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n13:  3ee5394ce8 = 14:  8e7b0f746c odb/source-packed: wire up `freshen_object()` callback\n14:  875ca3572a = 15:  52967e0d24 odb/source-packed: stub out remaining functions\n15:  5bd35384d6 = 16:  68cf451c52 midx: refactor interfaces to work on \"packed\" source\n16:  d1a7ce9f17 ! 17:  fbed40d82a odb/source-packed: drop pointer to \"files\" parent source\n    @@ odb/source-packed.c: struct odb_source_packed *odb_source_packed_new(struct odb_\n      \treturn packed;\n     \n      ## odb/source-packed.h ##\n    -@@ odb/source-packed.h: struct packfile_list_entry {\n    +@@\n       */\n      struct odb_source_packed {\n      \tstruct odb_source base;\n\n---\nbase-commit: 06d49cec508464ced5d42541890ce5d749542a61\nchange-id: 20260602-pks-odb-source-packed-3826c352f059\n\n"},{"id":"545024","messageId":"20260609-pks-odb-source-packed-v2-1-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 01/17] packfile: rename `struct packfile_store` to `odb_source_packed`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:50:54Z","receivedAt":"2026-06-09T08:51:11Z","isPatch":true,"body":"Not too long ago, we have introduced the packfile store in b7983adb51\n(packfile: introduce a new `struct packfile_store`, 2025-09-23). This\nstruct is responsible for managing all of our access to packfiles and is\nused as one of the two sources of objects for the \"files\" source.\n\nBack when I introduced this structure I didn't have the clear vision yet\nthat it will eventually also turn into a proper object database source,\nand how exactly that infrastructure will look like. Now though it's\nbecoming increasingly clear that it does make sense to treat it just the\nsame as any of our other ODB sources.\n\nThe consequence is that the naming is now a bit out-of-date: it's just\nanother source and will be turned into a proper `struct odb_source` over\nthe next couple of commits, but it's not named accordingly.\n\nRename the structure to `odb_source_packed` to align it with this goal\nand to bring it in line with the other sources we already have.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.h |  4 ++--\n packfile.c         | 56 +++++++++++++++++++++++++++---------------------------\n packfile.h         | 32 +++++++++++++++----------------\n 3 files changed, 46 insertions(+), 46 deletions(-)\n\ndiff --git a/odb/source-files.h b/odb/source-files.h\nindex 23a3b4e04b..d7ac3c1c81 100644\n--- a/odb/source-files.h\n+++ b/odb/source-files.h\n@@ -4,7 +4,7 @@\n #include \"odb/source.h\"\n \n struct odb_source_loose;\n-struct packfile_store;\n+struct odb_source_packed;\n \n /*\n  * The files object database source uses a combination of loose objects and\n@@ -13,7 +13,7 @@ struct packfile_store;\n struct odb_source_files {\n \tstruct odb_source base;\n \tstruct odb_source_loose *loose;\n-\tstruct packfile_store *packed;\n+\tstruct odb_source_packed *packed;\n };\n \n /* Allocate and initialize a new object source. */\ndiff --git a/packfile.c b/packfile.c\nindex 89366abfe3..a2d768d0ae 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -859,7 +859,7 @@ struct packed_git *add_packed_git(struct repository *r, const char *path,\n \treturn p;\n }\n \n-void packfile_store_add_pack(struct packfile_store *store,\n+void packfile_store_add_pack(struct odb_source_packed *store,\n \t\t\t     struct packed_git *pack)\n {\n \tif (pack->pack_fd != -1)\n@@ -869,7 +869,7 @@ void packfile_store_add_pack(struct packfile_store *store,\n \tstrmap_put(&store->packs_by_path, pack->pack_name, pack);\n }\n \n-struct packed_git *packfile_store_load_pack(struct packfile_store *store,\n+struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \t\t\t\t\t    const char *idx_path, int local)\n {\n \tstruct strbuf key = STRBUF_INIT;\n@@ -1068,7 +1068,7 @@ static int sort_pack(const struct packfile_list_entry *a,\n \treturn -1;\n }\n \n-void packfile_store_prepare(struct packfile_store *store)\n+void packfile_store_prepare(struct odb_source_packed *store)\n {\n \tif (store->initialized)\n \t\treturn;\n@@ -1084,13 +1084,13 @@ void packfile_store_prepare(struct packfile_store *store)\n \tstore->initialized = true;\n }\n \n-void packfile_store_reprepare(struct packfile_store *store)\n+void packfile_store_reprepare(struct odb_source_packed *store)\n {\n \tstore->initialized = false;\n \tpackfile_store_prepare(store);\n }\n \n-struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store)\n+struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store)\n {\n \tpackfile_store_prepare(store);\n \n@@ -1103,7 +1103,7 @@ struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *stor\n \treturn store->packs.head;\n }\n \n-int packfile_store_count_objects(struct packfile_store *store,\n+int packfile_store_count_objects(struct odb_source_packed *store,\n \t\t\t\t enum odb_count_objects_flags flags UNUSED,\n \t\t\t\t unsigned long *out)\n {\n@@ -2160,7 +2160,7 @@ static int fill_pack_entry(const struct object_id *oid,\n \treturn 1;\n }\n \n-static int find_pack_entry(struct packfile_store *store,\n+static int find_pack_entry(struct odb_source_packed *store,\n \t\t\t   const struct object_id *oid,\n \t\t\t   struct pack_entry *e)\n {\n@@ -2183,7 +2183,7 @@ static int find_pack_entry(struct packfile_store *store,\n \treturn 0;\n }\n \n-int packfile_store_freshen_object(struct packfile_store *store,\n+int packfile_store_freshen_object(struct odb_source_packed *store,\n \t\t\t\t  const struct object_id *oid)\n {\n \tstruct pack_entry e;\n@@ -2199,7 +2199,7 @@ int packfile_store_freshen_object(struct packfile_store *store,\n \treturn 1;\n }\n \n-int packfile_store_read_object_info(struct packfile_store *store,\n+int packfile_store_read_object_info(struct odb_source_packed *store,\n \t\t\t\t    const struct object_id *oid,\n \t\t\t\t    struct object_info *oi,\n \t\t\t\t    enum object_info_flags flags)\n@@ -2234,7 +2234,7 @@ int packfile_store_read_object_info(struct packfile_store *store,\n \treturn 0;\n }\n \n-static void maybe_invalidate_kept_pack_cache(struct packfile_store *store,\n+static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t     unsigned flags)\n {\n \tif (!store->kept_cache.packs)\n@@ -2245,7 +2245,7 @@ static void maybe_invalidate_kept_pack_cache(struct packfile_store *store,\n \tstore->kept_cache.flags = 0;\n }\n \n-struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store,\n+struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t\t       unsigned flags)\n {\n \tmaybe_invalidate_kept_pack_cache(store, flags);\n@@ -2365,8 +2365,8 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-struct packfile_store_for_each_object_wrapper_data {\n-\tstruct packfile_store *store;\n+struct odb_source_packed_for_each_object_wrapper_data {\n+\tstruct odb_source_packed *store;\n \tconst struct object_info *request;\n \todb_for_each_object_cb cb;\n \tvoid *cb_data;\n@@ -2377,7 +2377,7 @@ static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n \t\t\t\t\t\t  uint32_t index_pos,\n \t\t\t\t\t\t  void *cb_data)\n {\n-\tstruct packfile_store_for_each_object_wrapper_data *data = cb_data;\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data = cb_data;\n \n \tif (data->request) {\n \t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n@@ -2411,10 +2411,10 @@ static int match_hash(unsigned len, const unsigned char *a, const unsigned char\n }\n \n static int for_each_prefixed_object_in_midx(\n-\tstruct packfile_store *store,\n+\tstruct odb_source_packed *store,\n \tstruct multi_pack_index *m,\n \tconst struct odb_for_each_object_options *opts,\n-\tstruct packfile_store_for_each_object_wrapper_data *data)\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n {\n \tint ret;\n \n@@ -2470,10 +2470,10 @@ static int for_each_prefixed_object_in_midx(\n }\n \n static int for_each_prefixed_object_in_pack(\n-\tstruct packfile_store *store,\n+\tstruct odb_source_packed *store,\n \tstruct packed_git *p,\n \tconst struct odb_for_each_object_options *opts,\n-\tstruct packfile_store_for_each_object_wrapper_data *data)\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n {\n \tuint32_t num, i, first = 0;\n \tint len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ?\n@@ -2519,9 +2519,9 @@ static int for_each_prefixed_object_in_pack(\n }\n \n static int packfile_store_for_each_prefixed_object(\n-\tstruct packfile_store *store,\n+\tstruct odb_source_packed *store,\n \tconst struct odb_for_each_object_options *opts,\n-\tstruct packfile_store_for_each_object_wrapper_data *data)\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n {\n \tstruct packfile_list_entry *e;\n \tstruct multi_pack_index *m;\n@@ -2566,13 +2566,13 @@ static int packfile_store_for_each_prefixed_object(\n \treturn ret;\n }\n \n-int packfile_store_for_each_object(struct packfile_store *store,\n+int packfile_store_for_each_object(struct odb_source_packed *store,\n \t\t\t\t   const struct object_info *request,\n \t\t\t\t   odb_for_each_object_cb cb,\n \t\t\t\t   void *cb_data,\n \t\t\t\t   const struct odb_for_each_object_options *opts)\n {\n-\tstruct packfile_store_for_each_object_wrapper_data data = {\n+\tstruct odb_source_packed_for_each_object_wrapper_data data = {\n \t\t.store = store,\n \t\t.request = request,\n \t\t.cb = cb,\n@@ -2707,7 +2707,7 @@ static void find_abbrev_len_for_pack(struct packed_git *p,\n \t*out = len;\n }\n \n-int packfile_store_find_abbrev_len(struct packfile_store *store,\n+int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \t\t\t\t   const struct object_id *oid,\n \t\t\t\t   unsigned min_len,\n \t\t\t\t   unsigned *out)\n@@ -2832,16 +2832,16 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-struct packfile_store *packfile_store_new(struct odb_source *source)\n+struct odb_source_packed *packfile_store_new(struct odb_source *source)\n {\n-\tstruct packfile_store *store;\n+\tstruct odb_source_packed *store;\n \tCALLOC_ARRAY(store, 1);\n \tstore->source = source;\n \tstrmap_init(&store->packs_by_path);\n \treturn store;\n }\n \n-void packfile_store_free(struct packfile_store *store)\n+void packfile_store_free(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n \t\tfree(e->pack);\n@@ -2851,7 +2851,7 @@ void packfile_store_free(struct packfile_store *store)\n \tfree(store);\n }\n \n-void packfile_store_close(struct packfile_store *store)\n+void packfile_store_close(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {\n \t\tif (e->pack->do_not_close)\n@@ -2988,7 +2988,7 @@ int packfile_read_object_stream(struct odb_read_stream **out,\n }\n \n int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct packfile_store *store,\n+\t\t\t\t      struct odb_source_packed *store,\n \t\t\t\t      const struct object_id *oid)\n {\n \tstruct pack_entry e;\ndiff --git a/packfile.h b/packfile.h\nindex 49d6bdecf6..9cec15bc50 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -79,7 +79,7 @@ struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n /*\n  * A store that manages packfiles for a given object database.\n  */\n-struct packfile_store {\n+struct odb_source_packed {\n \tstruct odb_source *source;\n \n \t/*\n@@ -138,19 +138,19 @@ struct packfile_store {\n  * Allocate and initialize a new empty packfile store for the given object\n  * database source.\n  */\n-struct packfile_store *packfile_store_new(struct odb_source *source);\n+struct odb_source_packed *packfile_store_new(struct odb_source *source);\n \n /*\n  * Free the packfile store and all its associated state. All packfiles\n  * tracked by the store will be closed.\n  */\n-void packfile_store_free(struct packfile_store *store);\n+void packfile_store_free(struct odb_source_packed *store);\n \n /*\n  * Close all packfiles associated with this store. The packfiles won't be\n  * free'd, so they can be re-opened at a later point in time.\n  */\n-void packfile_store_close(struct packfile_store *store);\n+void packfile_store_close(struct odb_source_packed *store);\n \n /*\n  * Prepare the packfile store by loading packfiles and multi-pack indices for\n@@ -159,7 +159,7 @@ void packfile_store_close(struct packfile_store *store);\n  * It shouldn't typically be necessary to call this function directly, as\n  * functions that access the store know to prepare it.\n  */\n-void packfile_store_prepare(struct packfile_store *store);\n+void packfile_store_prepare(struct odb_source_packed *store);\n \n /*\n  * Clear the packfile caches and try to look up any new packfiles that have\n@@ -167,20 +167,20 @@ void packfile_store_prepare(struct packfile_store *store);\n  *\n  * This function must be called under the `odb_read_lock()`.\n  */\n-void packfile_store_reprepare(struct packfile_store *store);\n+void packfile_store_reprepare(struct odb_source_packed *store);\n \n /*\n  * Add the pack to the store so that contained objects become accessible via\n  * the store. This moves ownership into the store.\n  */\n-void packfile_store_add_pack(struct packfile_store *store,\n+void packfile_store_add_pack(struct odb_source_packed *store,\n \t\t\t     struct packed_git *pack);\n \n /*\n  * Get all packs managed by the given store, including packfiles that are\n  * referenced by multi-pack indices.\n  */\n-struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store);\n+struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store);\n \n struct repo_for_each_pack_data {\n \tstruct odb_source *source;\n@@ -239,7 +239,7 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *\n \t     repo_for_each_pack_data_next(&eack_pack_data))\n \n int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct packfile_store *store,\n+\t\t\t\t      struct odb_source_packed *store,\n \t\t\t\t      const struct object_id *oid);\n \n /*\n@@ -248,7 +248,7 @@ int packfile_store_read_object_stream(struct odb_read_stream **out,\n  * not found, 0 if it was and read successfully, and a negative error code in\n  * case the object was corrupted.\n  */\n-int packfile_store_read_object_info(struct packfile_store *store,\n+int packfile_store_read_object_info(struct odb_source_packed *store,\n \t\t\t\t    const struct object_id *oid,\n \t\t\t\t    struct object_info *oi,\n \t\t\t\t    enum object_info_flags flags);\n@@ -258,10 +258,10 @@ int packfile_store_read_object_info(struct packfile_store *store,\n  * either the newly opened packfile or the preexisting packfile. Returns a\n  * `NULL` pointer in case the packfile could not be opened.\n  */\n-struct packed_git *packfile_store_load_pack(struct packfile_store *store,\n+struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \t\t\t\t\t    const char *idx_path, int local);\n \n-int packfile_store_freshen_object(struct packfile_store *store,\n+int packfile_store_freshen_object(struct odb_source_packed *store,\n \t\t\t\t  const struct object_id *oid);\n \n enum kept_pack_type {\n@@ -276,7 +276,7 @@ enum kept_pack_type {\n  *\n  * Return 0 on success, a negative error code otherwise.\n  */\n-int packfile_store_count_objects(struct packfile_store *store,\n+int packfile_store_count_objects(struct odb_source_packed *store,\n \t\t\t\t enum odb_count_objects_flags flags,\n \t\t\t\t unsigned long *out);\n \n@@ -285,7 +285,7 @@ int packfile_store_count_objects(struct packfile_store *store,\n  * combination of `kept_pack_type` flags. The cache is computed on demand and\n  * will be recomputed whenever the flags change.\n  */\n-struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store,\n+struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t\t       unsigned flags);\n \n struct pack_window {\n@@ -365,13 +365,13 @@ int for_each_object_in_pack(struct packed_git *p,\n  *\n  * The flags parameter is a combination of `odb_for_each_object_flags`.\n  */\n-int packfile_store_for_each_object(struct packfile_store *store,\n+int packfile_store_for_each_object(struct odb_source_packed *store,\n \t\t\t\t   const struct object_info *request,\n \t\t\t\t   odb_for_each_object_cb cb,\n \t\t\t\t   void *cb_data,\n \t\t\t\t   const struct odb_for_each_object_options *opts);\n \n-int packfile_store_find_abbrev_len(struct packfile_store *store,\n+int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \t\t\t\t   const struct object_id *oid,\n \t\t\t\t   unsigned min_len,\n \t\t\t\t   unsigned *out);\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545025","messageId":"20260609-pks-odb-source-packed-v2-2-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 02/17] packfile: split out packfile list logic","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:50:55Z","receivedAt":"2026-06-09T08:51:14Z","isPatch":true,"body":"In the next commit we're about to introduce the \"packed\" object database\nsource. This source will embed a packfile list, and consequently we'll\nhave to include \"packfile.h\" to make the struct definition available.\nThis will unfortunately lead to a cyclic dependency that we cannot\nresolve with a forward declaration.\n\nSplit out the code that relates to the packfile list into a separate\ncompilation unit so that both \"packfile.h\" and \"odb/source-packed.h\" can\ninclude it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Makefile        |  1 +\n meson.build     |  1 +\n packfile-list.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n packfile-list.h | 28 +++++++++++++++++++\n packfile.c      | 83 -------------------------------------------------------\n packfile.h      | 23 +--------------\n 6 files changed, 117 insertions(+), 105 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 0976a69b4c..ed1731548e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1233,6 +1233,7 @@ LIB_OBJS += pack-refs.o\n LIB_OBJS += pack-revindex.o\n LIB_OBJS += pack-write.o\n LIB_OBJS += packfile.o\n+LIB_OBJS += packfile-list.o\n LIB_OBJS += pager.o\n LIB_OBJS += parallel-checkout.o\n LIB_OBJS += parse.o\ndiff --git a/meson.build b/meson.build\nindex 3247697f74..12913fc948 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -421,6 +421,7 @@ libgit_sources = [\n   'pack-revindex.c',\n   'pack-write.c',\n   'packfile.c',\n+  'packfile-list.c',\n   'pager.c',\n   'parallel-checkout.c',\n   'parse.c',\ndiff --git a/packfile-list.c b/packfile-list.c\nnew file mode 100644\nindex 0000000000..01fb913abf\n--- /dev/null\n+++ b/packfile-list.c\n@@ -0,0 +1,86 @@\n+#include \"git-compat-util.h\"\n+#include \"packfile.h\"\n+#include \"packfile-list.h\"\n+\n+void packfile_list_clear(struct packfile_list *list)\n+{\n+\tstruct packfile_list_entry *e, *next;\n+\n+\tfor (e = list->head; e; e = next) {\n+\t\tnext = e->next;\n+\t\tfree(e);\n+\t}\n+\n+\tlist->head = list->tail = NULL;\n+}\n+\n+static struct packfile_list_entry *packfile_list_remove_internal(struct packfile_list *list,\n+\t\t\t\t\t\t\t\t struct packed_git *pack)\n+{\n+\tstruct packfile_list_entry *e, *prev;\n+\n+\tfor (e = list->head, prev = NULL; e; prev = e, e = e->next) {\n+\t\tif (e->pack != pack)\n+\t\t\tcontinue;\n+\n+\t\tif (prev)\n+\t\t\tprev->next = e->next;\n+\t\tif (list->head == e)\n+\t\t\tlist->head = e->next;\n+\t\tif (list->tail == e)\n+\t\t\tlist->tail = prev;\n+\n+\t\treturn e;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+void packfile_list_remove(struct packfile_list *list, struct packed_git *pack)\n+{\n+\tfree(packfile_list_remove_internal(list, pack));\n+}\n+\n+void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack)\n+{\n+\tstruct packfile_list_entry *entry;\n+\n+\tentry = packfile_list_remove_internal(list, pack);\n+\tif (!entry) {\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t\tentry->pack = pack;\n+\t}\n+\tentry->next = list->head;\n+\n+\tlist->head = entry;\n+\tif (!list->tail)\n+\t\tlist->tail = entry;\n+}\n+\n+void packfile_list_append(struct packfile_list *list, struct packed_git *pack)\n+{\n+\tstruct packfile_list_entry *entry;\n+\n+\tentry = packfile_list_remove_internal(list, pack);\n+\tif (!entry) {\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t\tentry->pack = pack;\n+\t}\n+\tentry->next = NULL;\n+\n+\tif (list->tail) {\n+\t\tlist->tail->next = entry;\n+\t\tlist->tail = entry;\n+\t} else {\n+\t\tlist->head = list->tail = entry;\n+\t}\n+}\n+\n+struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n+\t\t\t\t\t  const struct object_id *oid)\n+{\n+\tfor (; packs; packs = packs->next)\n+\t\tif (find_pack_entry_one(oid, packs->pack))\n+\t\t\treturn packs->pack;\n+\treturn NULL;\n+}\ndiff --git a/packfile-list.h b/packfile-list.h\nnew file mode 100644\nindex 0000000000..1b05e2aa36\n--- /dev/null\n+++ b/packfile-list.h\n@@ -0,0 +1,28 @@\n+#ifndef PACKFILE_LIST_H\n+#define PACKFILE_LIST_H\n+\n+struct object_id;\n+\n+struct packfile_list {\n+\tstruct packfile_list_entry *head, *tail;\n+};\n+\n+struct packfile_list_entry {\n+\tstruct packfile_list_entry *next;\n+\tstruct packed_git *pack;\n+};\n+\n+void packfile_list_clear(struct packfile_list *list);\n+void packfile_list_remove(struct packfile_list *list, struct packed_git *pack);\n+void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack);\n+void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n+\n+/*\n+ * Find the pack within the \"packs\" list whose index contains the object\n+ * \"oid\". For general object lookups, you probably don't want this; use\n+ * find_pack_entry() instead.\n+ */\n+struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n+\t\t\t\t\t  const struct object_id *oid);\n+\n+#endif\ndiff --git a/packfile.c b/packfile.c\nindex a2d768d0ae..27ea4a8436 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -48,89 +48,6 @@ static size_t pack_mapped;\n #define SZ_FMT PRIuMAX\n static inline uintmax_t sz_fmt(size_t s) { return s; }\n \n-void packfile_list_clear(struct packfile_list *list)\n-{\n-\tstruct packfile_list_entry *e, *next;\n-\n-\tfor (e = list->head; e; e = next) {\n-\t\tnext = e->next;\n-\t\tfree(e);\n-\t}\n-\n-\tlist->head = list->tail = NULL;\n-}\n-\n-static struct packfile_list_entry *packfile_list_remove_internal(struct packfile_list *list,\n-\t\t\t\t\t\t\t\t struct packed_git *pack)\n-{\n-\tstruct packfile_list_entry *e, *prev;\n-\n-\tfor (e = list->head, prev = NULL; e; prev = e, e = e->next) {\n-\t\tif (e->pack != pack)\n-\t\t\tcontinue;\n-\n-\t\tif (prev)\n-\t\t\tprev->next = e->next;\n-\t\tif (list->head == e)\n-\t\t\tlist->head = e->next;\n-\t\tif (list->tail == e)\n-\t\t\tlist->tail = prev;\n-\n-\t\treturn e;\n-\t}\n-\n-\treturn NULL;\n-}\n-\n-void packfile_list_remove(struct packfile_list *list, struct packed_git *pack)\n-{\n-\tfree(packfile_list_remove_internal(list, pack));\n-}\n-\n-void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack)\n-{\n-\tstruct packfile_list_entry *entry;\n-\n-\tentry = packfile_list_remove_internal(list, pack);\n-\tif (!entry) {\n-\t\tentry = xmalloc(sizeof(*entry));\n-\t\tentry->pack = pack;\n-\t}\n-\tentry->next = list->head;\n-\n-\tlist->head = entry;\n-\tif (!list->tail)\n-\t\tlist->tail = entry;\n-}\n-\n-void packfile_list_append(struct packfile_list *list, struct packed_git *pack)\n-{\n-\tstruct packfile_list_entry *entry;\n-\n-\tentry = packfile_list_remove_internal(list, pack);\n-\tif (!entry) {\n-\t\tentry = xmalloc(sizeof(*entry));\n-\t\tentry->pack = pack;\n-\t}\n-\tentry->next = NULL;\n-\n-\tif (list->tail) {\n-\t\tlist->tail->next = entry;\n-\t\tlist->tail = entry;\n-\t} else {\n-\t\tlist->head = list->tail = entry;\n-\t}\n-}\n-\n-struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n-\t\t\t\t\t  const struct object_id *oid)\n-{\n-\tfor (; packs; packs = packs->next)\n-\t\tif (find_pack_entry_one(oid, packs->pack))\n-\t\t\treturn packs->pack;\n-\treturn NULL;\n-}\n-\n void pack_report(struct repository *repo)\n {\n \tfprintf(stderr,\ndiff --git a/packfile.h b/packfile.h\nindex 9cec15bc50..4e3d701a3a 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -6,6 +6,7 @@\n #include \"odb.h\"\n #include \"odb/source-files.h\"\n #include \"oidset.h\"\n+#include \"packfile-list.h\"\n #include \"repository.h\"\n #include \"strmap.h\"\n \n@@ -54,28 +55,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-struct packfile_list {\n-\tstruct packfile_list_entry *head, *tail;\n-};\n-\n-struct packfile_list_entry {\n-\tstruct packfile_list_entry *next;\n-\tstruct packed_git *pack;\n-};\n-\n-void packfile_list_clear(struct packfile_list *list);\n-void packfile_list_remove(struct packfile_list *list, struct packed_git *pack);\n-void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack);\n-void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n-\n-/*\n- * Find the pack within the \"packs\" list whose index contains the object\n- * \"oid\". For general object lookups, you probably don't want this; use\n- * find_pack_entry() instead.\n- */\n-struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n-\t\t\t\t\t  const struct object_id *oid);\n-\n /*\n  * A store that manages packfiles for a given object database.\n  */\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545026","messageId":"20260609-pks-odb-source-packed-v2-3-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 03/17] packfile: move packed source into \"odb/\" subsystem","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:50:56Z","receivedAt":"2026-06-09T08:51:16Z","isPatch":true,"body":"In subsequent patches we'll be turning `struct odb_source_packed` into a\nproper `struct odb_source`. As a first step towards this goal, move its\nstruct out of \"packfile.{c,h}\" and into \"odb/source-packed.{c,h}\".\n\nThis detaches the implementation of the packfile object source from the\ngeneric packfile code, following the same convention already used by the\n\"files\" and \"in-memory\" sources.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Makefile            |  1 +\n meson.build         |  1 +\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 11 ++++++++\n odb/source-packed.h | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.c          |  9 -------\n packfile.h          | 66 +-----------------------------------------------\n 7 files changed, 87 insertions(+), 75 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex ed1731548e..113fa45993 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1218,6 +1218,7 @@ LIB_OBJS += odb/source.o\n LIB_OBJS += odb/source-files.o\n LIB_OBJS += odb/source-inmemory.o\n LIB_OBJS += odb/source-loose.o\n+LIB_OBJS += odb/source-packed.o\n LIB_OBJS += odb/streaming.o\n LIB_OBJS += odb/transaction.o\n LIB_OBJS += oid-array.o\ndiff --git a/meson.build b/meson.build\nindex 12913fc948..ca235801cf 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -406,6 +406,7 @@ libgit_sources = [\n   'odb/source-files.c',\n   'odb/source-inmemory.c',\n   'odb/source-loose.c',\n+  'odb/source-packed.c',\n   'odb/streaming.c',\n   'odb/transaction.c',\n   'oid-array.c',\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 5bdd042922..191562f316 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tCALLOC_ARRAY(files, 1);\n \todb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);\n \tfiles->loose = odb_source_loose_new(odb, path, local);\n-\tfiles->packed = packfile_store_new(&files->base);\n+\tfiles->packed = odb_source_packed_new(&files->base);\n \n \tfiles->base.free = odb_source_files_free;\n \tfiles->base.close = odb_source_files_close;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nnew file mode 100644\nindex 0000000000..1e94b47ea0\n--- /dev/null\n+++ b/odb/source-packed.c\n@@ -0,0 +1,11 @@\n+#include \"git-compat-util.h\"\n+#include \"odb/source-packed.h\"\n+\n+struct odb_source_packed *odb_source_packed_new(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *store;\n+\tCALLOC_ARRAY(store, 1);\n+\tstore->source = source;\n+\tstrmap_init(&store->packs_by_path);\n+\treturn store;\n+}\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nnew file mode 100644\nindex 0000000000..327be4ad65\n--- /dev/null\n+++ b/odb/source-packed.h\n@@ -0,0 +1,72 @@\n+#ifndef ODB_SOURCE_PACKED_H\n+#define ODB_SOURCE_PACKED_H\n+\n+#include \"odb/source.h\"\n+#include \"packfile-list.h\"\n+#include \"strmap.h\"\n+\n+/*\n+ * A store that manages packfiles for a given object database.\n+ */\n+struct odb_source_packed {\n+\tstruct odb_source *source;\n+\n+\t/*\n+\t * The list of packfiles in the order in which they have been most\n+\t * recently used.\n+\t */\n+\tstruct packfile_list packs;\n+\n+\t/*\n+\t * Cache of packfiles which are marked as \"kept\", either because there\n+\t * is an on-disk \".keep\" file or because they are marked as \"kept\" in\n+\t * memory.\n+\t *\n+\t * Should not be accessed directly, but via\n+\t * `packfile_store_get_kept_pack_cache()`. The list of packs gets\n+\t * invalidated when the stored flags and the flags passed to\n+\t * `packfile_store_get_kept_pack_cache()` mismatch.\n+\t */\n+\tstruct {\n+\t\tstruct packed_git **packs;\n+\t\tunsigned flags;\n+\t} kept_cache;\n+\n+\t/* The multi-pack index that belongs to this specific packfile store. */\n+\tstruct multi_pack_index *midx;\n+\n+\t/*\n+\t * A map of packfile names to packed_git structs for tracking which\n+\t * packs have been loaded already.\n+\t */\n+\tstruct strmap packs_by_path;\n+\n+\t/*\n+\t * Whether packfiles have already been populated with this store's\n+\t * packs.\n+\t */\n+\tbool initialized;\n+\n+\t/*\n+\t * Usually, packfiles will be reordered to the front of the `packs`\n+\t * list whenever an object is looked up via them. This has the effect\n+\t * that packs that contain a lot of accessed objects will be located\n+\t * towards the front.\n+\t *\n+\t * This is usually desireable, but there are exceptions. One exception\n+\t * is when the looking up multiple objects in a loop for each packfile.\n+\t * In that case, we may easily end up with an infinite loop as the\n+\t * packfiles get reordered to the front repeatedly.\n+\t *\n+\t * Setting this field to `true` thus disables these reorderings.\n+\t */\n+\tbool skip_mru_updates;\n+};\n+\n+/*\n+ * Allocate and initialize a new empty packfile store for the given object\n+ * database source.\n+ */\n+struct odb_source_packed *odb_source_packed_new(struct odb_source *source);\n+\n+#endif\ndiff --git a/packfile.c b/packfile.c\nindex 27ea4a8436..99be5789ef 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2749,15 +2749,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-struct odb_source_packed *packfile_store_new(struct odb_source *source)\n-{\n-\tstruct odb_source_packed *store;\n-\tCALLOC_ARRAY(store, 1);\n-\tstore->source = source;\n-\tstrmap_init(&store->packs_by_path);\n-\treturn store;\n-}\n-\n void packfile_store_free(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\ndiff --git a/packfile.h b/packfile.h\nindex 4e3d701a3a..2d0bb7adbe 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -5,10 +5,10 @@\n #include \"object.h\"\n #include \"odb.h\"\n #include \"odb/source-files.h\"\n+#include \"odb/source-packed.h\"\n #include \"oidset.h\"\n #include \"packfile-list.h\"\n #include \"repository.h\"\n-#include \"strmap.h\"\n \n /* in odb.h */\n struct object_info;\n@@ -55,70 +55,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-/*\n- * A store that manages packfiles for a given object database.\n- */\n-struct odb_source_packed {\n-\tstruct odb_source *source;\n-\n-\t/*\n-\t * The list of packfiles in the order in which they have been most\n-\t * recently used.\n-\t */\n-\tstruct packfile_list packs;\n-\n-\t/*\n-\t * Cache of packfiles which are marked as \"kept\", either because there\n-\t * is an on-disk \".keep\" file or because they are marked as \"kept\" in\n-\t * memory.\n-\t *\n-\t * Should not be accessed directly, but via\n-\t * `packfile_store_get_kept_pack_cache()`. The list of packs gets\n-\t * invalidated when the stored flags and the flags passed to\n-\t * `packfile_store_get_kept_pack_cache()` mismatch.\n-\t */\n-\tstruct {\n-\t\tstruct packed_git **packs;\n-\t\tunsigned flags;\n-\t} kept_cache;\n-\n-\t/* The multi-pack index that belongs to this specific packfile store. */\n-\tstruct multi_pack_index *midx;\n-\n-\t/*\n-\t * A map of packfile names to packed_git structs for tracking which\n-\t * packs have been loaded already.\n-\t */\n-\tstruct strmap packs_by_path;\n-\n-\t/*\n-\t * Whether packfiles have already been populated with this store's\n-\t * packs.\n-\t */\n-\tbool initialized;\n-\n-\t/*\n-\t * Usually, packfiles will be reordered to the front of the `packs`\n-\t * list whenever an object is looked up via them. This has the effect\n-\t * that packs that contain a lot of accessed objects will be located\n-\t * towards the front.\n-\t *\n-\t * This is usually desireable, but there are exceptions. One exception\n-\t * is when the looking up multiple objects in a loop for each packfile.\n-\t * In that case, we may easily end up with an infinite loop as the\n-\t * packfiles get reordered to the front repeatedly.\n-\t *\n-\t * Setting this field to `true` thus disables these reorderings.\n-\t */\n-\tbool skip_mru_updates;\n-};\n-\n-/*\n- * Allocate and initialize a new empty packfile store for the given object\n- * database source.\n- */\n-struct odb_source_packed *packfile_store_new(struct odb_source *source);\n-\n /*\n  * Free the packfile store and all its associated state. All packfiles\n  * tracked by the store will be closed.\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545027","messageId":"20260609-pks-odb-source-packed-v2-4-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 04/17] odb/source-packed: store pointer to \"files\" instead of generic source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:50:57Z","receivedAt":"2026-06-09T08:51:19Z","isPatch":true,"body":"The `struct odb_source_packed` holds a pointer to its owning parent\nsource. The way that Git is currently structured, this parent is always\nthe \"files\" source. In subsequent commits we're going to detangle that\nso that the \"packed\" source doesn't have any owning parent source at\nall, which makes it usable as a completely standalone source.\n\nDetangling this mess is somewhat intricate though, and is made even more\nintricate because it's not always clear which kind of source one is\nholding at a specific point in time -- either the parent \"files\" source,\nor the child \"packed\" source.\n\nMake this relationship more explicit by storing a pointer to the \"files\"\nsource instead of storing a pointer to a generic `struct odb_source`.\nThis will help make subsequent steps a bit clearer.\n\nNote that this is a temporary step, only. At the end of this series\nwe will have dropped the parent pointer completely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c |  4 ++--\n odb/source-packed.h |  4 ++--\n packfile.c          | 12 ++++++------\n 4 files changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 191562f316..e04525fb08 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tCALLOC_ARRAY(files, 1);\n \todb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);\n \tfiles->loose = odb_source_loose_new(odb, path, local);\n-\tfiles->packed = odb_source_packed_new(&files->base);\n+\tfiles->packed = odb_source_packed_new(files);\n \n \tfiles->base.free = odb_source_files_free;\n \tfiles->base.close = odb_source_files_close;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 1e94b47ea0..12e785be48 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,11 +1,11 @@\n #include \"git-compat-util.h\"\n #include \"odb/source-packed.h\"\n \n-struct odb_source_packed *odb_source_packed_new(struct odb_source *source)\n+struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n {\n \tstruct odb_source_packed *store;\n \tCALLOC_ARRAY(store, 1);\n-\tstore->source = source;\n+\tstore->files = parent;\n \tstrmap_init(&store->packs_by_path);\n \treturn store;\n }\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 327be4ad65..3c2d229a17 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -9,7 +9,7 @@\n  * A store that manages packfiles for a given object database.\n  */\n struct odb_source_packed {\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *files;\n \n \t/*\n \t * The list of packfiles in the order in which they have been most\n@@ -67,6 +67,6 @@ struct odb_source_packed {\n  * Allocate and initialize a new empty packfile store for the given object\n  * database source.\n  */\n-struct odb_source_packed *odb_source_packed_new(struct odb_source *source);\n+struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent);\n \n #endif\ndiff --git a/packfile.c b/packfile.c\nindex 99be5789ef..862a24ad49 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -802,7 +802,7 @@ struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \n \tp = strmap_get(&store->packs_by_path, key.buf);\n \tif (!p) {\n-\t\tp = add_packed_git(store->source->odb->repo, idx_path,\n+\t\tp = add_packed_git(store->files->base.odb->repo, idx_path,\n \t\t\t\t   strlen(idx_path), local);\n \t\tif (p)\n \t\t\tpackfile_store_add_pack(store, p);\n@@ -990,8 +990,8 @@ void packfile_store_prepare(struct odb_source_packed *store)\n \tif (store->initialized)\n \t\treturn;\n \n-\tprepare_multi_pack_index_one(store->source);\n-\tprepare_packed_git_one(store->source);\n+\tprepare_multi_pack_index_one(&store->files->base);\n+\tprepare_packed_git_one(&store->files->base);\n \n \tsort_packs(&store->packs.head, sort_pack);\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n@@ -1029,7 +1029,7 @@ int packfile_store_count_objects(struct odb_source_packed *store,\n \tunsigned long count = 0;\n \tint ret;\n \n-\tm = get_multi_pack_index(store->source);\n+\tm = get_multi_pack_index(&store->files->base);\n \tif (m)\n \t\tcount += m->num_objects + m->num_objects_in_base;\n \n@@ -2450,7 +2450,7 @@ static int packfile_store_for_each_prefixed_object(\n \n \tstore->skip_mru_updates = true;\n \n-\tm = get_multi_pack_index(store->source);\n+\tm = get_multi_pack_index(&store->files->base);\n \tif (m) {\n \t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n \t\tif (ret)\n@@ -2632,7 +2632,7 @@ int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \tstruct packfile_list_entry *e;\n \tstruct multi_pack_index *m;\n \n-\tm = get_multi_pack_index(store->source);\n+\tm = get_multi_pack_index(&store->files->base);\n \tif (m)\n \t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n \n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545028","messageId":"20260609-pks-odb-source-packed-v2-5-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 05/17] odb/source-packed: start converting to a proper `struct odb_source`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:50:58Z","receivedAt":"2026-06-09T08:51:21Z","isPatch":true,"body":"Start converting `struct odb_source_packed` into a proper pluggable\n`struct odb_source` by embedding the base struct and assigning it the\nnew `ODB_SOURCE_PACKED` type. Furthermore, wire up lifecycle management\nof this source by implementing the `free` callback and taking ownership\nof the chdir notifications.\n\nNote that the packed source is not yet functional as a standalone `struct\nodb_source`, as it's missing all of the callback implementations. These\nwill be wired up in subsequent commits.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 49 ++++++++++++++++++++++++++++++++++++++++++++-----\n odb/source-packed.h | 12 ++++++++++++\n odb/source.h        |  3 +++\n packfile.c          | 10 ----------\n packfile.h          |  6 ------\n 6 files changed, 60 insertions(+), 22 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex e04525fb08..3608808e7c 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -29,7 +29,7 @@ static void odb_source_files_free(struct odb_source *source)\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \tchdir_notify_unregister(NULL, odb_source_files_reparent, files);\n \todb_source_free(&files->loose->base);\n-\tpackfile_store_free(files->packed);\n+\todb_source_free(&files->packed->base);\n \todb_source_release(&files->base);\n \tfree(files);\n }\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 12e785be48..f81a990cbd 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,11 +1,50 @@\n #include \"git-compat-util.h\"\n+#include \"abspath.h\"\n+#include \"chdir-notify.h\"\n #include \"odb/source-packed.h\"\n+#include \"packfile.h\"\n+\n+static void odb_source_packed_reparent(const char *name UNUSED,\n+\t\t\t\t       const char *old_cwd,\n+\t\t\t\t       const char *new_cwd,\n+\t\t\t\t       void *cb_data)\n+{\n+\tstruct odb_source_packed *packed = cb_data;\n+\tchar *path = reparent_relative_path(old_cwd, new_cwd,\n+\t\t\t\t\t    packed->base.path);\n+\tfree(packed->base.path);\n+\tpacked->base.path = path;\n+}\n+\n+static void odb_source_packed_free(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\n+\tchdir_notify_unregister(NULL, odb_source_packed_reparent, packed);\n+\n+\tfor (struct packfile_list_entry *e = packed->packs.head; e; e = e->next)\n+\t\tfree(e->pack);\n+\tpackfile_list_clear(&packed->packs);\n+\n+\tstrmap_clear(&packed->packs_by_path, 0);\n+\todb_source_release(&packed->base);\n+\tfree(packed);\n+}\n \n struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n {\n-\tstruct odb_source_packed *store;\n-\tCALLOC_ARRAY(store, 1);\n-\tstore->files = parent;\n-\tstrmap_init(&store->packs_by_path);\n-\treturn store;\n+\tstruct odb_source_packed *packed;\n+\n+\tCALLOC_ARRAY(packed, 1);\n+\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n+\t\t\tparent->base.path, parent->base.local);\n+\tpacked->files = parent;\n+\tstrmap_init(&packed->packs_by_path);\n+\n+\tpacked->base.free = odb_source_packed_free;\n+\n+\tif (!is_absolute_path(parent->base.path))\n+\t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n+\n+\treturn packed;\n }\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 3c2d229a17..68e64cabab 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -9,6 +9,7 @@\n  * A store that manages packfiles for a given object database.\n  */\n struct odb_source_packed {\n+\tstruct odb_source base;\n \tstruct odb_source_files *files;\n \n \t/*\n@@ -69,4 +70,15 @@ struct odb_source_packed {\n  */\n struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent);\n \n+/*\n+ * Cast the given object database source to the packed backend. This will cause\n+ * a BUG in case the source doesn't use this backend.\n+ */\n+static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_source *source)\n+{\n+\tif (source->type != ODB_SOURCE_PACKED)\n+\t\tBUG(\"trying to downcast source of type '%d' to packed\", source->type);\n+\treturn container_of(source, struct odb_source_packed, base);\n+}\n+\n #endif\ndiff --git a/odb/source.h b/odb/source.h\nindex 8bcb67787e..6865e1f71a 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -17,6 +17,9 @@ enum odb_source_type {\n \t/* The \"loose\" backend that uses loose objects, only. */\n \tODB_SOURCE_LOOSE,\n \n+\t/* The \"packed\" backend that uses packfiles. */\n+\tODB_SOURCE_PACKED,\n+\n \t/* The \"in-memory\" backend that stores objects in memory. */\n \tODB_SOURCE_INMEMORY,\n };\ndiff --git a/packfile.c b/packfile.c\nindex 862a24ad49..6d492216de 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2749,16 +2749,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-void packfile_store_free(struct odb_source_packed *store)\n-{\n-\tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n-\t\tfree(e->pack);\n-\tpackfile_list_clear(&store->packs);\n-\n-\tstrmap_clear(&store->packs_by_path, 0);\n-\tfree(store);\n-}\n-\n void packfile_store_close(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {\ndiff --git a/packfile.h b/packfile.h\nindex 2d0bb7adbe..e8bc9349f8 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -55,12 +55,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-/*\n- * Free the packfile store and all its associated state. All packfiles\n- * tracked by the store will be closed.\n- */\n-void packfile_store_free(struct odb_source_packed *store);\n-\n /*\n  * Close all packfiles associated with this store. The packfiles won't be\n  * free'd, so they can be re-opened at a later point in time.\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545029","messageId":"20260609-pks-odb-source-packed-v2-6-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 06/17] odb/source-packed: wire up `close()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:50:59Z","receivedAt":"2026-06-09T08:51:23Z","isPatch":true,"body":"Wire up a new `close()` callback for the packed source and call it from\nthe \"files\" source via the generic `odb_source_close()` interface.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 16 ++++++++++++++++\n packfile.c          | 12 ------------\n packfile.h          |  6 ------\n 4 files changed, 17 insertions(+), 19 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 3608808e7c..9b0fa9ccdc 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -38,7 +38,7 @@ static void odb_source_files_close(struct odb_source *source)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \todb_source_close(&files->loose->base);\n-\tpackfile_store_close(files->packed);\n+\todb_source_close(&files->packed->base);\n }\n \n static void odb_source_files_reprepare(struct odb_source *source)\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex f81a990cbd..74805be1dd 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n+#include \"midx.h\"\n #include \"odb/source-packed.h\"\n #include \"packfile.h\"\n \n@@ -16,6 +17,20 @@ static void odb_source_packed_reparent(const char *name UNUSED,\n \tpacked->base.path = path;\n }\n \n+static void odb_source_packed_close(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\n+\tfor (struct packfile_list_entry *e = packed->packs.head; e; e = e->next) {\n+\t\tif (e->pack->do_not_close)\n+\t\t\tBUG(\"want to close pack marked 'do-not-close'\");\n+\t\tclose_pack(e->pack);\n+\t}\n+\tif (packed->midx)\n+\t\tclose_midx(packed->midx);\n+\tpacked->midx = NULL;\n+}\n+\n static void odb_source_packed_free(struct odb_source *source)\n {\n \tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n@@ -42,6 +57,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tstrmap_init(&packed->packs_by_path);\n \n \tpacked->base.free = odb_source_packed_free;\n+\tpacked->base.close = odb_source_packed_close;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 6d492216de..e5386145a7 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2749,18 +2749,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-void packfile_store_close(struct odb_source_packed *store)\n-{\n-\tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {\n-\t\tif (e->pack->do_not_close)\n-\t\t\tBUG(\"want to close pack marked 'do-not-close'\");\n-\t\tclose_pack(e->pack);\n-\t}\n-\tif (store->midx)\n-\t\tclose_midx(store->midx);\n-\tstore->midx = NULL;\n-}\n-\n struct odb_packed_read_stream {\n \tstruct odb_read_stream base;\n \tstruct packed_git *pack;\ndiff --git a/packfile.h b/packfile.h\nindex e8bc9349f8..9dc3a13112 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -55,12 +55,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-/*\n- * Close all packfiles associated with this store. The packfiles won't be\n- * free'd, so they can be re-opened at a later point in time.\n- */\n-void packfile_store_close(struct odb_source_packed *store);\n-\n /*\n  * Prepare the packfile store by loading packfiles and multi-pack indices for\n  * all alternates. This becomes a no-op if the store is already prepared.\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545030","messageId":"20260609-pks-odb-source-packed-v2-7-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 07/17] odb/source-packed: wire up `reprepare()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:00Z","receivedAt":"2026-06-09T08:51:26Z","isPatch":true,"body":"Move the logic to prepare and reprepare the \"packed\" source into\n\"odb/source-packed.c\" and wire it up as the `reprepare()` callback.\n\nNote that \"preparing\" a source is not yet generic. Eventually, it would\nprobably make sense to turn the existing `reprepare()` callback into a\n`prepare()` callback with an optional flag to force re-preparing. But\nthis step will be handled in a separate patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c      |   2 +-\n midx.c              |   2 +-\n odb/source-files.c  |   2 +-\n odb/source-packed.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++\n odb/source-packed.h |   9 +++\n packfile.c          | 160 +---------------------------------------------------\n packfile.h          |  17 ------\n 7 files changed, 172 insertions(+), 177 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 6a09571903..8080d1bf5e 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -1363,7 +1363,7 @@ int cmd_grep(int argc,\n \t\t\todb_prepare_alternates(the_repository->objects);\n \t\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n \t\t\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\t\t\tpackfile_store_prepare(files->packed);\n+\t\t\t\todb_source_packed_prepare(files->packed);\n \t\t\t}\n \t\t}\n \ndiff --git a/midx.c b/midx.c\nindex efbfbb13f4..00bbd137b2 100644\n--- a/midx.c\n+++ b/midx.c\n@@ -102,7 +102,7 @@ static int midx_read_object_offsets(const unsigned char *chunk_start,\n struct multi_pack_index *get_multi_pack_index(struct odb_source *source)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tpackfile_store_prepare(files->packed);\n+\todb_source_packed_prepare(files->packed);\n \treturn files->packed->midx;\n }\n \ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 9b0fa9ccdc..7b1e0ac565 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -45,7 +45,7 @@ static void odb_source_files_reprepare(struct odb_source *source)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \todb_source_reprepare(&files->loose->base);\n-\tpackfile_store_reprepare(files->packed);\n+\todb_source_reprepare(&files->packed->base);\n }\n \n static int odb_source_files_read_object_info(struct odb_source *source,\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 74805be1dd..e8e2e5bb48 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,10 +1,166 @@\n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n+#include \"dir.h\"\n+#include \"mergesort.h\"\n #include \"midx.h\"\n #include \"odb/source-packed.h\"\n #include \"packfile.h\"\n \n+void (*report_garbage)(unsigned seen_bits, const char *path);\n+\n+static void report_helper(const struct string_list *list,\n+\t\t\t  int seen_bits, int first, int last)\n+{\n+\tif (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX))\n+\t\treturn;\n+\n+\tfor (; first < last; first++)\n+\t\treport_garbage(seen_bits, list->items[first].string);\n+}\n+\n+static void report_pack_garbage(struct string_list *list)\n+{\n+\tint baselen = -1, first = 0, seen_bits = 0;\n+\n+\tif (!report_garbage)\n+\t\treturn;\n+\n+\tstring_list_sort(list);\n+\n+\tfor (size_t i = 0; i < list->nr; i++) {\n+\t\tconst char *path = list->items[i].string;\n+\t\tif (baselen != -1 &&\n+\t\t    strncmp(path, list->items[first].string, baselen)) {\n+\t\t\treport_helper(list, seen_bits, first, i);\n+\t\t\tbaselen = -1;\n+\t\t\tseen_bits = 0;\n+\t\t}\n+\t\tif (baselen == -1) {\n+\t\t\tconst char *dot = strrchr(path, '.');\n+\t\t\tif (!dot) {\n+\t\t\t\treport_garbage(PACKDIR_FILE_GARBAGE, path);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tbaselen = dot - path + 1;\n+\t\t\tfirst = i;\n+\t\t}\n+\t\tif (!strcmp(path + baselen, \"pack\"))\n+\t\t\tseen_bits |= 1;\n+\t\telse if (!strcmp(path + baselen, \"idx\"))\n+\t\t\tseen_bits |= 2;\n+\t}\n+\treport_helper(list, seen_bits, first, list->nr);\n+}\n+\n+struct prepare_pack_data {\n+\tstruct odb_source *source;\n+\tstruct string_list *garbage;\n+};\n+\n+static void prepare_pack(const char *full_name, size_t full_name_len,\n+\t\t\t const char *file_name, void *_data)\n+{\n+\tstruct prepare_pack_data *data = (struct prepare_pack_data *)_data;\n+\tstruct odb_source_files *files = odb_source_files_downcast(data->source);\n+\tsize_t base_len = full_name_len;\n+\n+\tif (strip_suffix_mem(full_name, &base_len, \".idx\") &&\n+\t    !(files->packed->midx &&\n+\t      midx_contains_pack(files->packed->midx, file_name))) {\n+\t\tchar *trimmed_path = xstrndup(full_name, full_name_len);\n+\t\tpackfile_store_load_pack(files->packed,\n+\t\t\t\t\t trimmed_path, data->source->local);\n+\t\tfree(trimmed_path);\n+\t}\n+\n+\tif (!report_garbage)\n+\t\treturn;\n+\n+\tif (!strcmp(file_name, \"multi-pack-index\") ||\n+\t    !strcmp(file_name, \"multi-pack-index.d\"))\n+\t\treturn;\n+\tif (starts_with(file_name, \"multi-pack-index\") &&\n+\t    (ends_with(file_name, \".bitmap\") || ends_with(file_name, \".rev\")))\n+\t\treturn;\n+\tif (ends_with(file_name, \".idx\") ||\n+\t    ends_with(file_name, \".rev\") ||\n+\t    ends_with(file_name, \".pack\") ||\n+\t    ends_with(file_name, \".bitmap\") ||\n+\t    ends_with(file_name, \".keep\") ||\n+\t    ends_with(file_name, \".promisor\") ||\n+\t    ends_with(file_name, \".mtimes\"))\n+\t\tstring_list_append(data->garbage, full_name);\n+\telse\n+\t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n+}\n+\n+static void prepare_packed_git_one(struct odb_source *source)\n+{\n+\tstruct string_list garbage = STRING_LIST_INIT_DUP;\n+\tstruct prepare_pack_data data = {\n+\t\t.source = source,\n+\t\t.garbage = &garbage,\n+\t};\n+\n+\tfor_each_file_in_pack_dir(source->path, prepare_pack, &data);\n+\n+\treport_pack_garbage(data.garbage);\n+\tstring_list_clear(data.garbage, 0);\n+}\n+\n+DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next);\n+\n+static int sort_pack(const struct packfile_list_entry *a,\n+\t\t     const struct packfile_list_entry *b)\n+{\n+\tint st;\n+\n+\t/*\n+\t * Local packs tend to contain objects specific to our\n+\t * variant of the project than remote ones.  In addition,\n+\t * remote ones could be on a network mounted filesystem.\n+\t * Favor local ones for these reasons.\n+\t */\n+\tst = a->pack->pack_local - b->pack->pack_local;\n+\tif (st)\n+\t\treturn -st;\n+\n+\t/*\n+\t * Younger packs tend to contain more recent objects,\n+\t * and more recent objects tend to get accessed more\n+\t * often.\n+\t */\n+\tif (a->pack->mtime < b->pack->mtime)\n+\t\treturn 1;\n+\telse if (a->pack->mtime == b->pack->mtime)\n+\t\treturn 0;\n+\treturn -1;\n+}\n+\n+void odb_source_packed_prepare(struct odb_source_packed *source)\n+{\n+\tif (source->initialized)\n+\t\treturn;\n+\n+\tprepare_multi_pack_index_one(&source->files->base);\n+\tprepare_packed_git_one(&source->files->base);\n+\n+\tsort_packs(&source->packs.head, sort_pack);\n+\tfor (struct packfile_list_entry *e = source->packs.head; e; e = e->next)\n+\t\tif (!e->next)\n+\t\t\tsource->packs.tail = e;\n+\n+\tsource->initialized = true;\n+}\n+\n+static void odb_source_packed_reprepare(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tpacked->initialized = false;\n+\todb_source_packed_prepare(packed);\n+}\n+\n static void odb_source_packed_reparent(const char *name UNUSED,\n \t\t\t\t       const char *old_cwd,\n \t\t\t\t       const char *new_cwd,\n@@ -58,6 +214,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \n \tpacked->base.free = odb_source_packed_free;\n \tpacked->base.close = odb_source_packed_close;\n+\tpacked->base.reprepare = odb_source_packed_reprepare;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 68e64cabab..9d4796261a 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -81,4 +81,13 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so\n \treturn container_of(source, struct odb_source_packed, base);\n }\n \n+/*\n+ * Prepare the source by loading packfiles and multi-pack indices for\n+ * all alternates. This becomes a no-op if the source is already prepared.\n+ *\n+ * It shouldn't typically be necessary to call this function directly, as\n+ * functions that access the source know to prepare it.\n+ */\n+void odb_source_packed_prepare(struct odb_source_packed *source);\n+\n #endif\ndiff --git a/packfile.c b/packfile.c\nindex e5386145a7..65631f674f 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -8,7 +8,6 @@\n #include \"pack.h\"\n #include \"repository.h\"\n #include \"dir.h\"\n-#include \"mergesort.h\"\n #include \"packfile.h\"\n #include \"delta.h\"\n #include \"hash-lookup.h\"\n@@ -812,52 +811,6 @@ struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \treturn p;\n }\n \n-void (*report_garbage)(unsigned seen_bits, const char *path);\n-\n-static void report_helper(const struct string_list *list,\n-\t\t\t  int seen_bits, int first, int last)\n-{\n-\tif (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX))\n-\t\treturn;\n-\n-\tfor (; first < last; first++)\n-\t\treport_garbage(seen_bits, list->items[first].string);\n-}\n-\n-static void report_pack_garbage(struct string_list *list)\n-{\n-\tint i, baselen = -1, first = 0, seen_bits = 0;\n-\n-\tif (!report_garbage)\n-\t\treturn;\n-\n-\tstring_list_sort(list);\n-\n-\tfor (i = 0; i < list->nr; i++) {\n-\t\tconst char *path = list->items[i].string;\n-\t\tif (baselen != -1 &&\n-\t\t    strncmp(path, list->items[first].string, baselen)) {\n-\t\t\treport_helper(list, seen_bits, first, i);\n-\t\t\tbaselen = -1;\n-\t\t\tseen_bits = 0;\n-\t\t}\n-\t\tif (baselen == -1) {\n-\t\t\tconst char *dot = strrchr(path, '.');\n-\t\t\tif (!dot) {\n-\t\t\t\treport_garbage(PACKDIR_FILE_GARBAGE, path);\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t\tbaselen = dot - path + 1;\n-\t\t\tfirst = i;\n-\t\t}\n-\t\tif (!strcmp(path + baselen, \"pack\"))\n-\t\t\tseen_bits |= 1;\n-\t\telse if (!strcmp(path + baselen, \"idx\"))\n-\t\t\tseen_bits |= 2;\n-\t}\n-\treport_helper(list, seen_bits, first, list->nr);\n-}\n-\n void for_each_file_in_pack_subdir(const char *objdir,\n \t\t\t\t  const char *subdir,\n \t\t\t\t  each_file_in_pack_dir_fn fn,\n@@ -900,116 +853,9 @@ void for_each_file_in_pack_dir(const char *objdir,\n \tfor_each_file_in_pack_subdir(objdir, NULL, fn, data);\n }\n \n-struct prepare_pack_data {\n-\tstruct odb_source *source;\n-\tstruct string_list *garbage;\n-};\n-\n-static void prepare_pack(const char *full_name, size_t full_name_len,\n-\t\t\t const char *file_name, void *_data)\n-{\n-\tstruct prepare_pack_data *data = (struct prepare_pack_data *)_data;\n-\tstruct odb_source_files *files = odb_source_files_downcast(data->source);\n-\tsize_t base_len = full_name_len;\n-\n-\tif (strip_suffix_mem(full_name, &base_len, \".idx\") &&\n-\t    !(files->packed->midx &&\n-\t      midx_contains_pack(files->packed->midx, file_name))) {\n-\t\tchar *trimmed_path = xstrndup(full_name, full_name_len);\n-\t\tpackfile_store_load_pack(files->packed,\n-\t\t\t\t\t trimmed_path, data->source->local);\n-\t\tfree(trimmed_path);\n-\t}\n-\n-\tif (!report_garbage)\n-\t\treturn;\n-\n-\tif (!strcmp(file_name, \"multi-pack-index\") ||\n-\t    !strcmp(file_name, \"multi-pack-index.d\"))\n-\t\treturn;\n-\tif (starts_with(file_name, \"multi-pack-index\") &&\n-\t    (ends_with(file_name, \".bitmap\") || ends_with(file_name, \".rev\")))\n-\t\treturn;\n-\tif (ends_with(file_name, \".idx\") ||\n-\t    ends_with(file_name, \".rev\") ||\n-\t    ends_with(file_name, \".pack\") ||\n-\t    ends_with(file_name, \".bitmap\") ||\n-\t    ends_with(file_name, \".keep\") ||\n-\t    ends_with(file_name, \".promisor\") ||\n-\t    ends_with(file_name, \".mtimes\"))\n-\t\tstring_list_append(data->garbage, full_name);\n-\telse\n-\t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n-}\n-\n-static void prepare_packed_git_one(struct odb_source *source)\n-{\n-\tstruct string_list garbage = STRING_LIST_INIT_DUP;\n-\tstruct prepare_pack_data data = {\n-\t\t.source = source,\n-\t\t.garbage = &garbage,\n-\t};\n-\n-\tfor_each_file_in_pack_dir(source->path, prepare_pack, &data);\n-\n-\treport_pack_garbage(data.garbage);\n-\tstring_list_clear(data.garbage, 0);\n-}\n-\n-DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next);\n-\n-static int sort_pack(const struct packfile_list_entry *a,\n-\t\t     const struct packfile_list_entry *b)\n-{\n-\tint st;\n-\n-\t/*\n-\t * Local packs tend to contain objects specific to our\n-\t * variant of the project than remote ones.  In addition,\n-\t * remote ones could be on a network mounted filesystem.\n-\t * Favor local ones for these reasons.\n-\t */\n-\tst = a->pack->pack_local - b->pack->pack_local;\n-\tif (st)\n-\t\treturn -st;\n-\n-\t/*\n-\t * Younger packs tend to contain more recent objects,\n-\t * and more recent objects tend to get accessed more\n-\t * often.\n-\t */\n-\tif (a->pack->mtime < b->pack->mtime)\n-\t\treturn 1;\n-\telse if (a->pack->mtime == b->pack->mtime)\n-\t\treturn 0;\n-\treturn -1;\n-}\n-\n-void packfile_store_prepare(struct odb_source_packed *store)\n-{\n-\tif (store->initialized)\n-\t\treturn;\n-\n-\tprepare_multi_pack_index_one(&store->files->base);\n-\tprepare_packed_git_one(&store->files->base);\n-\n-\tsort_packs(&store->packs.head, sort_pack);\n-\tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n-\t\tif (!e->next)\n-\t\t\tstore->packs.tail = e;\n-\n-\tstore->initialized = true;\n-}\n-\n-void packfile_store_reprepare(struct odb_source_packed *store)\n-{\n-\tstore->initialized = false;\n-\tpackfile_store_prepare(store);\n-}\n-\n struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store)\n {\n-\tpackfile_store_prepare(store);\n+\todb_source_packed_prepare(store);\n \n \tif (store->midx) {\n \t\tstruct multi_pack_index *m = store->midx;\n@@ -2083,7 +1929,7 @@ static int find_pack_entry(struct odb_source_packed *store,\n {\n \tstruct packfile_list_entry *l;\n \n-\tpackfile_store_prepare(store);\n+\todb_source_packed_prepare(store);\n \tif (store->midx && fill_midx_entry(store->midx, oid, e))\n \t\treturn 1;\n \n@@ -2130,7 +1976,7 @@ int packfile_store_read_object_info(struct odb_source_packed *store,\n \t * been added since the last time we have prepared the packfile store.\n \t */\n \tif (flags & OBJECT_INFO_SECOND_READ)\n-\t\tpackfile_store_reprepare(store);\n+\t\todb_source_reprepare(&store->base);\n \n \tif (!find_pack_entry(store, oid, &e))\n \t\treturn 1;\ndiff --git a/packfile.h b/packfile.h\nindex 9dc3a13112..9674e573ae 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -55,23 +55,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-/*\n- * Prepare the packfile store by loading packfiles and multi-pack indices for\n- * all alternates. This becomes a no-op if the store is already prepared.\n- *\n- * It shouldn't typically be necessary to call this function directly, as\n- * functions that access the store know to prepare it.\n- */\n-void packfile_store_prepare(struct odb_source_packed *store);\n-\n-/*\n- * Clear the packfile caches and try to look up any new packfiles that have\n- * appeared since last preparing the packfiles store.\n- *\n- * This function must be called under the `odb_read_lock()`.\n- */\n-void packfile_store_reprepare(struct odb_source_packed *store);\n-\n /*\n  * Add the pack to the store so that contained objects become accessible via\n  * the store. This moves ownership into the store.\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545031","messageId":"20260609-pks-odb-source-packed-v2-8-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 08/17] packfile: use higher-level interface to implement `has_object_pack()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:01Z","receivedAt":"2026-06-09T08:51:28Z","isPatch":true,"body":"In `has_object_pack()` we're checking whether a specific object exists\nas part of a packfile. This is done by calling the low-level function\n`find_pack_entry()`, but this function will eventually be moved into\n\"odb/source-packed.c\" and made file-local.\n\nRefactor the code to use `packfile_store_read_object_info()` instead.\nThis refactoring is functionally equivalent as that function will call\n`find_pack_entry()` itself and then return immediately when it ain't got\nno object info pointer as parameter.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 6 ++----\n 1 file changed, 2 insertions(+), 4 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex 65631f674f..b35afd7797 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2049,14 +2049,12 @@ struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed\n int has_object_pack(struct repository *r, const struct object_id *oid)\n {\n \tstruct odb_source *source;\n-\tstruct pack_entry e;\n \n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n \t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tint ret = find_pack_entry(files->packed, oid, &e);\n-\t\tif (ret)\n-\t\t\treturn ret;\n+\t\tif (!packfile_store_read_object_info(files->packed, oid, NULL, 0))\n+\t\t\treturn 1;\n \t}\n \n \treturn 0;\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545032","messageId":"20260609-pks-odb-source-packed-v2-9-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 09/17] odb/source-packed: wire up `read_object_info()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:02Z","receivedAt":"2026-06-09T08:51:31Z","isPatch":true,"body":"Move the logic to read object info from a \"packed\" source into\n\"odb/source-packed.c\" and wire it up as the `read_object_info()`\ncallback.\n\nNote that we also move around the supporting `find_pack_entry()`, but we\nstill have to expose it to other callers that exist in \"packfile.c\".\nThis will be fixed in subsequent commits though, where all callers in\n\"packfile.c\" will have been moved into \"odb/source-packed.c\", and at\nthat point we'll be able to make `find_pack_entry()` file-local again.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 60 +++++++++++++++++++++++++++++++++++++++++++\n odb/source-packed.h |  6 +++++\n packfile.c          | 74 ++++++-----------------------------------------------\n packfile.h          | 15 +++--------\n 5 files changed, 79 insertions(+), 78 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 7b1e0ac565..8cae35d25e 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -55,7 +55,7 @@ static int odb_source_files_read_object_info(struct odb_source *source,\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \n-\tif (!packfile_store_read_object_info(files->packed, oid, oi, flags) ||\n+\tif (!odb_source_read_object_info(&files->packed->base, oid, oi, flags) ||\n \t    !odb_source_read_object_info(&files->loose->base, oid, oi, flags))\n \t\treturn 0;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex e8e2e5bb48..f71a194739 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -7,6 +7,65 @@\n #include \"odb/source-packed.h\"\n #include \"packfile.h\"\n \n+int find_pack_entry(struct odb_source_packed *store,\n+\t\t    const struct object_id *oid,\n+\t\t    struct pack_entry *e)\n+{\n+\tstruct packfile_list_entry *l;\n+\n+\todb_source_packed_prepare(store);\n+\tif (store->midx && fill_midx_entry(store->midx, oid, e))\n+\t\treturn 1;\n+\n+\tfor (l = store->packs.head; l; l = l->next) {\n+\t\tstruct packed_git *p = l->pack;\n+\n+\t\tif (!p->multi_pack_index && packfile_fill_entry(p, oid, e)) {\n+\t\t\tif (!store->skip_mru_updates)\n+\t\t\t\tpackfile_list_prepend(&store->packs, p);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int odb_source_packed_read_object_info(struct odb_source *source,\n+\t\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t\t      struct object_info *oi,\n+\t\t\t\t\t      enum object_info_flags flags)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct pack_entry e;\n+\tint ret;\n+\n+\t/*\n+\t * In case the first read didn't surface the object, we have to reload\n+\t * packfiles. This may cause us to discover new packfiles that have\n+\t * been added since the last time we have prepared the packfile store.\n+\t */\n+\tif (flags & OBJECT_INFO_SECOND_READ)\n+\t\todb_source_reprepare(source);\n+\n+\tif (!find_pack_entry(packed, oid, &e))\n+\t\treturn 1;\n+\n+\t/*\n+\t * We know that the caller doesn't actually need the\n+\t * information below, so return early.\n+\t */\n+\tif (!oi)\n+\t\treturn 0;\n+\n+\tret = packed_object_info(e.p, e.offset, oi);\n+\tif (ret < 0) {\n+\t\tmark_bad_packed_object(e.p, oid);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -215,6 +274,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.free = odb_source_packed_free;\n \tpacked->base.close = odb_source_packed_close;\n \tpacked->base.reprepare = odb_source_packed_reprepare;\n+\tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 9d4796261a..f430ee0b94 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -90,4 +90,10 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so\n  */\n void odb_source_packed_prepare(struct odb_source_packed *source);\n \n+struct pack_entry;\n+\n+int find_pack_entry(struct odb_source_packed *store,\n+\t\t    const struct object_id *oid,\n+\t\t    struct pack_entry *e);\n+\n #endif\ndiff --git a/packfile.c b/packfile.c\nindex b35afd7797..29530532ba 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1895,9 +1895,9 @@ int is_pack_valid(struct packed_git *p)\n \treturn !open_packed_git(p);\n }\n \n-static int fill_pack_entry(const struct object_id *oid,\n-\t\t\t   struct pack_entry *e,\n-\t\t\t   struct packed_git *p)\n+int packfile_fill_entry(struct packed_git *p,\n+\t\t\tconst struct object_id *oid,\n+\t\t\tstruct pack_entry *e)\n {\n \toff_t offset;\n \n@@ -1923,29 +1923,6 @@ static int fill_pack_entry(const struct object_id *oid,\n \treturn 1;\n }\n \n-static int find_pack_entry(struct odb_source_packed *store,\n-\t\t\t   const struct object_id *oid,\n-\t\t\t   struct pack_entry *e)\n-{\n-\tstruct packfile_list_entry *l;\n-\n-\todb_source_packed_prepare(store);\n-\tif (store->midx && fill_midx_entry(store->midx, oid, e))\n-\t\treturn 1;\n-\n-\tfor (l = store->packs.head; l; l = l->next) {\n-\t\tstruct packed_git *p = l->pack;\n-\n-\t\tif (!p->multi_pack_index && fill_pack_entry(oid, e, p)) {\n-\t\t\tif (!store->skip_mru_updates)\n-\t\t\t\tpackfile_list_prepend(&store->packs, p);\n-\t\t\treturn 1;\n-\t\t}\n-\t}\n-\n-\treturn 0;\n-}\n-\n int packfile_store_freshen_object(struct odb_source_packed *store,\n \t\t\t\t  const struct object_id *oid)\n {\n@@ -1962,41 +1939,6 @@ int packfile_store_freshen_object(struct odb_source_packed *store,\n \treturn 1;\n }\n \n-int packfile_store_read_object_info(struct odb_source_packed *store,\n-\t\t\t\t    const struct object_id *oid,\n-\t\t\t\t    struct object_info *oi,\n-\t\t\t\t    enum object_info_flags flags)\n-{\n-\tstruct pack_entry e;\n-\tint ret;\n-\n-\t/*\n-\t * In case the first read didn't surface the object, we have to reload\n-\t * packfiles. This may cause us to discover new packfiles that have\n-\t * been added since the last time we have prepared the packfile store.\n-\t */\n-\tif (flags & OBJECT_INFO_SECOND_READ)\n-\t\todb_source_reprepare(&store->base);\n-\n-\tif (!find_pack_entry(store, oid, &e))\n-\t\treturn 1;\n-\n-\t/*\n-\t * We know that the caller doesn't actually need the\n-\t * information below, so return early.\n-\t */\n-\tif (!oi)\n-\t\treturn 0;\n-\n-\tret = packed_object_info(e.p, e.offset, oi);\n-\tif (ret < 0) {\n-\t\tmark_bad_packed_object(e.p, oid);\n-\t\treturn -1;\n-\t}\n-\n-\treturn 0;\n-}\n-\n static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t     unsigned flags)\n {\n@@ -2053,7 +1995,7 @@ int has_object_pack(struct repository *r, const struct object_id *oid)\n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n \t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tif (!packfile_store_read_object_info(files->packed, oid, NULL, 0))\n+\t\tif (!odb_source_read_object_info(&files->packed->base, oid, NULL, 0))\n \t\t\treturn 1;\n \t}\n \n@@ -2074,7 +2016,7 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n \t\tfor (; *cache; cache++) {\n \t\t\tstruct packed_git *p = *cache;\n-\t\t\tif (fill_pack_entry(oid, &e, p))\n+\t\t\tif (packfile_fill_entry(p, oid, &e))\n \t\t\t\treturn 1;\n \t\t}\n \t}\n@@ -2208,8 +2150,8 @@ static int for_each_prefixed_object_in_midx(\n \t\t\tif (data->request) {\n \t\t\t\tstruct object_info oi = *data->request;\n \n-\t\t\t\tret = packfile_store_read_object_info(store, current,\n-\t\t\t\t\t\t\t\t      &oi, 0);\n+\t\t\t\tret = odb_source_read_object_info(&store->base, current,\n+\t\t\t\t\t\t\t\t  &oi, 0);\n \t\t\t\tif (ret)\n \t\t\t\t\tgoto out;\n \n@@ -2259,7 +2201,7 @@ static int for_each_prefixed_object_in_pack(\n \t\tif (data->request) {\n \t\t\tstruct object_info oi = *data->request;\n \n-\t\t\tret = packfile_store_read_object_info(store, &oid, &oi, 0);\n+\t\t\tret = odb_source_read_object_info(&store->base, &oid, &oi, 0);\n \t\t\tif (ret)\n \t\t\t\tgoto out;\n \ndiff --git a/packfile.h b/packfile.h\nindex 9674e573ae..25d458beb0 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -128,17 +128,6 @@ int packfile_store_read_object_stream(struct odb_read_stream **out,\n \t\t\t\t      struct odb_source_packed *store,\n \t\t\t\t      const struct object_id *oid);\n \n-/*\n- * Try to read the object identified by its ID from the object store and\n- * populate the object info with its data. Returns 1 in case the object was\n- * not found, 0 if it was and read successfully, and a negative error code in\n- * case the object was corrupted.\n- */\n-int packfile_store_read_object_info(struct odb_source_packed *store,\n-\t\t\t\t    const struct object_id *oid,\n-\t\t\t\t    struct object_info *oi,\n-\t\t\t\t    enum object_info_flags flags);\n-\n /*\n  * Open the packfile and add it to the store if it isn't yet known. Returns\n  * either the newly opened packfile or the preexisting packfile. Returns a\n@@ -340,6 +329,10 @@ off_t nth_packed_object_offset(const struct packed_git *, uint32_t n);\n  */\n off_t find_pack_entry_one(const struct object_id *oid, struct packed_git *);\n \n+int packfile_fill_entry(struct packed_git *p,\n+\t\t\tconst struct object_id *oid,\n+\t\t\tstruct pack_entry *e);\n+\n int is_pack_valid(struct packed_git *);\n void *unpack_entry(struct repository *r, struct packed_git *, off_t, enum object_type *, unsigned long *);\n unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, size_t *sizep);\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545033","messageId":"20260609-pks-odb-source-packed-v2-10-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 10/17] odb/source-packed: wire up `read_object_stream()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:03Z","receivedAt":"2026-06-09T08:51:33Z","isPatch":true,"body":"Wire up the `read_object_stream()` callback for the packed source and\ncall it in the \"files\" source via the `odb_source_read_object_stream()`\ninterface.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 16 ++++++++++++++++\n packfile.c          | 12 ------------\n packfile.h          |  4 ----\n 4 files changed, 17 insertions(+), 17 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 8cae35d25e..dff69d0e4e 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -67,7 +67,7 @@ static int odb_source_files_read_object_stream(struct odb_read_stream **out,\n \t\t\t\t\t       const struct object_id *oid)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tif (!packfile_store_read_object_stream(out, files->packed, oid) ||\n+\tif (!odb_source_read_object_stream(out, &files->packed->base, oid) ||\n \t    !odb_source_read_object_stream(out, &files->loose->base, oid))\n \t\treturn 0;\n \treturn -1;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex f71a194739..23d7149fe3 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -2,9 +2,11 @@\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n #include \"dir.h\"\n+#include \"git-zlib.h\"\n #include \"mergesort.h\"\n #include \"midx.h\"\n #include \"odb/source-packed.h\"\n+#include \"odb/streaming.h\"\n #include \"packfile.h\"\n \n int find_pack_entry(struct odb_source_packed *store,\n@@ -66,6 +68,19 @@ static int odb_source_packed_read_object_info(struct odb_source *source,\n \treturn 0;\n }\n \n+static int odb_source_packed_read_object_stream(struct odb_read_stream **out,\n+\t\t\t\t\t\tstruct odb_source *source,\n+\t\t\t\t\t\tconst struct object_id *oid)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct pack_entry e;\n+\n+\tif (!find_pack_entry(packed, oid, &e))\n+\t\treturn -1;\n+\n+\treturn packfile_read_object_stream(out, oid, e.p, e.offset);\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -275,6 +290,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.close = odb_source_packed_close;\n \tpacked->base.reprepare = odb_source_packed_reprepare;\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n+\tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 29530532ba..42c84397eb 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2658,15 +2658,3 @@ int packfile_read_object_stream(struct odb_read_stream **out,\n \n \treturn 0;\n }\n-\n-int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct odb_source_packed *store,\n-\t\t\t\t      const struct object_id *oid)\n-{\n-\tstruct pack_entry e;\n-\n-\tif (!find_pack_entry(store, oid, &e))\n-\t\treturn -1;\n-\n-\treturn packfile_read_object_stream(out, oid, e.p, e.offset);\n-}\ndiff --git a/packfile.h b/packfile.h\nindex 25d458beb0..dd97684e70 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -124,10 +124,6 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *\n \t     ((p) = (eack_pack_data.entry ? eack_pack_data.entry->pack : NULL)); \\\n \t     repo_for_each_pack_data_next(&eack_pack_data))\n \n-int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct odb_source_packed *store,\n-\t\t\t\t      const struct object_id *oid);\n-\n /*\n  * Open the packfile and add it to the store if it isn't yet known. Returns\n  * either the newly opened packfile or the preexisting packfile. Returns a\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545034","messageId":"20260609-pks-odb-source-packed-v2-11-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 11/17] odb/source-packed: wire up `for_each_object()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:04Z","receivedAt":"2026-06-09T08:51:37Z","isPatch":true,"body":"Move `packfile_store_for_each_object()` and its associated helpers from\n\"packfile.c\" into \"odb/source-packed.c\" and wire it up as the\n`for_each_object()` callback of the \"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     |   4 +-\n builtin/pack-objects.c |   4 +-\n commit-graph.c         |   4 +-\n odb/source-files.c     |   2 +-\n odb/source-packed.c    | 258 ++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.c             | 260 +------------------------------------------------\n packfile.h             |  17 +---\n 7 files changed, 269 insertions(+), 280 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 04b64006a5..d997011531 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -916,8 +916,8 @@ static void batch_each_object(struct batch_options *opt,\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);\n-\t\t\tint ret = packfile_store_for_each_object(files->packed, &oi,\n-\t\t\t\t\t\t\t\t batch_one_object_oi, &payload, &opts);\n+\t\t\tint ret = odb_source_for_each_object(&files->packed->base, &oi,\n+\t\t\t\t\t\t\t     batch_one_object_oi, &payload, &opts);\n \t\t\tif (ret)\n \t\t\t\tbreak;\n \t\t}\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 50675481e1..5e94805478 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4503,8 +4503,8 @@ static void add_objects_in_unpacked_packs(void)\n \t\tif (!source->local)\n \t\t\tcontinue;\n \n-\t\tif (packfile_store_for_each_object(files->packed, &oi,\n-\t\t\t\t\t\t   add_object_in_unpacked_pack, NULL, &opts))\n+\t\tif (odb_source_for_each_object(&files->packed->base, &oi,\n+\t\t\t\t\t       add_object_in_unpacked_pack, NULL, &opts))\n \t\t\tdie(_(\"cannot open pack index\"));\n \t}\n }\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 9abe62bd5a..1e4038baf3 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -2016,8 +2016,8 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n \todb_prepare_alternates(ctx->r->objects);\n \tfor (source = ctx->r->objects->sources; source; source = source->next) {\n \t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tpackfile_store_for_each_object(files->packed, &oi, add_packed_commits_oi,\n-\t\t\t\t\t       ctx, &opts);\n+\t\todb_source_for_each_object(&files->packed->base, &oi, add_packed_commits_oi,\n+\t\t\t\t\t   ctx, &opts);\n \t}\n \n \tif (ctx->progress_done < ctx->approx_nr_objects)\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex dff69d0e4e..c73a7e5f90 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -88,7 +88,7 @@ static int odb_source_files_for_each_object(struct odb_source *source,\n \t\t\treturn ret;\n \t}\n \n-\tret = packfile_store_for_each_object(files->packed, request, cb, cb_data, opts);\n+\tret = odb_source_for_each_object(&files->packed->base, request, cb, cb_data, opts);\n \tif (ret)\n \t\treturn ret;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 23d7149fe3..a61c809c8c 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -81,6 +81,263 @@ static int odb_source_packed_read_object_stream(struct odb_read_stream **out,\n \treturn packfile_read_object_stream(out, oid, e.p, e.offset);\n }\n \n+struct odb_source_packed_for_each_object_wrapper_data {\n+\tstruct odb_source_packed *store;\n+\tconst struct object_info *request;\n+\todb_for_each_object_cb cb;\n+\tvoid *cb_data;\n+};\n+\n+static int odb_source_packed_for_each_object_wrapper(const struct object_id *oid,\n+\t\t\t\t\t\t     struct packed_git *pack,\n+\t\t\t\t\t\t     uint32_t index_pos,\n+\t\t\t\t\t\t     void *cb_data)\n+{\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data = cb_data;\n+\n+\tif (data->request) {\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\t\t\t\t\t      &index_pos, &oi) < 0) {\n+\t\t\tmark_bad_packed_object(pack, oid);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\treturn data->cb(oid, &oi, data->cb_data);\n+\t} else {\n+\t\treturn data->cb(oid, NULL, data->cb_data);\n+\t}\n+}\n+\n+static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b)\n+{\n+\tdo {\n+\t\tif (*a != *b)\n+\t\t\treturn 0;\n+\t\ta++;\n+\t\tb++;\n+\t\tlen -= 2;\n+\t} while (len > 1);\n+\tif (len)\n+\t\tif ((*a ^ *b) & 0xf0)\n+\t\t\treturn 0;\n+\treturn 1;\n+}\n+\n+static int for_each_prefixed_object_in_midx(\n+\tstruct odb_source_packed *store,\n+\tstruct multi_pack_index *m,\n+\tconst struct odb_for_each_object_options *opts,\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n+{\n+\tint ret;\n+\n+\tfor (; m; m = m->base_midx) {\n+\t\tuint32_t num, i, first = 0;\n+\t\tint len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ?\n+\t\t\tm->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n+\n+\t\tif (!m->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tnum = m->num_objects + m->num_objects_in_base;\n+\n+\t\tbsearch_one_midx(opts->prefix, m, &first);\n+\n+\t\t/*\n+\t\t * At this point, \"first\" is the location of the lowest\n+\t\t * object with an object name that could match \"opts->prefix\".\n+\t\t * See if we have 0, 1 or more objects that actually match(es).\n+\t\t */\n+\t\tfor (i = first; i < num; i++) {\n+\t\t\tconst struct object_id *current = NULL;\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tcurrent = nth_midxed_object_oid(&oid, m, i);\n+\n+\t\t\tif (!match_hash(len, opts->prefix->hash, current->hash))\n+\t\t\t\tbreak;\n+\n+\t\t\tif (data->request) {\n+\t\t\t\tstruct object_info oi = *data->request;\n+\n+\t\t\t\tret = odb_source_read_object_info(&store->base, current,\n+\t\t\t\t\t\t\t\t  &oi, 0);\n+\t\t\t\tif (ret)\n+\t\t\t\t\tgoto out;\n+\n+\t\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n+\t\t\t\tif (ret)\n+\t\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n+\t\t\t\tif (ret)\n+\t\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\treturn ret;\n+}\n+\n+static int for_each_prefixed_object_in_pack(\n+\tstruct odb_source_packed *store,\n+\tstruct packed_git *p,\n+\tconst struct odb_for_each_object_options *opts,\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n+{\n+\tuint32_t num, i, first = 0;\n+\tint len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ?\n+\t\tp->repo->hash_algo->hexsz : opts->prefix_hex_len;\n+\tint ret;\n+\n+\tnum = p->num_objects;\n+\tbsearch_pack(opts->prefix, p, &first);\n+\n+\t/*\n+\t * At this point, \"first\" is the location of the lowest object\n+\t * with an object name that could match \"bin_pfx\".  See if we have\n+\t * 0, 1 or more objects that actually match(es).\n+\t */\n+\tfor (i = first; i < num; i++) {\n+\t\tstruct object_id oid;\n+\n+\t\tnth_packed_object_id(&oid, p, i);\n+\t\tif (!match_hash(len, opts->prefix->hash, oid.hash))\n+\t\t\tbreak;\n+\n+\t\tif (data->request) {\n+\t\t\tstruct object_info oi = *data->request;\n+\n+\t\t\tret = odb_source_read_object_info(&store->base, &oid, &oi, 0);\n+\t\t\tif (ret)\n+\t\t\t\tgoto out;\n+\n+\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n+\t\t\tif (ret)\n+\t\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n+\t\t\tif (ret)\n+\t\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\treturn ret;\n+}\n+\n+static int odb_source_packed_for_each_prefixed_object(\n+\tstruct odb_source_packed *store,\n+\tconst struct odb_for_each_object_options *opts,\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n+{\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n+\tbool pack_errors = false;\n+\tint ret;\n+\n+\tif (opts->flags)\n+\t\tBUG(\"flags unsupported\");\n+\n+\tstore->skip_mru_updates = true;\n+\n+\tm = get_multi_pack_index(&store->files->base);\n+\tif (m) {\n+\t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n+\t\tif (e->pack->multi_pack_index)\n+\t\t\tcontinue;\n+\n+\t\tif (open_pack_index(e->pack)) {\n+\t\t\tpack_errors = true;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!e->pack->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tret = for_each_prefixed_object_in_pack(store, e->pack, opts, data);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\tstore->skip_mru_updates = false;\n+\tif (!ret && pack_errors)\n+\t\tret = -1;\n+\treturn ret;\n+}\n+\n+static int odb_source_packed_for_each_object(struct odb_source *source,\n+\t\t\t\t\t     const struct object_info *request,\n+\t\t\t\t\t     odb_for_each_object_cb cb,\n+\t\t\t\t\t     void *cb_data,\n+\t\t\t\t\t     const struct odb_for_each_object_options *opts)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct odb_source_packed_for_each_object_wrapper_data data = {\n+\t\t.store = packed,\n+\t\t.request = request,\n+\t\t.cb = cb,\n+\t\t.cb_data = cb_data,\n+\t};\n+\tstruct packfile_list_entry *e;\n+\tint pack_errors = 0, ret;\n+\n+\tif (opts->prefix)\n+\t\treturn odb_source_packed_for_each_prefixed_object(packed, opts, &data);\n+\n+\tpacked->skip_mru_updates = true;\n+\n+\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n+\t\tstruct packed_git *p = e->pack;\n+\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n+\t\t    p->pack_keep_in_core)\n+\t\t\tcontinue;\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n+\t\t    p->pack_keep)\n+\t\t\tcontinue;\n+\t\tif (open_pack_index(p)) {\n+\t\t\tpack_errors = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tret = for_each_object_in_pack(p, odb_source_packed_for_each_object_wrapper,\n+\t\t\t\t\t      &data, opts->flags);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\tpacked->skip_mru_updates = false;\n+\n+\tif (!ret && pack_errors)\n+\t\tret = -1;\n+\treturn ret;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -291,6 +548,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.reprepare = odb_source_packed_reprepare;\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n+\tpacked->base.for_each_object = odb_source_packed_for_each_object;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 42c84397eb..b8d6054c16 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1362,8 +1362,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-static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n-\t\t\t\t\t     uint32_t *maybe_index_pos, struct object_info *oi)\n+int packed_object_info_with_index_pos(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 \tsize_t size;\n@@ -2068,262 +2068,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-struct odb_source_packed_for_each_object_wrapper_data {\n-\tstruct odb_source_packed *store;\n-\tconst struct object_info *request;\n-\todb_for_each_object_cb cb;\n-\tvoid *cb_data;\n-};\n-\n-static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n-\t\t\t\t\t\t  struct packed_git *pack,\n-\t\t\t\t\t\t  uint32_t index_pos,\n-\t\t\t\t\t\t  void *cb_data)\n-{\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data = cb_data;\n-\n-\tif (data->request) {\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\t\t\t\t\t      &index_pos, &oi) < 0) {\n-\t\t\tmark_bad_packed_object(pack, oid);\n-\t\t\treturn -1;\n-\t\t}\n-\n-\t\treturn data->cb(oid, &oi, data->cb_data);\n-\t} else {\n-\t\treturn data->cb(oid, NULL, data->cb_data);\n-\t}\n-}\n-\n-static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b)\n-{\n-\tdo {\n-\t\tif (*a != *b)\n-\t\t\treturn 0;\n-\t\ta++;\n-\t\tb++;\n-\t\tlen -= 2;\n-\t} while (len > 1);\n-\tif (len)\n-\t\tif ((*a ^ *b) & 0xf0)\n-\t\t\treturn 0;\n-\treturn 1;\n-}\n-\n-static int for_each_prefixed_object_in_midx(\n-\tstruct odb_source_packed *store,\n-\tstruct multi_pack_index *m,\n-\tconst struct odb_for_each_object_options *opts,\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n-{\n-\tint ret;\n-\n-\tfor (; m; m = m->base_midx) {\n-\t\tuint32_t num, i, first = 0;\n-\t\tint len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ?\n-\t\t\tm->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n-\n-\t\tif (!m->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tnum = m->num_objects + m->num_objects_in_base;\n-\n-\t\tbsearch_one_midx(opts->prefix, m, &first);\n-\n-\t\t/*\n-\t\t * At this point, \"first\" is the location of the lowest\n-\t\t * object with an object name that could match \"opts->prefix\".\n-\t\t * See if we have 0, 1 or more objects that actually match(es).\n-\t\t */\n-\t\tfor (i = first; i < num; i++) {\n-\t\t\tconst struct object_id *current = NULL;\n-\t\t\tstruct object_id oid;\n-\n-\t\t\tcurrent = nth_midxed_object_oid(&oid, m, i);\n-\n-\t\t\tif (!match_hash(len, opts->prefix->hash, current->hash))\n-\t\t\t\tbreak;\n-\n-\t\t\tif (data->request) {\n-\t\t\t\tstruct object_info oi = *data->request;\n-\n-\t\t\t\tret = odb_source_read_object_info(&store->base, current,\n-\t\t\t\t\t\t\t\t  &oi, 0);\n-\t\t\t\tif (ret)\n-\t\t\t\t\tgoto out;\n-\n-\t\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n-\t\t\t\tif (ret)\n-\t\t\t\t\tgoto out;\n-\t\t\t} else {\n-\t\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n-\t\t\t\tif (ret)\n-\t\t\t\t\tgoto out;\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\treturn ret;\n-}\n-\n-static int for_each_prefixed_object_in_pack(\n-\tstruct odb_source_packed *store,\n-\tstruct packed_git *p,\n-\tconst struct odb_for_each_object_options *opts,\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n-{\n-\tuint32_t num, i, first = 0;\n-\tint len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ?\n-\t\tp->repo->hash_algo->hexsz : opts->prefix_hex_len;\n-\tint ret;\n-\n-\tnum = p->num_objects;\n-\tbsearch_pack(opts->prefix, p, &first);\n-\n-\t/*\n-\t * At this point, \"first\" is the location of the lowest object\n-\t * with an object name that could match \"bin_pfx\".  See if we have\n-\t * 0, 1 or more objects that actually match(es).\n-\t */\n-\tfor (i = first; i < num; i++) {\n-\t\tstruct object_id oid;\n-\n-\t\tnth_packed_object_id(&oid, p, i);\n-\t\tif (!match_hash(len, opts->prefix->hash, oid.hash))\n-\t\t\tbreak;\n-\n-\t\tif (data->request) {\n-\t\t\tstruct object_info oi = *data->request;\n-\n-\t\t\tret = odb_source_read_object_info(&store->base, &oid, &oi, 0);\n-\t\t\tif (ret)\n-\t\t\t\tgoto out;\n-\n-\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n-\t\t\tif (ret)\n-\t\t\t\tgoto out;\n-\t\t} else {\n-\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n-\t\t\tif (ret)\n-\t\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\treturn ret;\n-}\n-\n-static int packfile_store_for_each_prefixed_object(\n-\tstruct odb_source_packed *store,\n-\tconst struct odb_for_each_object_options *opts,\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n-{\n-\tstruct packfile_list_entry *e;\n-\tstruct multi_pack_index *m;\n-\tbool pack_errors = false;\n-\tint ret;\n-\n-\tif (opts->flags)\n-\t\tBUG(\"flags unsupported\");\n-\n-\tstore->skip_mru_updates = true;\n-\n-\tm = get_multi_pack_index(&store->files->base);\n-\tif (m) {\n-\t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n-\t\tif (ret)\n-\t\t\tgoto out;\n-\t}\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tif (e->pack->multi_pack_index)\n-\t\t\tcontinue;\n-\n-\t\tif (open_pack_index(e->pack)) {\n-\t\t\tpack_errors = true;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tif (!e->pack->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tret = for_each_prefixed_object_in_pack(store, e->pack, opts, data);\n-\t\tif (ret)\n-\t\t\tgoto out;\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tstore->skip_mru_updates = false;\n-\tif (!ret && pack_errors)\n-\t\tret = -1;\n-\treturn ret;\n-}\n-\n-int packfile_store_for_each_object(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_info *request,\n-\t\t\t\t   odb_for_each_object_cb cb,\n-\t\t\t\t   void *cb_data,\n-\t\t\t\t   const struct odb_for_each_object_options *opts)\n-{\n-\tstruct odb_source_packed_for_each_object_wrapper_data data = {\n-\t\t.store = store,\n-\t\t.request = request,\n-\t\t.cb = cb,\n-\t\t.cb_data = cb_data,\n-\t};\n-\tstruct packfile_list_entry *e;\n-\tint pack_errors = 0, ret;\n-\n-\tif (opts->prefix)\n-\t\treturn packfile_store_for_each_prefixed_object(store, opts, &data);\n-\n-\tstore->skip_mru_updates = true;\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tstruct packed_git *p = e->pack;\n-\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\tcontinue;\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t    !p->pack_promisor)\n-\t\t\tcontinue;\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n-\t\t    p->pack_keep_in_core)\n-\t\t\tcontinue;\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n-\t\t    p->pack_keep)\n-\t\t\tcontinue;\n-\t\tif (open_pack_index(p)) {\n-\t\t\tpack_errors = 1;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tret = for_each_object_in_pack(p, packfile_store_for_each_object_wrapper,\n-\t\t\t\t\t      &data, opts->flags);\n-\t\tif (ret)\n-\t\t\tgoto out;\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tstore->skip_mru_updates = false;\n-\n-\tif (!ret && pack_errors)\n-\t\tret = -1;\n-\treturn ret;\n-}\n-\n static int extend_abbrev_len(const struct object_id *a,\n \t\t\t     const struct object_id *b,\n \t\t\t     unsigned *out)\ndiff --git a/packfile.h b/packfile.h\nindex dd97684e70..0097de0b27 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -227,21 +227,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn, void *data,\n \t\t\t    enum odb_for_each_object_flags flags);\n \n-/*\n- * Iterate through all packed objects in the given packfile store and invoke\n- * the callback function for each of them. If an object info request is given,\n- * then the object info will be read for every individual object and passed to\n- * the callback as if `packfile_store_read_object_info()` was called for the\n- * object.\n- *\n- * The flags parameter is a combination of `odb_for_each_object_flags`.\n- */\n-int packfile_store_for_each_object(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_info *request,\n-\t\t\t\t   odb_for_each_object_cb cb,\n-\t\t\t\t   void *cb_data,\n-\t\t\t\t   const struct odb_for_each_object_options *opts);\n-\n int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \t\t\t\t   const struct object_id *oid,\n \t\t\t\t   unsigned min_len,\n@@ -354,6 +339,8 @@ extern int do_check_packed_object_crc;\n  */\n int packed_object_info(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+\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 *);\n const struct packed_git *has_packed_and_bad(struct repository *, const struct object_id *);\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545035","messageId":"20260609-pks-odb-source-packed-v2-12-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 12/17] odb/source-packed: wire up `count_objects()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:05Z","receivedAt":"2026-06-09T08:51:39Z","isPatch":true,"body":"Move `packfile_store_count_objects()` from \"packfile.c\" into\n\"odb/source-packed.c\" and wire it up as the `count_objects()` callback\nof the \"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 33 +++++++++++++++++++++++++++++++++\n packfile.c          | 31 -------------------------------\n packfile.h          | 10 ----------\n 4 files changed, 34 insertions(+), 42 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex c73a7e5f90..274923e0ba 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -103,7 +103,7 @@ static int odb_source_files_count_objects(struct odb_source *source,\n \tunsigned long count;\n \tint ret;\n \n-\tret = packfile_store_count_objects(files->packed, flags, &count);\n+\tret = odb_source_count_objects(&files->packed->base, flags, &count);\n \tif (ret < 0)\n \t\tgoto out;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex a61c809c8c..070a4e3958 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -338,6 +338,38 @@ static int odb_source_packed_for_each_object(struct odb_source *source,\n \treturn ret;\n }\n \n+static int odb_source_packed_count_objects(struct odb_source *source,\n+\t\t\t\t\t   enum odb_count_objects_flags flags UNUSED,\n+\t\t\t\t\t   unsigned long *out)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n+\tunsigned long count = 0;\n+\tint ret;\n+\n+\tm = get_multi_pack_index(&packed->files->base);\n+\tif (m)\n+\t\tcount += m->num_objects + m->num_objects_in_base;\n+\n+\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n+\t\tif (e->pack->multi_pack_index)\n+\t\t\tcontinue;\n+\t\tif (open_pack_index(e->pack)) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tcount += e->pack->num_objects;\n+\t}\n+\n+\t*out = count;\n+\tret = 0;\n+\n+out:\n+\treturn ret;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -549,6 +581,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n+\tpacked->base.count_objects = odb_source_packed_count_objects;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex b8d6054c16..2da6bbe2b5 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -866,37 +866,6 @@ struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *s\n \treturn store->packs.head;\n }\n \n-int packfile_store_count_objects(struct odb_source_packed *store,\n-\t\t\t\t enum odb_count_objects_flags flags UNUSED,\n-\t\t\t\t unsigned long *out)\n-{\n-\tstruct packfile_list_entry *e;\n-\tstruct multi_pack_index *m;\n-\tunsigned long count = 0;\n-\tint ret;\n-\n-\tm = get_multi_pack_index(&store->files->base);\n-\tif (m)\n-\t\tcount += m->num_objects + m->num_objects_in_base;\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tif (e->pack->multi_pack_index)\n-\t\t\tcontinue;\n-\t\tif (open_pack_index(e->pack)) {\n-\t\t\tret = -1;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tcount += e->pack->num_objects;\n-\t}\n-\n-\t*out = count;\n-\tret = 0;\n-\n-out:\n-\treturn ret;\n-}\n-\n unsigned long unpack_object_header_buffer(const unsigned char *buf,\n \t\tunsigned long len, enum object_type *type, size_t *sizep)\n {\ndiff --git a/packfile.h b/packfile.h\nindex 0097de0b27..0613fd3c63 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -141,16 +141,6 @@ enum kept_pack_type {\n \tKEPT_PACK_IN_CORE_OPEN = (1 << 2),\n };\n \n-/*\n- * Count the number objects contained in the given packfile store. If\n- * successful, the number of objects will be written to the `out` pointer.\n- *\n- * Return 0 on success, a negative error code otherwise.\n- */\n-int packfile_store_count_objects(struct odb_source_packed *store,\n-\t\t\t\t enum odb_count_objects_flags flags,\n-\t\t\t\t unsigned long *out);\n-\n /*\n  * Retrieve the cache of kept packs from the given packfile store. Accepts a\n  * combination of `kept_pack_type` flags. The cache is computed on demand and\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545036","messageId":"20260609-pks-odb-source-packed-v2-13-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 13/17] odb/source-packed: wire up `find_abbrev_len()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:06Z","receivedAt":"2026-06-09T08:51:41Z","isPatch":true,"body":"Move `packfile_store_find_abbrev_len()` and its associated helpers from\n\"packfile.c\" into \"odb/source-packed.c\" and wire it up as the\n`find_abbrev_len()` callback of the \"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |   2 +-\n odb/source-packed.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.c          | 111 ---------------------------------------------------\n packfile.h          |   5 ---\n 4 files changed, 114 insertions(+), 117 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 274923e0ba..8ad782dc7b 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -133,7 +133,7 @@ static int odb_source_files_find_abbrev_len(struct odb_source *source,\n \tunsigned len = min_len;\n \tint ret;\n \n-\tret = packfile_store_find_abbrev_len(files->packed, oid, len, &len);\n+\tret = odb_source_find_abbrev_len(&files->packed->base, oid, len, &len);\n \tif (ret < 0)\n \t\tgoto out;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 070a4e3958..b801b62023 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -370,6 +370,118 @@ static int odb_source_packed_count_objects(struct odb_source *source,\n \treturn ret;\n }\n \n+static int extend_abbrev_len(const struct object_id *a,\n+\t\t\t     const struct object_id *b,\n+\t\t\t     unsigned *out)\n+{\n+\tunsigned len = oid_common_prefix_hexlen(a, b);\n+\tif (len != hash_algos[a->algo].hexsz && len >= *out)\n+\t\t*out = len + 1;\n+\treturn 0;\n+}\n+\n+static void find_abbrev_len_for_midx(struct multi_pack_index *m,\n+\t\t\t\t     const struct object_id *oid,\n+\t\t\t\t     unsigned min_len,\n+\t\t\t\t     unsigned *out)\n+{\n+\tunsigned len = min_len;\n+\n+\tfor (; m; m = m->base_midx) {\n+\t\tint match = 0;\n+\t\tuint32_t num, first = 0;\n+\t\tstruct object_id found_oid;\n+\n+\t\tif (!m->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tnum = m->num_objects + m->num_objects_in_base;\n+\t\tmatch = bsearch_one_midx(oid, m, &first);\n+\n+\t\t/*\n+\t\t * first is now the position in the packfile where we\n+\t\t * would insert the object ID if it does not exist (or the\n+\t\t * position of the object ID if it does exist). Hence, we\n+\t\t * consider a maximum of two objects nearby for the\n+\t\t * abbreviation length.\n+\t\t */\n+\n+\t\tif (!match) {\n+\t\t\tif (nth_midxed_object_oid(&found_oid, m, first))\n+\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t\t} else if (first < num - 1) {\n+\t\t\tif (nth_midxed_object_oid(&found_oid, m, first + 1))\n+\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t\t}\n+\t\tif (first > 0) {\n+\t\t\tif (nth_midxed_object_oid(&found_oid, m, first - 1))\n+\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t\t}\n+\t}\n+\n+\t*out = len;\n+}\n+\n+static void find_abbrev_len_for_pack(struct packed_git *p,\n+\t\t\t\t     const struct object_id *oid,\n+\t\t\t\t     unsigned min_len,\n+\t\t\t\t     unsigned *out)\n+{\n+\tint match;\n+\tuint32_t num, first = 0;\n+\tstruct object_id found_oid;\n+\tunsigned len = min_len;\n+\n+\tnum = p->num_objects;\n+\tmatch = bsearch_pack(oid, p, &first);\n+\n+\t/*\n+\t * first is now the position in the packfile where we would insert\n+\t * the object ID if it does not exist (or the position of mad->hash if\n+\t * it does exist). Hence, we consider a maximum of two objects\n+\t * nearby for the abbreviation length.\n+\t */\n+\tif (!match) {\n+\t\tif (!nth_packed_object_id(&found_oid, p, first))\n+\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t} else if (first < num - 1) {\n+\t\tif (!nth_packed_object_id(&found_oid, p, first + 1))\n+\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t}\n+\tif (first > 0) {\n+\t\tif (!nth_packed_object_id(&found_oid, p, first - 1))\n+\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t}\n+\n+\t*out = len;\n+}\n+\n+static int odb_source_packed_find_abbrev_len(struct odb_source *source,\n+\t\t\t\t\t     const struct object_id *oid,\n+\t\t\t\t\t     unsigned min_len,\n+\t\t\t\t\t     unsigned *out)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n+\n+\tm = get_multi_pack_index(&packed->files->base);\n+\tif (m)\n+\t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n+\n+\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n+\t\tif (e->pack->multi_pack_index)\n+\t\t\tcontinue;\n+\t\tif (open_pack_index(e->pack) || !e->pack->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tfind_abbrev_len_for_pack(e->pack, oid, min_len, &min_len);\n+\t}\n+\n+\t*out = min_len;\n+\treturn 0;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -582,6 +694,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n \tpacked->base.count_objects = odb_source_packed_count_objects;\n+\tpacked->base.find_abbrev_len = odb_source_packed_find_abbrev_len;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 2da6bbe2b5..7f84094e53 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2037,117 +2037,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-static int extend_abbrev_len(const struct object_id *a,\n-\t\t\t     const struct object_id *b,\n-\t\t\t     unsigned *out)\n-{\n-\tunsigned len = oid_common_prefix_hexlen(a, b);\n-\tif (len != hash_algos[a->algo].hexsz && len >= *out)\n-\t\t*out = len + 1;\n-\treturn 0;\n-}\n-\n-static void find_abbrev_len_for_midx(struct multi_pack_index *m,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     unsigned min_len,\n-\t\t\t\t     unsigned *out)\n-{\n-\tunsigned len = min_len;\n-\n-\tfor (; m; m = m->base_midx) {\n-\t\tint match = 0;\n-\t\tuint32_t num, first = 0;\n-\t\tstruct object_id found_oid;\n-\n-\t\tif (!m->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tnum = m->num_objects + m->num_objects_in_base;\n-\t\tmatch = bsearch_one_midx(oid, m, &first);\n-\n-\t\t/*\n-\t\t * first is now the position in the packfile where we\n-\t\t * would insert the object ID if it does not exist (or the\n-\t\t * position of the object ID if it does exist). Hence, we\n-\t\t * consider a maximum of two objects nearby for the\n-\t\t * abbreviation length.\n-\t\t */\n-\n-\t\tif (!match) {\n-\t\t\tif (nth_midxed_object_oid(&found_oid, m, first))\n-\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t\t} else if (first < num - 1) {\n-\t\t\tif (nth_midxed_object_oid(&found_oid, m, first + 1))\n-\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t\t}\n-\t\tif (first > 0) {\n-\t\t\tif (nth_midxed_object_oid(&found_oid, m, first - 1))\n-\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t\t}\n-\t}\n-\n-\t*out = len;\n-}\n-\n-static void find_abbrev_len_for_pack(struct packed_git *p,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     unsigned min_len,\n-\t\t\t\t     unsigned *out)\n-{\n-\tint match;\n-\tuint32_t num, first = 0;\n-\tstruct object_id found_oid;\n-\tunsigned len = min_len;\n-\n-\tnum = p->num_objects;\n-\tmatch = bsearch_pack(oid, p, &first);\n-\n-\t/*\n-\t * first is now the position in the packfile where we would insert\n-\t * the object ID if it does not exist (or the position of mad->hash if\n-\t * it does exist). Hence, we consider a maximum of two objects\n-\t * nearby for the abbreviation length.\n-\t */\n-\tif (!match) {\n-\t\tif (!nth_packed_object_id(&found_oid, p, first))\n-\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t} else if (first < num - 1) {\n-\t\tif (!nth_packed_object_id(&found_oid, p, first + 1))\n-\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t}\n-\tif (first > 0) {\n-\t\tif (!nth_packed_object_id(&found_oid, p, first - 1))\n-\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t}\n-\n-\t*out = len;\n-}\n-\n-int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t   unsigned min_len,\n-\t\t\t\t   unsigned *out)\n-{\n-\tstruct packfile_list_entry *e;\n-\tstruct multi_pack_index *m;\n-\n-\tm = get_multi_pack_index(&store->files->base);\n-\tif (m)\n-\t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tif (e->pack->multi_pack_index)\n-\t\t\tcontinue;\n-\t\tif (open_pack_index(e->pack) || !e->pack->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tfind_abbrev_len_for_pack(e->pack, oid, min_len, &min_len);\n-\t}\n-\n-\t*out = min_len;\n-\treturn 0;\n-}\n-\n struct add_promisor_object_data {\n \tstruct repository *repo;\n \tstruct oidset *set;\ndiff --git a/packfile.h b/packfile.h\nindex 0613fd3c63..79324e4010 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -217,11 +217,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn, void *data,\n \t\t\t    enum odb_for_each_object_flags flags);\n \n-int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t   unsigned min_len,\n-\t\t\t\t   unsigned *out);\n-\n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\n #define PACKDIR_FILE_IDX 2\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545037","messageId":"20260609-pks-odb-source-packed-v2-14-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 14/17] odb/source-packed: wire up `freshen_object()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:07Z","receivedAt":"2026-06-09T08:51:44Z","isPatch":true,"body":"Move `packfile_store_freshen_object()` and from \"packfile.c\" into\n\"odb/source-packed.c\" and wire it up as the `freshen_object()` callback\nof the \"packed\" source.\n\nNote that this removes the last external caller of `find_pack_entry()`\nfrom \"packfile.c\", which means that we can now make this function\nstatic.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 26 +++++++++++++++++++++++---\n odb/source-packed.h |  6 ------\n packfile.c          | 16 ----------------\n packfile.h          |  3 ---\n 5 files changed, 24 insertions(+), 29 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 8ad782dc7b..fa2e18e71b 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -152,7 +152,7 @@ static int odb_source_files_freshen_object(struct odb_source *source,\n \t\t\t\t\t   const struct object_id *oid)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tif (packfile_store_freshen_object(files->packed, oid) ||\n+\tif (odb_source_freshen_object(&files->packed->base, oid) ||\n \t    odb_source_freshen_object(&files->loose->base, oid))\n \t\treturn 1;\n \treturn 0;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex b801b62023..e40b52e445 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -9,9 +9,9 @@\n #include \"odb/streaming.h\"\n #include \"packfile.h\"\n \n-int find_pack_entry(struct odb_source_packed *store,\n-\t\t    const struct object_id *oid,\n-\t\t    struct pack_entry *e)\n+static int find_pack_entry(struct odb_source_packed *store,\n+\t\t\t   const struct object_id *oid,\n+\t\t\t   struct pack_entry *e)\n {\n \tstruct packfile_list_entry *l;\n \n@@ -482,6 +482,25 @@ static int odb_source_packed_find_abbrev_len(struct odb_source *source,\n \treturn 0;\n }\n \n+static int odb_source_packed_freshen_object(struct odb_source *source,\n+\t\t\t\t\t    const struct object_id *oid)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct pack_entry e;\n+\n+\tif (!find_pack_entry(packed, oid, &e))\n+\t\treturn 0;\n+\tif (e.p->is_cruft)\n+\t\treturn 0;\n+\tif (e.p->freshened)\n+\t\treturn 1;\n+\tif (utime(e.p->pack_name, NULL))\n+\t\treturn 0;\n+\te.p->freshened = 1;\n+\n+\treturn 1;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -695,6 +714,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n \tpacked->base.count_objects = odb_source_packed_count_objects;\n \tpacked->base.find_abbrev_len = odb_source_packed_find_abbrev_len;\n+\tpacked->base.freshen_object = odb_source_packed_freshen_object;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex f430ee0b94..9d4796261a 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -90,10 +90,4 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so\n  */\n void odb_source_packed_prepare(struct odb_source_packed *source);\n \n-struct pack_entry;\n-\n-int find_pack_entry(struct odb_source_packed *store,\n-\t\t    const struct object_id *oid,\n-\t\t    struct pack_entry *e);\n-\n #endif\ndiff --git a/packfile.c b/packfile.c\nindex 7f84094e53..a577275d4f 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1892,22 +1892,6 @@ int packfile_fill_entry(struct packed_git *p,\n \treturn 1;\n }\n \n-int packfile_store_freshen_object(struct odb_source_packed *store,\n-\t\t\t\t  const struct object_id *oid)\n-{\n-\tstruct pack_entry e;\n-\tif (!find_pack_entry(store, oid, &e))\n-\t\treturn 0;\n-\tif (e.p->is_cruft)\n-\t\treturn 0;\n-\tif (e.p->freshened)\n-\t\treturn 1;\n-\tif (utime(e.p->pack_name, NULL))\n-\t\treturn 0;\n-\te.p->freshened = 1;\n-\treturn 1;\n-}\n-\n static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t     unsigned flags)\n {\ndiff --git a/packfile.h b/packfile.h\nindex 79324e4010..71a71017ee 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -132,9 +132,6 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *\n struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \t\t\t\t\t    const char *idx_path, int local);\n \n-int packfile_store_freshen_object(struct odb_source_packed *store,\n-\t\t\t\t  const struct object_id *oid);\n-\n enum kept_pack_type {\n \tKEPT_PACK_ON_DISK = (1 << 0),\n \tKEPT_PACK_IN_CORE = (1 << 1),\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545038","messageId":"20260609-pks-odb-source-packed-v2-15-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 15/17] odb/source-packed: stub out remaining functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:08Z","receivedAt":"2026-06-09T08:51:46Z","isPatch":true,"body":"Stub out remaining functions that we either don't need or that are\nbasically no-ops.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-packed.c | 42 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 42 insertions(+)\n\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex e40b52e445..08a2de9bc5 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -501,6 +501,43 @@ static int odb_source_packed_freshen_object(struct odb_source *source,\n \treturn 1;\n }\n \n+static int odb_source_packed_write_object(struct odb_source *source UNUSED,\n+\t\t\t\t\t  const void *buf UNUSED,\n+\t\t\t\t\t  unsigned long len UNUSED,\n+\t\t\t\t\t  enum object_type type UNUSED,\n+\t\t\t\t\t  struct object_id *oid UNUSED,\n+\t\t\t\t\t  struct object_id *compat_oid UNUSED,\n+\t\t\t\t\t  unsigned flags UNUSED)\n+{\n+\treturn error(\"packed backend cannot write objects\");\n+}\n+\n+static int odb_source_packed_write_object_stream(struct odb_source *source UNUSED,\n+\t\t\t\t\t\t struct odb_write_stream *stream UNUSED,\n+\t\t\t\t\t\t size_t len UNUSED,\n+\t\t\t\t\t\t struct object_id *oid UNUSED)\n+{\n+\treturn error(\"packed backend cannot write object streams\");\n+}\n+\n+static int odb_source_packed_begin_transaction(struct odb_source *source UNUSED,\n+\t\t\t\t\t       struct odb_transaction **out UNUSED)\n+{\n+\treturn error(\"packed backend cannot begin transactions\");\n+}\n+\n+static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,\n+\t\t\t\t\t     struct strvec *out UNUSED)\n+{\n+\treturn 0;\n+}\n+\n+static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,\n+\t\t\t\t\t     const char *alternate UNUSED)\n+{\n+\treturn error(\"packed backend cannot write alternates\");\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -715,6 +752,11 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.count_objects = odb_source_packed_count_objects;\n \tpacked->base.find_abbrev_len = odb_source_packed_find_abbrev_len;\n \tpacked->base.freshen_object = odb_source_packed_freshen_object;\n+\tpacked->base.write_object = odb_source_packed_write_object;\n+\tpacked->base.write_object_stream = odb_source_packed_write_object_stream;\n+\tpacked->base.begin_transaction = odb_source_packed_begin_transaction;\n+\tpacked->base.read_alternates = odb_source_packed_read_alternates;\n+\tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545040","messageId":"20260609-pks-odb-source-packed-v2-16-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 16/17] midx: refactor interfaces to work on \"packed\" source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:09Z","receivedAt":"2026-06-09T08:51:49Z","isPatch":true,"body":"Our interfaces used to interact with MIDXs all work on top of the\ngeneric `struct odb_source`. This doesn't make much sense though: a MIDX\nis strictly tied to the \"packed\" source, so passing in a generic source\ngives the false sense that it may also work with a different type of\nsource.\n\nFix this conceptual weirdness and instead require the caller to pass in\na \"packed\" source explicitly. This also makes the next commit easier to\nimplement, where we drop the pointer to the \"files\" source in the\n\"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/multi-pack-index.c |  29 +++++------\n builtin/pack-objects.c     |   3 +-\n builtin/repack.c           |   8 ++-\n midx-write.c               |  34 ++++++-------\n midx.c                     | 118 ++++++++++++++++++++++-----------------------\n midx.h                     |  30 ++++++------\n odb/source-packed.c        |  12 ++---\n pack-bitmap.c              |   8 +--\n pack-revindex.c            |   6 +--\n repack-geometry.c          |   3 +-\n repack-midx.c              |   9 ++--\n repack.c                   |   6 +--\n t/helper/test-read-midx.c  |   7 ++-\n 13 files changed, 144 insertions(+), 129 deletions(-)\n\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 00ffb36394..6e73c85cde 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -10,6 +10,7 @@\n #include \"trace2.h\"\n #include \"odb.h\"\n #include \"odb/source.h\"\n+#include \"odb/source-files.h\"\n #include \"replace-object.h\"\n #include \"repository.h\"\n \n@@ -85,12 +86,12 @@ static int parse_object_dir(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n-static struct odb_source *handle_object_dir_option(struct repository *repo)\n+static struct odb_source_files *handle_object_dir_option(struct repository *repo)\n {\n \tstruct odb_source *source = odb_find_source(repo->objects, opts.object_dir);\n \tif (!source)\n \t\tsource = odb_add_to_alternates_memory(repo->objects, opts.object_dir);\n-\treturn source;\n+\treturn odb_source_files_downcast(source);\n }\n \n static struct option common_opts[] = {\n@@ -167,7 +168,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \t\t\t     N_(\"refs snapshot for selecting bitmap commits\")),\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \tint ret;\n \n \topts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;\n@@ -211,7 +212,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \n \t\tread_packs_from_stdin(&packs);\n \n-\t\tret = write_midx_file_only(source, &packs,\n+\t\tret = write_midx_file_only(source->packed, &packs,\n \t\t\t\t\t   opts.preferred_pack,\n \t\t\t\t\t   opts.refs_snapshot,\n \t\t\t\t\t   opts.incremental_base, opts.flags);\n@@ -223,7 +224,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \n \t}\n \n-\tret = write_midx_file(source, opts.preferred_pack,\n+\tret = write_midx_file(source->packed, opts.preferred_pack,\n \t\t\t      opts.refs_snapshot, opts.flags);\n \n \tfree(opts.refs_snapshot);\n@@ -237,7 +238,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,\n \tstruct multi_pack_index *m, *cur;\n \tstruct multi_pack_index *from_midx = NULL;\n \tstruct multi_pack_index *to_midx = NULL;\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \tint ret;\n \n \tstruct option *options;\n@@ -282,7 +283,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\tm = get_multi_pack_index(source);\n+\tm = get_multi_pack_index(source->packed);\n \n \tfor (cur = m; cur && !(from_midx && to_midx); cur = cur->base_midx) {\n \t\tconst char *midx_csum = midx_get_checksum_hex(cur);\n@@ -305,7 +306,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,\n \t\t\tdie(_(\"MIDX %s must be an ancestor of %s\"), argv[0], argv[1]);\n \t}\n \n-\tret = write_midx_file_compact(source, from_midx, to_midx,\n+\tret = write_midx_file_compact(source->packed, from_midx, to_midx,\n \t\t\t\t      opts.incremental_base, opts.flags);\n \n \treturn ret;\n@@ -319,7 +320,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,\n \tstatic struct option builtin_multi_pack_index_verify_options[] = {\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \n \toptions = add_common_options(builtin_multi_pack_index_verify_options);\n \n@@ -337,7 +338,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\treturn verify_midx_file(source, opts.flags);\n+\treturn verify_midx_file(source->packed, opts.flags);\n }\n \n static int cmd_multi_pack_index_expire(int argc, const char **argv,\n@@ -348,7 +349,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,\n \tstatic struct option builtin_multi_pack_index_expire_options[] = {\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \n \toptions = add_common_options(builtin_multi_pack_index_expire_options);\n \n@@ -366,7 +367,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\treturn expire_midx_packs(source, opts.flags);\n+\treturn expire_midx_packs(source->packed, opts.flags);\n }\n \n static int cmd_multi_pack_index_repack(int argc, const char **argv,\n@@ -379,7 +380,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,\n \t\t  N_(\"during repack, collect pack-files of smaller size into a batch that is larger than this size\")),\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \n \toptions = add_common_options(builtin_multi_pack_index_repack_options);\n \n@@ -398,7 +399,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\treturn midx_repack(source, (size_t)opts.batch_size, opts.flags);\n+\treturn midx_repack(source->packed, (size_t)opts.batch_size, opts.flags);\n }\n \n int cmd_multi_pack_index(int argc,\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 5e94805478..424c92cc29 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -1775,7 +1775,8 @@ static int want_object_in_pack_mtime(const struct object_id *oid,\n \todb_prepare_alternates(the_repository->objects);\n \n \tfor (source = the_repository->objects->sources; source; source = source->next) {\n-\t\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \t\tstruct pack_entry e;\n \n \t\tif (m && fill_midx_entry(m, oid, &e)) {\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 1524a9c13a..47966a686b 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -458,6 +458,8 @@ int cmd_repack(int argc,\n \t}\n \n \tif (!names.nr) {\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(existing.source);\n+\n \t\tif (!po_args.quiet)\n \t\t\tprintf_ln(_(\"Nothing new to pack.\"));\n \t\t/*\n@@ -473,7 +475,7 @@ int cmd_repack(int argc,\n \t\t * midx_has_unknown_packs() will make the decision for\n \t\t * us.\n \t\t */\n-\t\tif (!get_multi_pack_index(existing.source))\n+\t\tif (!get_multi_pack_index(files->packed))\n \t\t\tmidx_must_contain_cruft = 1;\n \t}\n \n@@ -626,10 +628,12 @@ int cmd_repack(int argc,\n \t\tupdate_server_info(repo, 0);\n \n \tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) {\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(existing.source);\n \t\tunsigned flags = 0;\n+\n \t\tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0))\n \t\t\tflags |= MIDX_WRITE_INCREMENTAL;\n-\t\twrite_midx_file(existing.source, NULL, NULL, flags);\n+\t\twrite_midx_file(files->packed, NULL, NULL, flags);\n \t}\n \n cleanup:\ndiff --git a/midx-write.c b/midx-write.c\nindex 561e9eedc0..7cafc49fb8 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -25,9 +25,9 @@\n #define NO_PREFERRED_PACK (~((uint32_t)0))\n \n extern int midx_checksum_valid(struct multi_pack_index *m);\n-extern void clear_midx_files_ext(struct odb_source *source, const char *ext,\n+extern void clear_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t\t const char *keep_hash);\n-extern void clear_incremental_midx_files_ext(struct odb_source *source,\n+extern void clear_incremental_midx_files_ext(struct odb_source_packed *source,\n \t\t\t\t\t     const char *ext,\n \t\t\t\t\t     const struct strvec *keep_hashes);\n extern int cmp_idx_or_pack_name(const char *idx_or_pack_name,\n@@ -119,7 +119,7 @@ struct write_midx_context {\n \tstruct string_list *to_include;\n \n \tstruct repository *repo;\n-\tstruct odb_source *source;\n+\tstruct odb_source_packed *source;\n };\n \n static uint32_t midx_pack_perm(struct write_midx_context *ctx,\n@@ -1107,7 +1107,7 @@ static int link_midx_to_chain(struct multi_pack_index *m)\n \treturn ret;\n }\n \n-static void clear_midx_files(struct odb_source *source,\n+static void clear_midx_files(struct odb_source_packed *source,\n \t\t\t     const struct strvec *hashes, unsigned incremental)\n {\n \t/*\n@@ -1237,7 +1237,7 @@ static int midx_hashcmp(const struct multi_pack_index *a,\n }\n \n struct write_midx_opts {\n-\tstruct odb_source *source; /* non-optional */\n+\tstruct odb_source_packed *source; /* non-optional */\n \n \tstruct string_list *packs_to_include;\n \tstruct string_list *packs_to_drop;\n@@ -1253,7 +1253,7 @@ struct write_midx_opts {\n \n static int write_midx_internal(struct write_midx_opts *opts)\n {\n-\tstruct repository *r = opts->source->odb->repo;\n+\tstruct repository *r = opts->source->base.odb->repo;\n \tstruct strbuf midx_name = STRBUF_INIT;\n \tunsigned char midx_hash[GIT_MAX_RAWSZ];\n \tuint32_t start_pack;\n@@ -1301,7 +1301,7 @@ static int write_midx_internal(struct write_midx_opts *opts)\n \tif (ctx.incremental)\n \t\tstrbuf_addf(&midx_name,\n \t\t\t    \"%s/pack/multi-pack-index.d/tmp_midx_XXXXXX\",\n-\t\t\t    opts->source->path);\n+\t\t\t    opts->source->base.path);\n \telse\n \t\tget_midx_filename(opts->source, &midx_name);\n \tif (safe_create_leading_directories(r, midx_name.buf))\n@@ -1396,7 +1396,7 @@ static int write_midx_internal(struct write_midx_opts *opts)\n \t\tfill_packs_from_midx_range(&ctx, bitmap_order);\n \t} else {\n \t\tctx.to_include = opts->packs_to_include;\n-\t\tfor_each_file_in_pack_dir(opts->source->path, add_pack_to_midx, &ctx);\n+\t\tfor_each_file_in_pack_dir(opts->source->base.path, add_pack_to_midx, &ctx);\n \t}\n \tstop_progress(&ctx.progress);\n \n@@ -1847,7 +1847,7 @@ static int write_midx_internal(struct write_midx_opts *opts)\n \treturn result;\n }\n \n-int write_midx_file(struct odb_source *source,\n+int write_midx_file(struct odb_source_packed *source,\n \t\t    const char *preferred_pack_name,\n \t\t    const char *refs_snapshot,\n \t\t    unsigned flags)\n@@ -1862,7 +1862,7 @@ int write_midx_file(struct odb_source *source,\n \treturn write_midx_internal(&opts);\n }\n \n-int write_midx_file_only(struct odb_source *source,\n+int write_midx_file_only(struct odb_source_packed *source,\n \t\t\t struct string_list *packs_to_include,\n \t\t\t const char *preferred_pack_name,\n \t\t\t const char *refs_snapshot,\n@@ -1881,7 +1881,7 @@ int write_midx_file_only(struct odb_source *source,\n \treturn write_midx_internal(&opts);\n }\n \n-int write_midx_file_compact(struct odb_source *source,\n+int write_midx_file_compact(struct odb_source_packed *source,\n \t\t\t    struct multi_pack_index *from,\n \t\t\t    struct multi_pack_index *to,\n \t\t\t    const char *incremental_base,\n@@ -1898,7 +1898,7 @@ int write_midx_file_compact(struct odb_source *source,\n \treturn write_midx_internal(&opts);\n }\n \n-int expire_midx_packs(struct odb_source *source, unsigned flags)\n+int expire_midx_packs(struct odb_source_packed *source, unsigned flags)\n {\n \tuint32_t i, *count, result = 0;\n \tstruct string_list packs_to_drop = STRING_LIST_INIT_DUP;\n@@ -1915,7 +1915,7 @@ int expire_midx_packs(struct odb_source *source, unsigned flags)\n \n \tif (flags & MIDX_PROGRESS)\n \t\tprogress = start_delayed_progress(\n-\t\t\t\t\t  source->odb->repo,\n+\t\t\t\t\t  source->base.odb->repo,\n \t\t\t\t\t  _(\"Counting referenced objects\"),\n \t\t\t\t\t  m->num_objects);\n \tfor (i = 0; i < m->num_objects; i++) {\n@@ -1927,7 +1927,7 @@ int expire_midx_packs(struct odb_source *source, unsigned flags)\n \n \tif (flags & MIDX_PROGRESS)\n \t\tprogress = start_delayed_progress(\n-\t\t\t\t\t  source->odb->repo,\n+\t\t\t\t\t  source->base.odb->repo,\n \t\t\t\t\t  _(\"Finding and deleting unreferenced packfiles\"),\n \t\t\t\t\t  m->num_packs);\n \tfor (i = 0; i < m->num_packs; i++) {\n@@ -2085,9 +2085,9 @@ static void fill_included_packs_batch(struct repository *r,\n \tfree(pack_info);\n }\n \n-int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags)\n+int midx_repack(struct odb_source_packed *source, size_t batch_size, unsigned flags)\n {\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \tint result = 0;\n \tuint32_t i, packs_to_repack = 0;\n \tunsigned char *include_pack;\n@@ -2131,7 +2131,7 @@ int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags)\n \n \tstrvec_push(&cmd.args, \"pack-objects\");\n \n-\tstrvec_pushf(&cmd.args, \"%s/pack/pack\", source->path);\n+\tstrvec_pushf(&cmd.args, \"%s/pack/pack\", source->base.path);\n \n \tif (delta_base_offset)\n \t\tstrvec_push(&cmd.args, \"--delta-base-offset\");\ndiff --git a/midx.c b/midx.c\nindex 00bbd137b2..cc6b94f9dd 100644\n--- a/midx.c\n+++ b/midx.c\n@@ -17,9 +17,9 @@\n #define MIDX_PACK_ERROR ((void *)(intptr_t)-1)\n \n int midx_checksum_valid(struct multi_pack_index *m);\n-void clear_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t  const char *keep_hash);\n-void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t\t      const struct strvec *keep_hashes);\n int cmp_idx_or_pack_name(const char *idx_or_pack_name,\n \t\t\t const char *idx_name);\n@@ -27,25 +27,25 @@ int cmp_idx_or_pack_name(const char *idx_or_pack_name,\n const char *midx_get_checksum_hex(const struct multi_pack_index *m)\n {\n \treturn hash_to_hex_algop(midx_get_checksum_hash(m),\n-\t\t\t\t m->source->odb->repo->hash_algo);\n+\t\t\t\t m->source->base.odb->repo->hash_algo);\n }\n \n const unsigned char *midx_get_checksum_hash(const struct multi_pack_index *m)\n {\n-\treturn m->data + m->data_len - m->source->odb->repo->hash_algo->rawsz;\n+\treturn m->data + m->data_len - m->source->base.odb->repo->hash_algo->rawsz;\n }\n \n-void get_midx_filename(struct odb_source *source, struct strbuf *out)\n+void get_midx_filename(struct odb_source_packed *source, struct strbuf *out)\n {\n \tget_midx_filename_ext(source, out, NULL, NULL);\n }\n \n-void get_midx_filename_ext(struct odb_source *source, struct strbuf *out,\n+void get_midx_filename_ext(struct odb_source_packed *source, struct strbuf *out,\n \t\t\t   const unsigned char *hash, const char *ext)\n {\n-\tstrbuf_addf(out, \"%s/pack/multi-pack-index\", source->path);\n+\tstrbuf_addf(out, \"%s/pack/multi-pack-index\", source->base.path);\n \tif (ext)\n-\t\tstrbuf_addf(out, \"-%s.%s\", hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext);\n+\t\tstrbuf_addf(out, \"-%s.%s\", hash_to_hex_algop(hash, source->base.odb->repo->hash_algo), ext);\n }\n \n static int midx_read_oid_fanout(const unsigned char *chunk_start,\n@@ -99,17 +99,16 @@ static int midx_read_object_offsets(const unsigned char *chunk_start,\n \treturn 0;\n }\n \n-struct multi_pack_index *get_multi_pack_index(struct odb_source *source)\n+struct multi_pack_index *get_multi_pack_index(struct odb_source_packed *source)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\todb_source_packed_prepare(files->packed);\n-\treturn files->packed->midx;\n+\todb_source_packed_prepare(source);\n+\treturn source->midx;\n }\n \n-static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *source,\n+static struct multi_pack_index *load_multi_pack_index_one(struct odb_source_packed *source,\n \t\t\t\t\t\t\t  const char *midx_name)\n {\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \tstruct multi_pack_index *m = NULL;\n \tint fd;\n \tstruct stat st;\n@@ -234,23 +233,23 @@ static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *sou\n \treturn NULL;\n }\n \n-void get_midx_chain_dirname(struct odb_source *source, struct strbuf *buf)\n+void get_midx_chain_dirname(struct odb_source_packed *source, struct strbuf *buf)\n {\n-\tstrbuf_addf(buf, \"%s/pack/multi-pack-index.d\", source->path);\n+\tstrbuf_addf(buf, \"%s/pack/multi-pack-index.d\", source->base.path);\n }\n \n-void get_midx_chain_filename(struct odb_source *source, struct strbuf *buf)\n+void get_midx_chain_filename(struct odb_source_packed *source, struct strbuf *buf)\n {\n \tget_midx_chain_dirname(source, buf);\n \tstrbuf_addstr(buf, \"/multi-pack-index-chain\");\n }\n \n-void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf,\n+void get_split_midx_filename_ext(struct odb_source_packed *source, struct strbuf *buf,\n \t\t\t\t const unsigned char *hash, const char *ext)\n {\n \tget_midx_chain_dirname(source, buf);\n \tstrbuf_addf(buf, \"/multi-pack-index-%s.%s\",\n-\t\t    hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext);\n+\t\t    hash_to_hex_algop(hash, source->base.odb->repo->hash_algo), ext);\n }\n \n static int open_multi_pack_index_chain(const struct git_hash_algo *hash_algo,\n@@ -306,11 +305,11 @@ static int add_midx_to_chain(struct multi_pack_index *midx,\n \treturn 1;\n }\n \n-static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source,\n+static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source_packed *source,\n \t\t\t\t\t\t      int fd, struct stat *st,\n \t\t\t\t\t\t      int *incomplete_chain)\n {\n-\tconst struct git_hash_algo *hash_algo = source->odb->repo->hash_algo;\n+\tconst struct git_hash_algo *hash_algo = source->base.odb->repo->hash_algo;\n \tstruct multi_pack_index *midx_chain = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint valid = 1;\n@@ -362,7 +361,7 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source,\n \treturn midx_chain;\n }\n \n-static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *source)\n+static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source_packed *source)\n {\n \tstruct strbuf chain_file = STRBUF_INIT;\n \tstruct stat st;\n@@ -370,7 +369,8 @@ static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *s\n \tstruct multi_pack_index *m = NULL;\n \n \tget_midx_chain_filename(source, &chain_file);\n-\tif (open_multi_pack_index_chain(source->odb->repo->hash_algo, chain_file.buf, &fd, &st)) {\n+\tif (open_multi_pack_index_chain(source->base.odb->repo->hash_algo,\n+\t\t\t\t\tchain_file.buf, &fd, &st)) {\n \t\tint incomplete;\n \t\t/* ownership of fd is taken over by load function */\n \t\tm = load_midx_chain_fd_st(source, fd, &st, &incomplete);\n@@ -380,7 +380,7 @@ static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *s\n \treturn m;\n }\n \n-struct multi_pack_index *load_multi_pack_index(struct odb_source *source)\n+struct multi_pack_index *load_multi_pack_index(struct odb_source_packed *source)\n {\n \tstruct strbuf midx_name = STRBUF_INIT;\n \tstruct multi_pack_index *m;\n@@ -456,7 +456,7 @@ static uint32_t midx_for_pack(struct multi_pack_index **_m,\n int prepare_midx_pack(struct multi_pack_index *m,\n \t\t      uint32_t pack_int_id)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(m->source);\n+\tstruct odb_source_packed *packed = m->source;\n \tstruct strbuf pack_name = STRBUF_INIT;\n \tstruct packed_git *p;\n \n@@ -467,10 +467,10 @@ int prepare_midx_pack(struct multi_pack_index *m,\n \tif (m->packs[pack_int_id])\n \t\treturn 0;\n \n-\tstrbuf_addf(&pack_name, \"%s/pack/%s\", files->base.path,\n+\tstrbuf_addf(&pack_name, \"%s/pack/%s\", packed->base.path,\n \t\t    m->pack_names[pack_int_id]);\n-\tp = packfile_store_load_pack(files->packed,\n-\t\t\t\t     pack_name.buf, files->base.local);\n+\tp = packfile_store_load_pack(packed,\n+\t\t\t\t     pack_name.buf, packed->base.local);\n \tstrbuf_release(&pack_name);\n \n \tif (!p) {\n@@ -523,7 +523,7 @@ int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m,\n {\n \tint ret = bsearch_hash(oid->hash, m->chunk_oid_fanout,\n \t\t\t       m->chunk_oid_lookup,\n-\t\t\t       m->source->odb->repo->hash_algo->rawsz,\n+\t\t\t       m->source->base.odb->repo->hash_algo->rawsz,\n \t\t\t       result);\n \tif (result)\n \t\t*result += m->num_objects_in_base;\n@@ -554,7 +554,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,\n \tn = midx_for_object(&m, n);\n \n \toidread(oid, m->chunk_oid_lookup + st_mult(m->hash_len, n),\n-\t\tm->source->odb->repo->hash_algo);\n+\t\tm->source->base.odb->repo->hash_algo);\n \treturn oid;\n }\n \n@@ -734,26 +734,25 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id)\n \treturn 0;\n }\n \n-int prepare_multi_pack_index_one(struct odb_source *source)\n+int prepare_multi_pack_index_one(struct odb_source_packed *source)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \n \tprepare_repo_settings(r);\n \tif (!r->settings.core_multi_pack_index)\n \t\treturn 0;\n \n-\tif (files->packed->midx)\n+\tif (source->midx)\n \t\treturn 1;\n \n-\tfiles->packed->midx = load_multi_pack_index(source);\n+\tsource->midx = load_multi_pack_index(source);\n \n-\treturn !!files->packed->midx;\n+\treturn !!source->midx;\n }\n \n int midx_checksum_valid(struct multi_pack_index *m)\n {\n-\treturn hashfile_checksum_valid(m->source->odb->repo->hash_algo,\n+\treturn hashfile_checksum_valid(m->source->base.odb->repo->hash_algo,\n \t\t\t\t       m->data, m->data_len);\n }\n \n@@ -776,7 +775,7 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len UNUS\n \t\tdie_errno(_(\"failed to remove %s\"), full_path);\n }\n \n-void clear_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t  const char *keep_hash)\n {\n \tstruct clear_midx_data data = {\n@@ -793,12 +792,12 @@ void clear_midx_files_ext(struct odb_source *source, const char *ext,\n \t\tstrbuf_release(&buf);\n \t}\n \n-\tfor_each_file_in_pack_dir(source->path, clear_midx_file_ext, &data);\n+\tfor_each_file_in_pack_dir(source->base.path, clear_midx_file_ext, &data);\n \n \tstrset_clear(&data.keep);\n }\n \n-void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t\t      const struct strvec *keep_hashes)\n {\n \tstruct clear_midx_data data = {\n@@ -817,7 +816,7 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext\n \t\t}\n \t}\n \n-\tfor_each_file_in_pack_subdir(source->path, \"multi-pack-index.d\",\n+\tfor_each_file_in_pack_subdir(source->base.path, \"multi-pack-index.d\",\n \t\t\t\t     clear_midx_file_ext, &data);\n \n \tstrbuf_release(&buf);\n@@ -826,26 +825,28 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext\n \n void clear_midx_file(struct repository *r)\n {\n+\tstruct odb_source_files *files;\n \tstruct strbuf midx = STRBUF_INIT;\n \n-\tget_midx_filename(r->objects->sources, &midx);\n-\n \tif (r->objects) {\n \t\tstruct odb_source *source;\n \n \t\tfor (source = r->objects->sources; source; source = source->next) {\n-\t\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\t\tfiles = odb_source_files_downcast(source);\n \t\t\tif (files->packed->midx)\n \t\t\t\tclose_midx(files->packed->midx);\n \t\t\tfiles->packed->midx = NULL;\n \t\t}\n \t}\n \n+\tfiles = odb_source_files_downcast(r->objects->sources);\n+\tget_midx_filename(files->packed, &midx);\n+\n \tif (remove_path(midx.buf))\n \t\tdie(_(\"failed to clear multi-pack-index at %s\"), midx.buf);\n \n-\tclear_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP, NULL);\n-\tclear_midx_files_ext(r->objects->sources, MIDX_EXT_REV, NULL);\n+\tclear_midx_files_ext(files->packed, MIDX_EXT_BITMAP, NULL);\n+\tclear_midx_files_ext(files->packed, MIDX_EXT_REV, NULL);\n \n \tstrbuf_release(&midx);\n }\n@@ -853,28 +854,27 @@ void clear_midx_file(struct repository *r)\n void clear_incremental_midx_files(struct repository *r,\n \t\t\t\t  const struct strvec *keep_hashes)\n {\n-\tstruct odb_source *source = r->objects->sources;\n+\tstruct odb_source_files *files;\n+\tstruct odb_source *source;\n \tstruct strbuf chain = STRBUF_INIT;\n \n-\tget_midx_chain_filename(source, &chain);\n-\n-\tfor (; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\tfor (source = r->objects->sources; source; source = source->next) {\n+\t\tfiles = odb_source_files_downcast(source);\n \t\tif (files->packed->midx)\n \t\t\tclose_midx(files->packed->midx);\n \t\tfiles->packed->midx = NULL;\n \t}\n \n+\tfiles = odb_source_files_downcast(r->objects->sources);\n+\tget_midx_chain_filename(files->packed, &chain);\n+\n \tif (!keep_hashes && remove_path(chain.buf))\n \t\tdie(_(\"failed to clear multi-pack-index chain at %s\"),\n \t\t    chain.buf);\n \n-\tclear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP,\n-\t\t\t\t\t keep_hashes);\n-\tclear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_REV,\n-\t\t\t\t\t keep_hashes);\n-\tclear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_MIDX,\n-\t\t\t\t\t keep_hashes);\n+\tclear_incremental_midx_files_ext(files->packed, MIDX_EXT_BITMAP, keep_hashes);\n+\tclear_incremental_midx_files_ext(files->packed, MIDX_EXT_REV, keep_hashes);\n+\tclear_incremental_midx_files_ext(files->packed, MIDX_EXT_MIDX, keep_hashes);\n \n \tstrbuf_release(&chain);\n }\n@@ -918,9 +918,9 @@ static int compare_pair_pos_vs_id(const void *_a, const void *_b)\n \t\t\tdisplay_progress(progress, _n); \\\n \t} while (0)\n \n-int verify_midx_file(struct odb_source *source, unsigned flags)\n+int verify_midx_file(struct odb_source_packed *source, unsigned flags)\n {\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \tstruct pair_pos_vs_id *pairs = NULL;\n \tuint32_t i;\n \tstruct progress *progress = NULL;\ndiff --git a/midx.h b/midx.h\nindex 63853a03a4..939c18e588 100644\n--- a/midx.h\n+++ b/midx.h\n@@ -37,7 +37,7 @@ struct strvec;\n \t\"GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL\"\n \n struct multi_pack_index {\n-\tstruct odb_source *source;\n+\tstruct odb_source_packed *source;\n \n \tconst unsigned char *data;\n \tsize_t data_len;\n@@ -92,16 +92,16 @@ struct multi_pack_index {\n \n const char *midx_get_checksum_hex(const struct multi_pack_index *m) /* static buffer */;\n const unsigned char *midx_get_checksum_hash(const struct multi_pack_index *m);\n-void get_midx_filename(struct odb_source *source, struct strbuf *out);\n-void get_midx_filename_ext(struct odb_source *source, struct strbuf *out,\n+void get_midx_filename(struct odb_source_packed *source, struct strbuf *out);\n+void get_midx_filename_ext(struct odb_source_packed *source, struct strbuf *out,\n \t\t\t   const unsigned char *hash, const char *ext);\n-void get_midx_chain_dirname(struct odb_source *source, struct strbuf *out);\n-void get_midx_chain_filename(struct odb_source *source, struct strbuf *out);\n-void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf,\n+void get_midx_chain_dirname(struct odb_source_packed *source, struct strbuf *out);\n+void get_midx_chain_filename(struct odb_source_packed *source, struct strbuf *out);\n+void get_split_midx_filename_ext(struct odb_source_packed *source, struct strbuf *buf,\n \t\t\t\t const unsigned char *hash, const char *ext);\n \n-struct multi_pack_index *get_multi_pack_index(struct odb_source *source);\n-struct multi_pack_index *load_multi_pack_index(struct odb_source *source);\n+struct multi_pack_index *get_multi_pack_index(struct odb_source_packed *source);\n+struct multi_pack_index *load_multi_pack_index(struct odb_source_packed *source);\n int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id);\n struct packed_git *nth_midxed_pack(struct multi_pack_index *m,\n \t\t\t\t   uint32_t pack_int_id);\n@@ -123,22 +123,22 @@ int midx_contains_pack(struct multi_pack_index *m,\n int midx_layer_contains_pack(struct multi_pack_index *m,\n \t\t\t     const char *idx_or_pack_name);\n int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id);\n-int prepare_multi_pack_index_one(struct odb_source *source);\n+int prepare_multi_pack_index_one(struct odb_source_packed *source);\n \n /*\n  * Variant of write_midx_file which writes a MIDX containing only the packs\n  * specified in packs_to_include.\n  */\n-int write_midx_file(struct odb_source *source,\n+int write_midx_file(struct odb_source_packed *source,\n \t\t    const char *preferred_pack_name, const char *refs_snapshot,\n \t\t    unsigned flags);\n-int write_midx_file_only(struct odb_source *source,\n+int write_midx_file_only(struct odb_source_packed *source,\n \t\t\t struct string_list *packs_to_include,\n \t\t\t const char *preferred_pack_name,\n \t\t\t const char *refs_snapshot,\n \t\t\t const char *incremental_base,\n \t\t\t unsigned flags);\n-int write_midx_file_compact(struct odb_source *source,\n+int write_midx_file_compact(struct odb_source_packed *source,\n \t\t\t    struct multi_pack_index *from,\n \t\t\t    struct multi_pack_index *to,\n \t\t\t    const char *incremental_base,\n@@ -146,9 +146,9 @@ int write_midx_file_compact(struct odb_source *source,\n void clear_midx_file(struct repository *r);\n void clear_incremental_midx_files(struct repository *r,\n \t\t\t\t  const struct strvec *keep_hashes);\n-int verify_midx_file(struct odb_source *source, unsigned flags);\n-int expire_midx_packs(struct odb_source *source, unsigned flags);\n-int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags);\n+int verify_midx_file(struct odb_source_packed *source, unsigned flags);\n+int expire_midx_packs(struct odb_source_packed *source, unsigned flags);\n+int midx_repack(struct odb_source_packed *source, size_t batch_size, unsigned flags);\n \n void close_midx(struct multi_pack_index *m);\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 08a2de9bc5..d513b3efc3 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -136,8 +136,8 @@ static int for_each_prefixed_object_in_midx(\n \n \tfor (; m; m = m->base_midx) {\n \t\tuint32_t num, i, first = 0;\n-\t\tint len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ?\n-\t\t\tm->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n+\t\tint len = opts->prefix_hex_len > m->source->base.odb->repo->hash_algo->hexsz ?\n+\t\t\tm->source->base.odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n \n \t\tif (!m->num_objects)\n \t\t\tcontinue;\n@@ -249,7 +249,7 @@ static int odb_source_packed_for_each_prefixed_object(\n \n \tstore->skip_mru_updates = true;\n \n-\tm = get_multi_pack_index(&store->files->base);\n+\tm = get_multi_pack_index(store);\n \tif (m) {\n \t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n \t\tif (ret)\n@@ -348,7 +348,7 @@ static int odb_source_packed_count_objects(struct odb_source *source,\n \tunsigned long count = 0;\n \tint ret;\n \n-\tm = get_multi_pack_index(&packed->files->base);\n+\tm = get_multi_pack_index(packed);\n \tif (m)\n \t\tcount += m->num_objects + m->num_objects_in_base;\n \n@@ -465,7 +465,7 @@ static int odb_source_packed_find_abbrev_len(struct odb_source *source,\n \tstruct packfile_list_entry *e;\n \tstruct multi_pack_index *m;\n \n-\tm = get_multi_pack_index(&packed->files->base);\n+\tm = get_multi_pack_index(packed);\n \tif (m)\n \t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n \n@@ -674,7 +674,7 @@ void odb_source_packed_prepare(struct odb_source_packed *source)\n \tif (source->initialized)\n \t\treturn;\n \n-\tprepare_multi_pack_index_one(&source->files->base);\n+\tprepare_multi_pack_index_one(source);\n \tprepare_packed_git_one(&source->files->base);\n \n \tsort_packs(&source->packs.head, sort_pack);\ndiff --git a/pack-bitmap.c b/pack-bitmap.c\nindex f9af8a96bd..6bfcbc8ce6 100644\n--- a/pack-bitmap.c\n+++ b/pack-bitmap.c\n@@ -238,7 +238,7 @@ static uint32_t bitmap_name_hash(struct bitmap_index *index, uint32_t pos)\n static struct repository *bitmap_repo(struct bitmap_index *bitmap_git)\n {\n \tif (bitmap_is_midx(bitmap_git))\n-\t\treturn bitmap_git->midx->source->odb->repo;\n+\t\treturn bitmap_git->midx->source->base.odb->repo;\n \treturn bitmap_git->pack->repo;\n }\n \n@@ -711,7 +711,8 @@ static int open_midx_bitmap(struct repository *r,\n \n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct multi_pack_index *midx = get_multi_pack_index(source);\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct multi_pack_index *midx = get_multi_pack_index(files->packed);\n \t\tif (midx && !open_midx_bitmap_1(bitmap_git, midx))\n \t\t\tret = 0;\n \t}\n@@ -3399,7 +3400,8 @@ int verify_bitmap_files(struct repository *r)\n \n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \t\tchar *midx_bitmap_name;\n \n \t\tif (!m)\ndiff --git a/pack-revindex.c b/pack-revindex.c\nindex 1b67863606..62387ae632 100644\n--- a/pack-revindex.c\n+++ b/pack-revindex.c\n@@ -383,13 +383,13 @@ int load_midx_revindex(struct multi_pack_index *m)\n \t\t * not want to accidentally call munmap() in the middle of the\n \t\t * MIDX.\n \t\t */\n-\t\ttrace2_data_string(\"load_midx_revindex\", m->source->odb->repo,\n+\t\ttrace2_data_string(\"load_midx_revindex\", m->source->base.odb->repo,\n \t\t\t\t   \"source\", \"midx\");\n \t\tm->revindex_data = (const uint32_t *)m->chunk_revindex;\n \t\treturn 0;\n \t}\n \n-\ttrace2_data_string(\"load_midx_revindex\", m->source->odb->repo,\n+\ttrace2_data_string(\"load_midx_revindex\", m->source->base.odb->repo,\n \t\t\t   \"source\", \"rev\");\n \n \tif (m->has_chain)\n@@ -401,7 +401,7 @@ int load_midx_revindex(struct multi_pack_index *m)\n \t\t\t\t      midx_get_checksum_hash(m),\n \t\t\t\t      MIDX_EXT_REV);\n \n-\tret = load_revindex_from_disk(m->source->odb->repo->hash_algo,\n+\tret = load_revindex_from_disk(m->source->base.odb->repo->hash_algo,\n \t\t\t\t      revindex_name.buf,\n \t\t\t\t      m->num_objects,\n \t\t\t\t      &m->revindex_map,\ndiff --git a/repack-geometry.c b/repack-geometry.c\nindex 2064683dcf..15b3412950 100644\n--- a/repack-geometry.c\n+++ b/repack-geometry.c\n@@ -32,7 +32,8 @@ void pack_geometry_init(struct pack_geometry *geometry,\n {\n \tstruct packed_git *p;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct multi_pack_index *m = get_multi_pack_index(existing->source);\n+\tstruct odb_source_files *files = odb_source_files_downcast(existing->source);\n+\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \n \trepo_for_each_pack(existing->repo, p) {\n \t\tif (geometry->midx_layer_threshold_set && m &&\ndiff --git a/repack-midx.c b/repack-midx.c\nindex b6b1de7180..7c7c3620e5 100644\n--- a/repack-midx.c\n+++ b/repack-midx.c\n@@ -557,13 +557,14 @@ static void repack_make_midx_append_plan(struct repack_write_midx_opts *opts,\n \t\t\t\t\t struct midx_compaction_step **steps_p,\n \t\t\t\t\t size_t *steps_nr_p)\n {\n+\tstruct odb_source_files *files = odb_source_files_downcast(opts->existing->source);\n \tstruct multi_pack_index *m;\n \tstruct midx_compaction_step *steps = NULL;\n \tstruct midx_compaction_step *step;\n \tsize_t steps_nr = 0, steps_alloc = 0;\n \n \todb_reprepare(opts->existing->repo->objects);\n-\tm = get_multi_pack_index(opts->existing->source);\n+\tm = get_multi_pack_index(files->packed);\n \n \tif (opts->names->nr) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n@@ -606,6 +607,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,\n \t\t\t\t\t    struct midx_compaction_step **steps_p,\n \t\t\t\t\t    size_t *steps_nr_p)\n {\n+\tstruct odb_source_files *files = odb_source_files_downcast(opts->existing->source);\n \tstruct multi_pack_index *m;\n \tstruct midx_compaction_step *steps = NULL;\n \tstruct midx_compaction_step step = { 0 };\n@@ -618,7 +620,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,\n \t\t\t    opts->existing->repo);\n \n \todb_reprepare(opts->existing->repo->objects);\n-\tm = get_multi_pack_index(opts->existing->source);\n+\tm = get_multi_pack_index(files->packed);\n \n \tfor (i = 0; m && i < m->num_packs + m->num_packs_in_base; i++) {\n \t\tif (prepare_midx_pack(m, i)) {\n@@ -938,6 +940,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,\n \n static int write_midx_incremental(struct repack_write_midx_opts *opts)\n {\n+\tstruct odb_source_files *files = odb_source_files_downcast(opts->existing->source);\n \tstruct midx_compaction_step *steps = NULL;\n \tstruct strbuf lock_name = STRBUF_INIT;\n \tstruct lock_file lf;\n@@ -946,7 +949,7 @@ static int write_midx_incremental(struct repack_write_midx_opts *opts)\n \tsize_t i;\n \tint ret = 0;\n \n-\tget_midx_chain_filename(opts->existing->source, &lock_name);\n+\tget_midx_chain_filename(files->packed, &lock_name);\n \tif (safe_create_leading_directories(opts->existing->repo,\n \t\t\t\t\t    lock_name.buf))\n \t\tdie_errno(_(\"unable to create leading directories of %s\"),\ndiff --git a/repack.c b/repack.c\nindex 571dabb665..d2aa58e134 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -59,10 +59,10 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \t\t\t\t  bool wrote_incremental_midx)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct odb_source *source = repo->objects->sources;\n-\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\tstruct odb_source_files *files = odb_source_files_downcast(repo->objects->sources);\n+\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \tstrbuf_addf(&buf, \"%s.pack\", base_name);\n-\tif (m && source->local && midx_contains_pack(m, buf.buf)) {\n+\tif (m && files->base.local && midx_contains_pack(m, buf.buf)) {\n \t\tclear_midx_file(repo);\n \t\tif (!wrote_incremental_midx)\n \t\t\tclear_incremental_midx_files(repo, NULL);\ndiff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c\nindex 790000fb26..fb16ec0176 100644\n--- a/t/helper/test-read-midx.c\n+++ b/t/helper/test-read-midx.c\n@@ -13,13 +13,16 @@\n \n static struct multi_pack_index *setup_midx(const char *object_dir)\n {\n+\tstruct odb_source_files *files;\n \tstruct odb_source *source;\n \tsetup_git_directory(the_repository);\n \tsource = odb_find_source(the_repository->objects, object_dir);\n \tif (!source)\n \t\tsource = odb_add_to_alternates_memory(the_repository->objects,\n \t\t\t\t\t\t      object_dir);\n-\treturn load_multi_pack_index(source);\n+\tfiles = odb_source_files_downcast(source);\n+\n+\treturn load_multi_pack_index(files->packed);\n }\n \n static int read_midx_file(const char *object_dir, const char *checksum,\n@@ -70,7 +73,7 @@ static int read_midx_file(const char *object_dir, const char *checksum,\n \tfor (i = 0; i < m->num_packs; i++)\n \t\tprintf(\"%s\\n\", m->pack_names[i]);\n \n-\tprintf(\"object-dir: %s\\n\", m->source->path);\n+\tprintf(\"object-dir: %s\\n\", m->source->base.path);\n \n \tif (show_objects) {\n \t\tstruct object_id oid;\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545039","messageId":"20260609-pks-odb-source-packed-v2-17-839089132c8b@pks.im","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im","subject":"[PATCH v2 17/17] odb/source-packed: drop pointer to \"files\" parent source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T08:51:10Z","receivedAt":"2026-06-09T08:51:51Z","isPatch":true,"body":"Over the last commits we have turned the packfile store into a proper\nobject database source that can be used as a standalone backend. As\nsuch, it is no longer necessary to have it coupled to the \"files\" parent\nsource.\n\nRemove the pointer to the owning \"files\" source so that the \"packed\"\nsource can be used as a standalone entity.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 27 +++++++++++++--------------\n odb/source-packed.h |  7 ++++---\n packfile.c          |  2 +-\n 4 files changed, 19 insertions(+), 19 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex fa2e18e71b..3bc6419dd7 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tCALLOC_ARRAY(files, 1);\n \todb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);\n \tfiles->loose = odb_source_loose_new(odb, path, local);\n-\tfiles->packed = odb_source_packed_new(files);\n+\tfiles->packed = odb_source_packed_new(odb, path, local);\n \n \tfiles->base.free = odb_source_files_free;\n \tfiles->base.close = odb_source_files_close;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex d513b3efc3..42c28fba0e 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -585,7 +585,7 @@ static void report_pack_garbage(struct string_list *list)\n }\n \n struct prepare_pack_data {\n-\tstruct odb_source *source;\n+\tstruct odb_source_packed *source;\n \tstruct string_list *garbage;\n };\n \n@@ -593,15 +593,14 @@ static void prepare_pack(const char *full_name, size_t full_name_len,\n \t\t\t const char *file_name, void *_data)\n {\n \tstruct prepare_pack_data *data = (struct prepare_pack_data *)_data;\n-\tstruct odb_source_files *files = odb_source_files_downcast(data->source);\n \tsize_t base_len = full_name_len;\n \n \tif (strip_suffix_mem(full_name, &base_len, \".idx\") &&\n-\t    !(files->packed->midx &&\n-\t      midx_contains_pack(files->packed->midx, file_name))) {\n+\t    !(data->source->midx &&\n+\t      midx_contains_pack(data->source->midx, file_name))) {\n \t\tchar *trimmed_path = xstrndup(full_name, full_name_len);\n-\t\tpackfile_store_load_pack(files->packed,\n-\t\t\t\t\t trimmed_path, data->source->local);\n+\t\tpackfile_store_load_pack(data->source,\n+\t\t\t\t\t trimmed_path, data->source->base.local);\n \t\tfree(trimmed_path);\n \t}\n \n@@ -626,7 +625,7 @@ static void prepare_pack(const char *full_name, size_t full_name_len,\n \t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n }\n \n-static void prepare_packed_git_one(struct odb_source *source)\n+static void prepare_packed_git_one(struct odb_source_packed *source)\n {\n \tstruct string_list garbage = STRING_LIST_INIT_DUP;\n \tstruct prepare_pack_data data = {\n@@ -634,7 +633,7 @@ static void prepare_packed_git_one(struct odb_source *source)\n \t\t.garbage = &garbage,\n \t};\n \n-\tfor_each_file_in_pack_dir(source->path, prepare_pack, &data);\n+\tfor_each_file_in_pack_dir(source->base.path, prepare_pack, &data);\n \n \treport_pack_garbage(data.garbage);\n \tstring_list_clear(data.garbage, 0);\n@@ -675,7 +674,7 @@ void odb_source_packed_prepare(struct odb_source_packed *source)\n \t\treturn;\n \n \tprepare_multi_pack_index_one(source);\n-\tprepare_packed_git_one(&source->files->base);\n+\tprepare_packed_git_one(source);\n \n \tsort_packs(&source->packs.head, sort_pack);\n \tfor (struct packfile_list_entry *e = source->packs.head; e; e = e->next)\n@@ -733,14 +732,14 @@ static void odb_source_packed_free(struct odb_source *source)\n \tfree(packed);\n }\n \n-struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n+struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n+\t\t\t\t\t\tconst char *path,\n+\t\t\t\t\t\tbool local)\n {\n \tstruct odb_source_packed *packed;\n \n \tCALLOC_ARRAY(packed, 1);\n-\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n-\t\t\tparent->base.path, parent->base.local);\n-\tpacked->files = parent;\n+\todb_source_init(&packed->base, odb, ODB_SOURCE_PACKED, path, local);\n \tstrmap_init(&packed->packs_by_path);\n \n \tpacked->base.free = odb_source_packed_free;\n@@ -758,7 +757,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.read_alternates = odb_source_packed_read_alternates;\n \tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n-\tif (!is_absolute_path(parent->base.path))\n+\tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n \n \treturn packed;\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 9d4796261a..88994098c1 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -10,7 +10,6 @@\n  */\n struct odb_source_packed {\n \tstruct odb_source base;\n-\tstruct odb_source_files *files;\n \n \t/*\n \t * The list of packfiles in the order in which they have been most\n@@ -66,9 +65,11 @@ struct odb_source_packed {\n \n /*\n  * Allocate and initialize a new empty packfile store for the given object\n- * database source.\n+ * database.\n  */\n-struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent);\n+struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n+\t\t\t\t\t\tconst char *path,\n+\t\t\t\t\t\tbool local);\n \n /*\n  * Cast the given object database source to the packed backend. This will cause\ndiff --git a/packfile.c b/packfile.c\nindex a577275d4f..59cee7925d 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -801,7 +801,7 @@ struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \n \tp = strmap_get(&store->packs_by_path, key.buf);\n \tif (!p) {\n-\t\tp = add_packed_git(store->files->base.odb->repo, idx_path,\n+\t\tp = add_packed_git(store->base.odb->repo, idx_path,\n \t\t\t\t   strlen(idx_path), local);\n \t\tif (p)\n \t\t\tpackfile_store_add_pack(store, p);\n\n-- \n2.54.0.1136.gdb2ca164c4.dirty\n\n"},{"id":"545060","messageId":"aifvTmsPzaVdC8Yq@pks.im","threadId":"65752","inReplyTo":"aifAVpxanV31KUpC@pks.im","subject":"Re: [PATCH 04/16] odb/source-packed: start converting to a proper `struct odb_source`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-09T10:47:42Z","receivedAt":"2026-06-09T10:47:49Z","isPatch":true,"body":"On Tue, Jun 09, 2026 at 09:27:25AM +0200, Patrick Steinhardt wrote:\n> On Mon, Jun 08, 2026 at 08:29:04AM -0700, Karthik Nayak wrote:\n> > Patrick Steinhardt <ps@pks.im> writes:\n> > > diff --git a/odb/source-packed.c b/odb/source-packed.c\n> > > index 12e785be48..f81a990cbd 100644\n> > > --- a/odb/source-packed.c\n> > > +++ b/odb/source-packed.c\n> > > +\tCALLOC_ARRAY(packed, 1);\n> > > +\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n> > > +\t\t\tparent->base.path, parent->base.local);\n> > > +\tpacked->files = parent;\n> > > +\tstrmap_init(&packed->packs_by_path);\n> > > +\n> > > +\tpacked->base.free = odb_source_packed_free;\n> > > +\n> > > +\tif (!is_absolute_path(parent->base.path))\n> > > +\t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n> > > +\n> > \n> > Tangent: seems like no one sets the 'name' field within\n> > `chdir_notify_register()`. It is meant for tracing purposes, but if no\n> > one is using it, we might as well remove it...? Perhaps #leftoverbits\n> \n> There are some callers: `chdir_notify_reparent()` calls\n> `chdir_notify_register()` with a name, and the reference backends call\n> that function with names.\n> \n> Ultimately though I think that this infrastructure is somewhat misguided\n> overall: we use this to update relative paths after chdir(3p), but if we\n> stored absolute paths in the first place then we wouldn't have to care\n> about the paths changing at all.\n> \n> I plan to revisit this infra for the object database going forward: we\n> expose and use `struct odb_source::path` in various other subsystems,\n> including user-facing ones. This is inherently wrong though, as there\n> may be sources that don't even have an on-disk path. So there's a need\n> to drop that field and make it an internal implementation detail of the\n> source's backend. And once we've done that, we can just as well start to\n> store absolute paths.\n> \n> For the reference backends we can already do that refactoring now-ish.\n> I'll send a patch series later today.\n\nWell, as ever so often I went down the rabbit hole and discovered way\nmore than I wanted:\n\n  - We never free the `struct repository::refs_private` field.\n\n  - We create the refdb multiple times in case we have \"onbranch\"\n    conditionals and never free them.\n\n  - The chicken-and-egg problem we have with \"onbranch\" and refdb\n    creation is awkward.\n\nSo I'm now sitting at 11 patches to fix all of those bugs, and I'll also\nhave to make changes to \"setup.c\" to fix them. So I'll first have to\nwait for ps/setup-centralize-odb-creation to land.\n\nPatrick\n"},{"id":"545688","messageId":"ajG3jqV4QHUMw-EN@denethor","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-1-839089132c8b@pks.im","subject":"Re: [PATCH v2 01/17] packfile: rename `struct packfile_store` to `odb_source_packed`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-16T21:01:07Z","receivedAt":"2026-06-16T21:01:12Z","isPatch":true,"body":"On 26/06/09 10:50AM, Patrick Steinhardt wrote:\n> Not too long ago, we have introduced the packfile store in b7983adb51\n> (packfile: introduce a new `struct packfile_store`, 2025-09-23). This\n> struct is responsible for managing all of our access to packfiles and is\n> used as one of the two sources of objects for the \"files\" source.\n> \n> Back when I introduced this structure I didn't have the clear vision yet\n> that it will eventually also turn into a proper object database source,\n> and how exactly that infrastructure will look like. Now though it's\n> becoming increasingly clear that it does make sense to treat it just the\n> same as any of our other ODB sources.\n\nWe already have `struct odb_source_loose` which is used in `struct\nodb_source_files` which is a proper ODB source. Since it seems accessing\npacked objects is moving in the same direction, the renaming here makes\nsense to me.\n\n> The consequence is that the naming is now a bit out-of-date: it's just\n> another source and will be turned into a proper `struct odb_source` over\n> the next couple of commits, but it's not named accordingly.\n> \n> Rename the structure to `odb_source_packed` to align it with this goal\n> and to bring it in line with the other sources we already have.\n\nThe rest of this patch is just trivial renames and looks good to me.\n\n-Justin\n"},{"id":"545689","messageId":"ajG69JZHx_u2mt7q@denethor","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-4-839089132c8b@pks.im","subject":"Re: [PATCH v2 04/17] odb/source-packed: store pointer to \"files\" instead of generic source","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-16T21:14:02Z","receivedAt":"2026-06-16T21:14:08Z","isPatch":true,"body":"On 26/06/09 10:50AM, Patrick Steinhardt wrote:\n> The `struct odb_source_packed` holds a pointer to its owning parent\n> source. The way that Git is currently structured, this parent is always\n> the \"files\" source. In subsequent commits we're going to detangle that\n> so that the \"packed\" source doesn't have any owning parent source at\n> all, which makes it usable as a completely standalone source.\n\nOut of curiousity, `struct odb_source_loose` also stores a similar to\npointer to its owning parent. Is the plan to also eventually do the same\nthere?\n\n> Detangling this mess is somewhat intricate though, and is made even more\n> intricate because it's not always clear which kind of source one is\n> holding at a specific point in time -- either the parent \"files\" source,\n> or the child \"packed\" source.\n> \n> Make this relationship more explicit by storing a pointer to the \"files\"\n> source instead of storing a pointer to a generic `struct odb_source`.\n> This will help make subsequent steps a bit clearer.\n> \n> Note that this is a temporary step, only. At the end of this series\n> we will have dropped the parent pointer completely.\n\nOk, so IIUC the eventual goal is to get rid of the pointer entirely, but\nfor now we are just making its concrete type explicit without having to\ndowncast. It's not immediately obvious to me how this step gets us\ncloser to that goal, but that may become more obvious in the next\npatches. :)\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  odb/source-files.c  |  2 +-\n>  odb/source-packed.c |  4 ++--\n>  odb/source-packed.h |  4 ++--\n>  packfile.c          | 12 ++++++------\n>  4 files changed, 11 insertions(+), 11 deletions(-)\n> \n> diff --git a/odb/source-files.c b/odb/source-files.c\n> index 191562f316..e04525fb08 100644\n> --- a/odb/source-files.c\n> +++ b/odb/source-files.c\n> @@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n>  \tCALLOC_ARRAY(files, 1);\n>  \todb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);\n>  \tfiles->loose = odb_source_loose_new(odb, path, local);\n> -\tfiles->packed = odb_source_packed_new(&files->base);\n> +\tfiles->packed = odb_source_packed_new(files);\n>  \n>  \tfiles->base.free = odb_source_files_free;\n>  \tfiles->base.close = odb_source_files_close;\n> diff --git a/odb/source-packed.c b/odb/source-packed.c\n> index 1e94b47ea0..12e785be48 100644\n> --- a/odb/source-packed.c\n> +++ b/odb/source-packed.c\n> @@ -1,11 +1,11 @@\n>  #include \"git-compat-util.h\"\n>  #include \"odb/source-packed.h\"\n>  \n> -struct odb_source_packed *odb_source_packed_new(struct odb_source *source)\n> +struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n>  {\n>  \tstruct odb_source_packed *store;\n>  \tCALLOC_ARRAY(store, 1);\n> -\tstore->source = source;\n> +\tstore->files = parent;\n>  \tstrmap_init(&store->packs_by_path);\n>  \treturn store;\n>  }\n> diff --git a/odb/source-packed.h b/odb/source-packed.h\n> index 327be4ad65..3c2d229a17 100644\n> --- a/odb/source-packed.h\n> +++ b/odb/source-packed.h\n> @@ -9,7 +9,7 @@\n>   * A store that manages packfiles for a given object database.\n>   */\n>  struct odb_source_packed {\n> -\tstruct odb_source *source;\n> +\tstruct odb_source_files *files;\n\nThe rest of this patch updates the callsites accordingly and looks\ncorrect.\n\n-Justin\n"},{"id":"545691","messageId":"ajG_gC73ch923EkI@denethor","threadId":"65752","inReplyTo":"ajG69JZHx_u2mt7q@denethor","subject":"Re: [PATCH v2 04/17] odb/source-packed: store pointer to \"files\" instead of generic source","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-16T21:28:59Z","receivedAt":"2026-06-16T21:29:01Z","isPatch":true,"body":"On 26/06/16 04:14PM, Justin Tobler wrote:\n> On 26/06/09 10:50AM, Patrick Steinhardt wrote:\n> > The `struct odb_source_packed` holds a pointer to its owning parent\n> > source. The way that Git is currently structured, this parent is always\n> > the \"files\" source. In subsequent commits we're going to detangle that\n> > so that the \"packed\" source doesn't have any owning parent source at\n> > all, which makes it usable as a completely standalone source.\n> \n> Out of curiousity, `struct odb_source_loose` also stores a similar to\n> pointer to its owning parent. Is the plan to also eventually do the same\n> there?\n\nOk, I think I got myself mixed up. IIUC the `struct odb_source` pointer\ncurrently stored in `struct odb_source_loose` is not to the parent\nsource, but to its \"base\". And the eventual goal here is to move towards\nthat same direction which makes sense.\n\n-Justin\n"},{"id":"545694","messageId":"ajG-rP7cRjZa3zJE@denethor","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-5-839089132c8b@pks.im","subject":"Re: [PATCH v2 05/17] odb/source-packed: start converting to a proper `struct odb_source`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-16T21:36:59Z","receivedAt":"2026-06-16T21:37:01Z","isPatch":true,"body":"On 26/06/09 10:50AM, Patrick Steinhardt wrote:\n> Start converting `struct odb_source_packed` into a proper pluggable\n> `struct odb_source` by embedding the base struct and assigning it the\n> new `ODB_SOURCE_PACKED` type. Furthermore, wire up lifecycle management\n> of this source by implementing the `free` callback and taking ownership\n> of the chdir notifications.\n> \n> Note that the packed source is not yet functional as a standalone `struct\n> odb_source`, as it's missing all of the callback implementations. These\n> will be wired up in subsequent commits.\n\nOk.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n[snip]\n>  struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n>  {\n> -\tstruct odb_source_packed *store;\n> -\tCALLOC_ARRAY(store, 1);\n> -\tstore->files = parent;\n> -\tstrmap_init(&store->packs_by_path);\n> -\treturn store;\n> +\tstruct odb_source_packed *packed;\n> +\n> +\tCALLOC_ARRAY(packed, 1);\n> +\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n> +\t\t\tparent->base.path, parent->base.local);\n> +\tpacked->files = parent;\n> +\tstrmap_init(&packed->packs_by_path);\n> +\n> +\tpacked->base.free = odb_source_packed_free;\n> +\n> +\tif (!is_absolute_path(parent->base.path))\n> +\t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n\nOut of curiousity, did the packfile store previously not have to worry\nabout changing directories?\n\n> +\n> +\treturn packed;\n>  }\n> diff --git a/odb/source-packed.h b/odb/source-packed.h\n> index 3c2d229a17..68e64cabab 100644\n> --- a/odb/source-packed.h\n> +++ b/odb/source-packed.h\n> @@ -9,6 +9,7 @@\n>   * A store that manages packfiles for a given object database.\n>   */\n>  struct odb_source_packed {\n> +\tstruct odb_source base;\n>  \tstruct odb_source_files *files;\n\nAt first I got confused as to why we were adding back a `struct\nodb_source` pointer, but this is for the \"base\" not the parent ODB\nsource. We need to embed a `struct odb_source` here to make this a\nproper ODB source.\n\nLooking good.\n\n-Justin\n"},{"id":"545696","messageId":"ajHDm_pG3Ckca4mp@denethor","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-7-839089132c8b@pks.im","subject":"Re: [PATCH v2 07/17] odb/source-packed: wire up `reprepare()` callback","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-16T21:53:38Z","receivedAt":"2026-06-16T21:53:43Z","isPatch":true,"body":"On 26/06/09 10:51AM, Patrick Steinhardt wrote:\n> Move the logic to prepare and reprepare the \"packed\" source into\n> \"odb/source-packed.c\" and wire it up as the `reprepare()` callback.\n> \n> Note that \"preparing\" a source is not yet generic. Eventually, it would\n> probably make sense to turn the existing `reprepare()` callback into a\n> `prepare()` callback with an optional flag to force re-preparing. But\n> this step will be handled in a separate patch series.\n\nI do find the prepare vs reprepare semantics a bit confusing. The\nmentioned change above would be nice to see in the future. :)\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n[snip]\n>  static void odb_source_packed_reparent(const char *name UNUSED,\n>  \t\t\t\t       const char *old_cwd,\n>  \t\t\t\t       const char *new_cwd,\n> @@ -58,6 +214,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n>  \n>  \tpacked->base.free = odb_source_packed_free;\n>  \tpacked->base.close = odb_source_packed_close;\n> +\tpacked->base.reprepare = odb_source_packed_reprepare;\n\nWe moved `packfile_store_reprepare()` logic into \"odb/source-packed.c\"\nand setup the callback making it accessible via `odb_source_reprepare()`\ninstead. Makes sense.\n\n>  \tif (!is_absolute_path(parent->base.path))\n>  \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n> diff --git a/odb/source-packed.h b/odb/source-packed.h\n> index 68e64cabab..9d4796261a 100644\n> --- a/odb/source-packed.h\n> +++ b/odb/source-packed.h\n> @@ -81,4 +81,13 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so\n>  \treturn container_of(source, struct odb_source_packed, base);\n>  }\n>  \n> +/*\n> + * Prepare the source by loading packfiles and multi-pack indices for\n> + * all alternates. This becomes a no-op if the source is already prepared.\n> + *\n> + * It shouldn't typically be necessary to call this function directly, as\n> + * functions that access the source know to prepare it.\n> + */\n> +void odb_source_packed_prepare(struct odb_source_packed *source);\n\nThe logic for `packfile_store_prepare()` is also moved into\n\"odb/source-packed.c\" and made accessible this function since there\nisn't a matching callback.\n\nThe other changes are mostly 1:1 moves of associated logic and callsite\nupdates. Looks good.\n\n-Justin\n"},{"id":"545697","messageId":"ajHIjrQJvWtbCrZp@denethor","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-11-839089132c8b@pks.im","subject":"Re: [PATCH v2 11/17] odb/source-packed: wire up `for_each_object()` callback","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-16T22:10:28Z","receivedAt":"2026-06-16T22:10:33Z","isPatch":true,"body":"On 26/06/09 10:51AM, Patrick Steinhardt wrote:\n> Move `packfile_store_for_each_object()` and its associated helpers from\n> \"packfile.c\" into \"odb/source-packed.c\" and wire it up as the\n> `for_each_object()` callback of the \"packed\" source.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n[snip]\n> @@ -291,6 +548,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n>  \tpacked->base.reprepare = odb_source_packed_reprepare;\n>  \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n>  \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n> +\tpacked->base.for_each_object = odb_source_packed_for_each_object;\n\nWiring up the callback. Looks good.\n\n>  \tif (!is_absolute_path(parent->base.path))\n>  \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n> diff --git a/packfile.c b/packfile.c\n> index 42c84397eb..b8d6054c16 100644\n> --- a/packfile.c\n> +++ b/packfile.c\n> @@ -1362,8 +1362,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> -static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n> -\t\t\t\t\t     uint32_t *maybe_index_pos, struct object_info *oi)\n> +int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n> +\t\t\t\t      uint32_t *maybe_index_pos, struct object_info *oi)\n\nLooks like we are also exposing `packed_object_info_with_index_pos()`\nnow. Not sure yet if this is also intended to be temporary like\n`find_pack_entry()` in a previous patch though though.\n\n-Justin\n"},{"id":"545708","messageId":"ajHKmbimHJnFV2IE@denethor","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-16-839089132c8b@pks.im","subject":"Re: [PATCH v2 16/17] midx: refactor interfaces to work on \"packed\" source","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-16T22:37:53Z","receivedAt":"2026-06-16T22:37:57Z","isPatch":true,"body":"On 26/06/09 10:51AM, Patrick Steinhardt wrote:\n> Our interfaces used to interact with MIDXs all work on top of the\n> generic `struct odb_source`. This doesn't make much sense though: a MIDX\n> is strictly tied to the \"packed\" source, so passing in a generic source\n> gives the false sense that it may also work with a different type of\n> source.\n\nA `struct odb_source_packed` now acts as the source specific to all\npackfiles in the repository. Being that MIDXs only work with packfiles,\nit also makes sense to me to use the appropriate concrete source here.\n\n> Fix this conceptual weirdness and instead require the caller to pass in\n> a \"packed\" source explicitly. This also makes the next commit easier to\n> implement, where we drop the pointer to the \"files\" source in the\n> \"packed\" source.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/multi-pack-index.c |  29 +++++------\n>  builtin/pack-objects.c     |   3 +-\n>  builtin/repack.c           |   8 ++-\n>  midx-write.c               |  34 ++++++-------\n>  midx.c                     | 118 ++++++++++++++++++++++-----------------------\n>  midx.h                     |  30 ++++++------\n>  odb/source-packed.c        |  12 ++---\n>  pack-bitmap.c              |   8 +--\n>  pack-revindex.c            |   6 +--\n>  repack-geometry.c          |   3 +-\n>  repack-midx.c              |   9 ++--\n>  repack.c                   |   6 +--\n>  t/helper/test-read-midx.c  |   7 ++-\n>  13 files changed, 144 insertions(+), 129 deletions(-)\n> \n> diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\n> index 00ffb36394..6e73c85cde 100644\n> --- a/builtin/multi-pack-index.c\n> +++ b/builtin/multi-pack-index.c\n> @@ -10,6 +10,7 @@\n>  #include \"trace2.h\"\n>  #include \"odb.h\"\n>  #include \"odb/source.h\"\n> +#include \"odb/source-files.h\"\n>  #include \"replace-object.h\"\n>  #include \"repository.h\"\n>  \n> @@ -85,12 +86,12 @@ static int parse_object_dir(const struct option *opt, const char *arg,\n>  \treturn 0;\n>  }\n>  \n> -static struct odb_source *handle_object_dir_option(struct repository *repo)\n> +static struct odb_source_files *handle_object_dir_option(struct repository *repo)\n>  {\n>  \tstruct odb_source *source = odb_find_source(repo->objects, opts.object_dir);\n>  \tif (!source)\n>  \t\tsource = odb_add_to_alternates_memory(repo->objects, opts.object_dir);\n> -\treturn source;\n> +\treturn odb_source_files_downcast(source);\n\nNow that we are passing around concrete ODB sources to many of these\ninterfaces, callers may have to handle downcasting explicitly. Since now\nthe downcasting may happen a bit ealier than previously in some cases, I\nwondered if this would alter any error flows. I don't think this would\nbe the case though because ultimately we only have the \"files\" source\ncurrently anyways.\n\nThe updates to the function signatures and call sites accordingly in\nthis patch all look good to me.\n\n-Justin\n"},{"id":"545709","messageId":"ajHRkrsvNtcBMFx2@denethor","threadId":"65752","inReplyTo":"20260609-pks-odb-source-packed-v2-17-839089132c8b@pks.im","subject":"Re: [PATCH v2 17/17] odb/source-packed: drop pointer to \"files\" parent source","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-16T22:51:43Z","receivedAt":"2026-06-16T22:51:47Z","isPatch":true,"body":"On 26/06/09 10:51AM, Patrick Steinhardt wrote:\n> Over the last commits we have turned the packfile store into a proper\n> object database source that can be used as a standalone backend. As\n> such, it is no longer necessary to have it coupled to the \"files\" parent\n> source.\n> \n> Remove the pointer to the owning \"files\" source so that the \"packed\"\n> source can be used as a standalone entity.\n\nMakes sense.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n[snip]\n> @@ -626,7 +625,7 @@ static void prepare_pack(const char *full_name, size_t full_name_len,\n>  \t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n>  }\n>  \n> -static void prepare_packed_git_one(struct odb_source *source)\n> +static void prepare_packed_git_one(struct odb_source_packed *source)\n\nAt first I was a bit confused to see this change here, but IIUC\npreviously this function was passed the \"base\" source of the \"files\"\nODB. Now that we have a proper \"packed\" source we can use that directly\ninstead.\n\n[snip]\n> -struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n> +struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n> +\t\t\t\t\t\tconst char *path,\n> +\t\t\t\t\t\tbool local)\n\nSince we no longer depend on the parent source the function signature is\nupdated accordingly. This also matches the \"loose\" ODB source.\n\n[snip]\n> diff --git a/odb/source-packed.h b/odb/source-packed.h\n> index 9d4796261a..88994098c1 100644\n> --- a/odb/source-packed.h\n> +++ b/odb/source-packed.h\n> @@ -10,7 +10,6 @@\n>   */\n>  struct odb_source_packed {\n>  \tstruct odb_source base;\n> -\tstruct odb_source_files *files;\n\nNice. :)\n\n-Justin\n"},{"id":"545711","messageId":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im","subject":"[PATCH v3 00/17] odb: make packed object source a proper `struct odb_source`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:43Z","receivedAt":"2026-06-17T06:39:59Z","isPatch":true,"body":"Hi,\n\nthis patch series converts the \"packed\" source into a proper `struct\nodb_source`. It's thus the equivalent to [1], which did the same thing\nfor the \"loose\" source.\n\nThis series here is unfortunately a bit bigger, mostly because I'm also\nrenaming `struct packfile_store` to `struct odb_source_packed`. Back\nwhen I introduced the packfile store I didn't yet have the full vision\nof how the final layout will look like, so I didn't have the foresight\nyet to call it `struct odb_source_packed`. But now that the layout has\nmaterialized I think it's sensible to adjust its naming to match all the\nother sources that we have.\n\nAlso: I don't have anything else in the pipeline anymore that moves\naround large pieces of our code in the vicinity of the object database.\nSo after this series got merged, subsequent changes should be of a more\nincremental nature.\n\nThis series is built on top of 9ac3f193c0 (The 11th batch, 2026-06-02)\nwith ps/odb-source-loose at ef4778bcba (odb/source-loose: drop pointer\nto the \"files\" source, 2026-06-01) merged into it.\n\nChanges in v3:\n  - Add some more explanations to the commit messages.\n  - Link to v2: https://patch.msgid.link/20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im\n\nChanges in v2:\n  - Split out `struct packfile_list` into a separate code unit to fix a\n    cyclic dependency between \"packfile.h\" and \"odb/souurce-packed.h\".\n  - Fix an extraneous newline.\n  - Link to v1: https://patch.msgid.link/20260604-pks-odb-source-packed-v1-0-2e7ab31b4b5c@pks.im\n\nThanks!\n\nPatrick\n\n[1]: <20260521-b4-pks-odb-source-loose-v1-0-6553b399be2d@pks.im>\n\n---\nPatrick Steinhardt (17):\n      packfile: rename `struct packfile_store` to `odb_source_packed`\n      packfile: split out packfile list logic\n      packfile: move packed source into \"odb/\" subsystem\n      odb/source-packed: store pointer to \"files\" instead of generic source\n      odb/source-packed: start converting to a proper `struct odb_source`\n      odb/source-packed: wire up `close()` callback\n      odb/source-packed: wire up `reprepare()` callback\n      packfile: use higher-level interface to implement `has_object_pack()`\n      odb/source-packed: wire up `read_object_info()` callback\n      odb/source-packed: wire up `read_object_stream()` callback\n      odb/source-packed: wire up `for_each_object()` callback\n      odb/source-packed: wire up `count_objects()` callback\n      odb/source-packed: wire up `find_abbrev_len()` callback\n      odb/source-packed: wire up `freshen_object()` callback\n      odb/source-packed: stub out remaining functions\n      midx: refactor interfaces to work on \"packed\" source\n      odb/source-packed: drop pointer to \"files\" parent source\n\n Makefile                   |   2 +\n builtin/cat-file.c         |   4 +-\n builtin/grep.c             |   2 +-\n builtin/multi-pack-index.c |  29 +-\n builtin/pack-objects.c     |   7 +-\n builtin/repack.c           |   8 +-\n commit-graph.c             |   4 +-\n meson.build                |   2 +\n midx-write.c               |  34 +-\n midx.c                     | 118 +++----\n midx.h                     |  30 +-\n odb/source-files.c         |  20 +-\n odb/source-files.h         |   4 +-\n odb/source-packed.c        | 764 +++++++++++++++++++++++++++++++++++++++++++\n odb/source-packed.h        |  94 ++++++\n odb/source.h               |   3 +\n pack-bitmap.c              |   8 +-\n pack-revindex.c            |   6 +-\n packfile-list.c            |  86 +++++\n packfile-list.h            |  28 ++\n packfile.c                 | 784 +--------------------------------------------\n packfile.h                 | 180 +----------\n repack-geometry.c          |   3 +-\n repack-midx.c              |   9 +-\n repack.c                   |   6 +-\n t/helper/test-read-midx.c  |   7 +-\n 26 files changed, 1163 insertions(+), 1079 deletions(-)\n\nRange-diff versus v2:\n\n 1:  9f917fe0cd =  1:  9c4cb4106c packfile: rename `struct packfile_store` to `odb_source_packed`\n 2:  eb6740c34c =  2:  198b137c84 packfile: split out packfile list logic\n 3:  5c363883f1 =  3:  fa91889861 packfile: move packed source into \"odb/\" subsystem\n 4:  caa19f59bb !  4:  5f0da3a334 odb/source-packed: store pointer to \"files\" instead of generic source\n    @@ Commit message\n     \n         Make this relationship more explicit by storing a pointer to the \"files\"\n         source instead of storing a pointer to a generic `struct odb_source`.\n    -    This will help make subsequent steps a bit clearer.\n    +    This will help make subsequent steps a bit clearer by making it more\n    +    obvious whether we're using the generic \"base\" source or the owning\n    +    \"files\" source.\n     \n         Note that this is a temporary step, only. At the end of this series\n    -    we will have dropped the parent pointer completely.\n    +    we will have dropped the \"files\" pointer completely.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n 5:  8eb3cb17a1 !  5:  c9b1e1da26 odb/source-packed: start converting to a proper `struct odb_source`\n    @@ Commit message\n         odb_source`, as it's missing all of the callback implementations. These\n         will be wired up in subsequent commits.\n     \n    +    Further note that we're also registering a `chdir_notify` callback to\n    +    reparent our path. This wasn't previously necessary (and still isn't at\n    +    this point in time) because all paths are taken from the owning \"files\"\n    +    source, and that source already handles the reparenting for us. But a\n    +    subsequent commit will change that so that we're using the path of the\n    +    \"packed\" source, and once that happens we'll need it to be updated when\n    +    changing the working directory.\n    +\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## odb/source-files.c ##\n 6:  0b81cf21c8 =  6:  350cc18b4c odb/source-packed: wire up `close()` callback\n 7:  3024f236eb =  7:  b42a633423 odb/source-packed: wire up `reprepare()` callback\n 8:  e3553cae61 =  8:  2eda992ff9 packfile: use higher-level interface to implement `has_object_pack()`\n 9:  0d4d888c66 =  9:  81201eca00 odb/source-packed: wire up `read_object_info()` callback\n10:  0a81bc811f = 10:  2f6ea598a5 odb/source-packed: wire up `read_object_stream()` callback\n11:  fc4b2477b5 = 11:  154ded231a odb/source-packed: wire up `for_each_object()` callback\n12:  58c7c4c6c1 = 12:  a35c357358 odb/source-packed: wire up `count_objects()` callback\n13:  b81e053716 = 13:  134c51a0cd odb/source-packed: wire up `find_abbrev_len()` callback\n14:  424b9ee1b2 = 14:  542694cff2 odb/source-packed: wire up `freshen_object()` callback\n15:  f91f6355fe = 15:  fa222d0cda odb/source-packed: stub out remaining functions\n16:  877721f2ea = 16:  cee3806dc7 midx: refactor interfaces to work on \"packed\" source\n17:  5779eab630 = 17:  200fbcf4bb odb/source-packed: drop pointer to \"files\" parent source\n\n---\nbase-commit: 06d49cec508464ced5d42541890ce5d749542a61\nchange-id: 20260602-pks-odb-source-packed-3826c352f059\n\n"},{"id":"545712","messageId":"20260617-pks-odb-source-packed-v3-1-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 01/17] packfile: rename `struct packfile_store` to `odb_source_packed`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:44Z","receivedAt":"2026-06-17T06:40:00Z","isPatch":true,"body":"Not too long ago, we have introduced the packfile store in b7983adb51\n(packfile: introduce a new `struct packfile_store`, 2025-09-23). This\nstruct is responsible for managing all of our access to packfiles and is\nused as one of the two sources of objects for the \"files\" source.\n\nBack when I introduced this structure I didn't have the clear vision yet\nthat it will eventually also turn into a proper object database source,\nand how exactly that infrastructure will look like. Now though it's\nbecoming increasingly clear that it does make sense to treat it just the\nsame as any of our other ODB sources.\n\nThe consequence is that the naming is now a bit out-of-date: it's just\nanother source and will be turned into a proper `struct odb_source` over\nthe next couple of commits, but it's not named accordingly.\n\nRename the structure to `odb_source_packed` to align it with this goal\nand to bring it in line with the other sources we already have.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.h |  4 ++--\n packfile.c         | 56 +++++++++++++++++++++++++++---------------------------\n packfile.h         | 32 +++++++++++++++----------------\n 3 files changed, 46 insertions(+), 46 deletions(-)\n\ndiff --git a/odb/source-files.h b/odb/source-files.h\nindex 23a3b4e04b..d7ac3c1c81 100644\n--- a/odb/source-files.h\n+++ b/odb/source-files.h\n@@ -4,7 +4,7 @@\n #include \"odb/source.h\"\n \n struct odb_source_loose;\n-struct packfile_store;\n+struct odb_source_packed;\n \n /*\n  * The files object database source uses a combination of loose objects and\n@@ -13,7 +13,7 @@ struct packfile_store;\n struct odb_source_files {\n \tstruct odb_source base;\n \tstruct odb_source_loose *loose;\n-\tstruct packfile_store *packed;\n+\tstruct odb_source_packed *packed;\n };\n \n /* Allocate and initialize a new object source. */\ndiff --git a/packfile.c b/packfile.c\nindex 89366abfe3..a2d768d0ae 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -859,7 +859,7 @@ struct packed_git *add_packed_git(struct repository *r, const char *path,\n \treturn p;\n }\n \n-void packfile_store_add_pack(struct packfile_store *store,\n+void packfile_store_add_pack(struct odb_source_packed *store,\n \t\t\t     struct packed_git *pack)\n {\n \tif (pack->pack_fd != -1)\n@@ -869,7 +869,7 @@ void packfile_store_add_pack(struct packfile_store *store,\n \tstrmap_put(&store->packs_by_path, pack->pack_name, pack);\n }\n \n-struct packed_git *packfile_store_load_pack(struct packfile_store *store,\n+struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \t\t\t\t\t    const char *idx_path, int local)\n {\n \tstruct strbuf key = STRBUF_INIT;\n@@ -1068,7 +1068,7 @@ static int sort_pack(const struct packfile_list_entry *a,\n \treturn -1;\n }\n \n-void packfile_store_prepare(struct packfile_store *store)\n+void packfile_store_prepare(struct odb_source_packed *store)\n {\n \tif (store->initialized)\n \t\treturn;\n@@ -1084,13 +1084,13 @@ void packfile_store_prepare(struct packfile_store *store)\n \tstore->initialized = true;\n }\n \n-void packfile_store_reprepare(struct packfile_store *store)\n+void packfile_store_reprepare(struct odb_source_packed *store)\n {\n \tstore->initialized = false;\n \tpackfile_store_prepare(store);\n }\n \n-struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store)\n+struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store)\n {\n \tpackfile_store_prepare(store);\n \n@@ -1103,7 +1103,7 @@ struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *stor\n \treturn store->packs.head;\n }\n \n-int packfile_store_count_objects(struct packfile_store *store,\n+int packfile_store_count_objects(struct odb_source_packed *store,\n \t\t\t\t enum odb_count_objects_flags flags UNUSED,\n \t\t\t\t unsigned long *out)\n {\n@@ -2160,7 +2160,7 @@ static int fill_pack_entry(const struct object_id *oid,\n \treturn 1;\n }\n \n-static int find_pack_entry(struct packfile_store *store,\n+static int find_pack_entry(struct odb_source_packed *store,\n \t\t\t   const struct object_id *oid,\n \t\t\t   struct pack_entry *e)\n {\n@@ -2183,7 +2183,7 @@ static int find_pack_entry(struct packfile_store *store,\n \treturn 0;\n }\n \n-int packfile_store_freshen_object(struct packfile_store *store,\n+int packfile_store_freshen_object(struct odb_source_packed *store,\n \t\t\t\t  const struct object_id *oid)\n {\n \tstruct pack_entry e;\n@@ -2199,7 +2199,7 @@ int packfile_store_freshen_object(struct packfile_store *store,\n \treturn 1;\n }\n \n-int packfile_store_read_object_info(struct packfile_store *store,\n+int packfile_store_read_object_info(struct odb_source_packed *store,\n \t\t\t\t    const struct object_id *oid,\n \t\t\t\t    struct object_info *oi,\n \t\t\t\t    enum object_info_flags flags)\n@@ -2234,7 +2234,7 @@ int packfile_store_read_object_info(struct packfile_store *store,\n \treturn 0;\n }\n \n-static void maybe_invalidate_kept_pack_cache(struct packfile_store *store,\n+static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t     unsigned flags)\n {\n \tif (!store->kept_cache.packs)\n@@ -2245,7 +2245,7 @@ static void maybe_invalidate_kept_pack_cache(struct packfile_store *store,\n \tstore->kept_cache.flags = 0;\n }\n \n-struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store,\n+struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t\t       unsigned flags)\n {\n \tmaybe_invalidate_kept_pack_cache(store, flags);\n@@ -2365,8 +2365,8 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-struct packfile_store_for_each_object_wrapper_data {\n-\tstruct packfile_store *store;\n+struct odb_source_packed_for_each_object_wrapper_data {\n+\tstruct odb_source_packed *store;\n \tconst struct object_info *request;\n \todb_for_each_object_cb cb;\n \tvoid *cb_data;\n@@ -2377,7 +2377,7 @@ static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n \t\t\t\t\t\t  uint32_t index_pos,\n \t\t\t\t\t\t  void *cb_data)\n {\n-\tstruct packfile_store_for_each_object_wrapper_data *data = cb_data;\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data = cb_data;\n \n \tif (data->request) {\n \t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n@@ -2411,10 +2411,10 @@ static int match_hash(unsigned len, const unsigned char *a, const unsigned char\n }\n \n static int for_each_prefixed_object_in_midx(\n-\tstruct packfile_store *store,\n+\tstruct odb_source_packed *store,\n \tstruct multi_pack_index *m,\n \tconst struct odb_for_each_object_options *opts,\n-\tstruct packfile_store_for_each_object_wrapper_data *data)\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n {\n \tint ret;\n \n@@ -2470,10 +2470,10 @@ static int for_each_prefixed_object_in_midx(\n }\n \n static int for_each_prefixed_object_in_pack(\n-\tstruct packfile_store *store,\n+\tstruct odb_source_packed *store,\n \tstruct packed_git *p,\n \tconst struct odb_for_each_object_options *opts,\n-\tstruct packfile_store_for_each_object_wrapper_data *data)\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n {\n \tuint32_t num, i, first = 0;\n \tint len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ?\n@@ -2519,9 +2519,9 @@ static int for_each_prefixed_object_in_pack(\n }\n \n static int packfile_store_for_each_prefixed_object(\n-\tstruct packfile_store *store,\n+\tstruct odb_source_packed *store,\n \tconst struct odb_for_each_object_options *opts,\n-\tstruct packfile_store_for_each_object_wrapper_data *data)\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n {\n \tstruct packfile_list_entry *e;\n \tstruct multi_pack_index *m;\n@@ -2566,13 +2566,13 @@ static int packfile_store_for_each_prefixed_object(\n \treturn ret;\n }\n \n-int packfile_store_for_each_object(struct packfile_store *store,\n+int packfile_store_for_each_object(struct odb_source_packed *store,\n \t\t\t\t   const struct object_info *request,\n \t\t\t\t   odb_for_each_object_cb cb,\n \t\t\t\t   void *cb_data,\n \t\t\t\t   const struct odb_for_each_object_options *opts)\n {\n-\tstruct packfile_store_for_each_object_wrapper_data data = {\n+\tstruct odb_source_packed_for_each_object_wrapper_data data = {\n \t\t.store = store,\n \t\t.request = request,\n \t\t.cb = cb,\n@@ -2707,7 +2707,7 @@ static void find_abbrev_len_for_pack(struct packed_git *p,\n \t*out = len;\n }\n \n-int packfile_store_find_abbrev_len(struct packfile_store *store,\n+int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \t\t\t\t   const struct object_id *oid,\n \t\t\t\t   unsigned min_len,\n \t\t\t\t   unsigned *out)\n@@ -2832,16 +2832,16 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-struct packfile_store *packfile_store_new(struct odb_source *source)\n+struct odb_source_packed *packfile_store_new(struct odb_source *source)\n {\n-\tstruct packfile_store *store;\n+\tstruct odb_source_packed *store;\n \tCALLOC_ARRAY(store, 1);\n \tstore->source = source;\n \tstrmap_init(&store->packs_by_path);\n \treturn store;\n }\n \n-void packfile_store_free(struct packfile_store *store)\n+void packfile_store_free(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n \t\tfree(e->pack);\n@@ -2851,7 +2851,7 @@ void packfile_store_free(struct packfile_store *store)\n \tfree(store);\n }\n \n-void packfile_store_close(struct packfile_store *store)\n+void packfile_store_close(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {\n \t\tif (e->pack->do_not_close)\n@@ -2988,7 +2988,7 @@ int packfile_read_object_stream(struct odb_read_stream **out,\n }\n \n int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct packfile_store *store,\n+\t\t\t\t      struct odb_source_packed *store,\n \t\t\t\t      const struct object_id *oid)\n {\n \tstruct pack_entry e;\ndiff --git a/packfile.h b/packfile.h\nindex 49d6bdecf6..9cec15bc50 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -79,7 +79,7 @@ struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n /*\n  * A store that manages packfiles for a given object database.\n  */\n-struct packfile_store {\n+struct odb_source_packed {\n \tstruct odb_source *source;\n \n \t/*\n@@ -138,19 +138,19 @@ struct packfile_store {\n  * Allocate and initialize a new empty packfile store for the given object\n  * database source.\n  */\n-struct packfile_store *packfile_store_new(struct odb_source *source);\n+struct odb_source_packed *packfile_store_new(struct odb_source *source);\n \n /*\n  * Free the packfile store and all its associated state. All packfiles\n  * tracked by the store will be closed.\n  */\n-void packfile_store_free(struct packfile_store *store);\n+void packfile_store_free(struct odb_source_packed *store);\n \n /*\n  * Close all packfiles associated with this store. The packfiles won't be\n  * free'd, so they can be re-opened at a later point in time.\n  */\n-void packfile_store_close(struct packfile_store *store);\n+void packfile_store_close(struct odb_source_packed *store);\n \n /*\n  * Prepare the packfile store by loading packfiles and multi-pack indices for\n@@ -159,7 +159,7 @@ void packfile_store_close(struct packfile_store *store);\n  * It shouldn't typically be necessary to call this function directly, as\n  * functions that access the store know to prepare it.\n  */\n-void packfile_store_prepare(struct packfile_store *store);\n+void packfile_store_prepare(struct odb_source_packed *store);\n \n /*\n  * Clear the packfile caches and try to look up any new packfiles that have\n@@ -167,20 +167,20 @@ void packfile_store_prepare(struct packfile_store *store);\n  *\n  * This function must be called under the `odb_read_lock()`.\n  */\n-void packfile_store_reprepare(struct packfile_store *store);\n+void packfile_store_reprepare(struct odb_source_packed *store);\n \n /*\n  * Add the pack to the store so that contained objects become accessible via\n  * the store. This moves ownership into the store.\n  */\n-void packfile_store_add_pack(struct packfile_store *store,\n+void packfile_store_add_pack(struct odb_source_packed *store,\n \t\t\t     struct packed_git *pack);\n \n /*\n  * Get all packs managed by the given store, including packfiles that are\n  * referenced by multi-pack indices.\n  */\n-struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store);\n+struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store);\n \n struct repo_for_each_pack_data {\n \tstruct odb_source *source;\n@@ -239,7 +239,7 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *\n \t     repo_for_each_pack_data_next(&eack_pack_data))\n \n int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct packfile_store *store,\n+\t\t\t\t      struct odb_source_packed *store,\n \t\t\t\t      const struct object_id *oid);\n \n /*\n@@ -248,7 +248,7 @@ int packfile_store_read_object_stream(struct odb_read_stream **out,\n  * not found, 0 if it was and read successfully, and a negative error code in\n  * case the object was corrupted.\n  */\n-int packfile_store_read_object_info(struct packfile_store *store,\n+int packfile_store_read_object_info(struct odb_source_packed *store,\n \t\t\t\t    const struct object_id *oid,\n \t\t\t\t    struct object_info *oi,\n \t\t\t\t    enum object_info_flags flags);\n@@ -258,10 +258,10 @@ int packfile_store_read_object_info(struct packfile_store *store,\n  * either the newly opened packfile or the preexisting packfile. Returns a\n  * `NULL` pointer in case the packfile could not be opened.\n  */\n-struct packed_git *packfile_store_load_pack(struct packfile_store *store,\n+struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \t\t\t\t\t    const char *idx_path, int local);\n \n-int packfile_store_freshen_object(struct packfile_store *store,\n+int packfile_store_freshen_object(struct odb_source_packed *store,\n \t\t\t\t  const struct object_id *oid);\n \n enum kept_pack_type {\n@@ -276,7 +276,7 @@ enum kept_pack_type {\n  *\n  * Return 0 on success, a negative error code otherwise.\n  */\n-int packfile_store_count_objects(struct packfile_store *store,\n+int packfile_store_count_objects(struct odb_source_packed *store,\n \t\t\t\t enum odb_count_objects_flags flags,\n \t\t\t\t unsigned long *out);\n \n@@ -285,7 +285,7 @@ int packfile_store_count_objects(struct packfile_store *store,\n  * combination of `kept_pack_type` flags. The cache is computed on demand and\n  * will be recomputed whenever the flags change.\n  */\n-struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store,\n+struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t\t       unsigned flags);\n \n struct pack_window {\n@@ -365,13 +365,13 @@ int for_each_object_in_pack(struct packed_git *p,\n  *\n  * The flags parameter is a combination of `odb_for_each_object_flags`.\n  */\n-int packfile_store_for_each_object(struct packfile_store *store,\n+int packfile_store_for_each_object(struct odb_source_packed *store,\n \t\t\t\t   const struct object_info *request,\n \t\t\t\t   odb_for_each_object_cb cb,\n \t\t\t\t   void *cb_data,\n \t\t\t\t   const struct odb_for_each_object_options *opts);\n \n-int packfile_store_find_abbrev_len(struct packfile_store *store,\n+int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \t\t\t\t   const struct object_id *oid,\n \t\t\t\t   unsigned min_len,\n \t\t\t\t   unsigned *out);\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545713","messageId":"20260617-pks-odb-source-packed-v3-2-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 02/17] packfile: split out packfile list logic","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:45Z","receivedAt":"2026-06-17T06:40:05Z","isPatch":true,"body":"In the next commit we're about to introduce the \"packed\" object database\nsource. This source will embed a packfile list, and consequently we'll\nhave to include \"packfile.h\" to make the struct definition available.\nThis will unfortunately lead to a cyclic dependency that we cannot\nresolve with a forward declaration.\n\nSplit out the code that relates to the packfile list into a separate\ncompilation unit so that both \"packfile.h\" and \"odb/source-packed.h\" can\ninclude it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Makefile        |  1 +\n meson.build     |  1 +\n packfile-list.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n packfile-list.h | 28 +++++++++++++++++++\n packfile.c      | 83 -------------------------------------------------------\n packfile.h      | 23 +--------------\n 6 files changed, 117 insertions(+), 105 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 0976a69b4c..ed1731548e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1233,6 +1233,7 @@ LIB_OBJS += pack-refs.o\n LIB_OBJS += pack-revindex.o\n LIB_OBJS += pack-write.o\n LIB_OBJS += packfile.o\n+LIB_OBJS += packfile-list.o\n LIB_OBJS += pager.o\n LIB_OBJS += parallel-checkout.o\n LIB_OBJS += parse.o\ndiff --git a/meson.build b/meson.build\nindex 3247697f74..12913fc948 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -421,6 +421,7 @@ libgit_sources = [\n   'pack-revindex.c',\n   'pack-write.c',\n   'packfile.c',\n+  'packfile-list.c',\n   'pager.c',\n   'parallel-checkout.c',\n   'parse.c',\ndiff --git a/packfile-list.c b/packfile-list.c\nnew file mode 100644\nindex 0000000000..01fb913abf\n--- /dev/null\n+++ b/packfile-list.c\n@@ -0,0 +1,86 @@\n+#include \"git-compat-util.h\"\n+#include \"packfile.h\"\n+#include \"packfile-list.h\"\n+\n+void packfile_list_clear(struct packfile_list *list)\n+{\n+\tstruct packfile_list_entry *e, *next;\n+\n+\tfor (e = list->head; e; e = next) {\n+\t\tnext = e->next;\n+\t\tfree(e);\n+\t}\n+\n+\tlist->head = list->tail = NULL;\n+}\n+\n+static struct packfile_list_entry *packfile_list_remove_internal(struct packfile_list *list,\n+\t\t\t\t\t\t\t\t struct packed_git *pack)\n+{\n+\tstruct packfile_list_entry *e, *prev;\n+\n+\tfor (e = list->head, prev = NULL; e; prev = e, e = e->next) {\n+\t\tif (e->pack != pack)\n+\t\t\tcontinue;\n+\n+\t\tif (prev)\n+\t\t\tprev->next = e->next;\n+\t\tif (list->head == e)\n+\t\t\tlist->head = e->next;\n+\t\tif (list->tail == e)\n+\t\t\tlist->tail = prev;\n+\n+\t\treturn e;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+void packfile_list_remove(struct packfile_list *list, struct packed_git *pack)\n+{\n+\tfree(packfile_list_remove_internal(list, pack));\n+}\n+\n+void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack)\n+{\n+\tstruct packfile_list_entry *entry;\n+\n+\tentry = packfile_list_remove_internal(list, pack);\n+\tif (!entry) {\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t\tentry->pack = pack;\n+\t}\n+\tentry->next = list->head;\n+\n+\tlist->head = entry;\n+\tif (!list->tail)\n+\t\tlist->tail = entry;\n+}\n+\n+void packfile_list_append(struct packfile_list *list, struct packed_git *pack)\n+{\n+\tstruct packfile_list_entry *entry;\n+\n+\tentry = packfile_list_remove_internal(list, pack);\n+\tif (!entry) {\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t\tentry->pack = pack;\n+\t}\n+\tentry->next = NULL;\n+\n+\tif (list->tail) {\n+\t\tlist->tail->next = entry;\n+\t\tlist->tail = entry;\n+\t} else {\n+\t\tlist->head = list->tail = entry;\n+\t}\n+}\n+\n+struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n+\t\t\t\t\t  const struct object_id *oid)\n+{\n+\tfor (; packs; packs = packs->next)\n+\t\tif (find_pack_entry_one(oid, packs->pack))\n+\t\t\treturn packs->pack;\n+\treturn NULL;\n+}\ndiff --git a/packfile-list.h b/packfile-list.h\nnew file mode 100644\nindex 0000000000..1b05e2aa36\n--- /dev/null\n+++ b/packfile-list.h\n@@ -0,0 +1,28 @@\n+#ifndef PACKFILE_LIST_H\n+#define PACKFILE_LIST_H\n+\n+struct object_id;\n+\n+struct packfile_list {\n+\tstruct packfile_list_entry *head, *tail;\n+};\n+\n+struct packfile_list_entry {\n+\tstruct packfile_list_entry *next;\n+\tstruct packed_git *pack;\n+};\n+\n+void packfile_list_clear(struct packfile_list *list);\n+void packfile_list_remove(struct packfile_list *list, struct packed_git *pack);\n+void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack);\n+void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n+\n+/*\n+ * Find the pack within the \"packs\" list whose index contains the object\n+ * \"oid\". For general object lookups, you probably don't want this; use\n+ * find_pack_entry() instead.\n+ */\n+struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n+\t\t\t\t\t  const struct object_id *oid);\n+\n+#endif\ndiff --git a/packfile.c b/packfile.c\nindex a2d768d0ae..27ea4a8436 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -48,89 +48,6 @@ static size_t pack_mapped;\n #define SZ_FMT PRIuMAX\n static inline uintmax_t sz_fmt(size_t s) { return s; }\n \n-void packfile_list_clear(struct packfile_list *list)\n-{\n-\tstruct packfile_list_entry *e, *next;\n-\n-\tfor (e = list->head; e; e = next) {\n-\t\tnext = e->next;\n-\t\tfree(e);\n-\t}\n-\n-\tlist->head = list->tail = NULL;\n-}\n-\n-static struct packfile_list_entry *packfile_list_remove_internal(struct packfile_list *list,\n-\t\t\t\t\t\t\t\t struct packed_git *pack)\n-{\n-\tstruct packfile_list_entry *e, *prev;\n-\n-\tfor (e = list->head, prev = NULL; e; prev = e, e = e->next) {\n-\t\tif (e->pack != pack)\n-\t\t\tcontinue;\n-\n-\t\tif (prev)\n-\t\t\tprev->next = e->next;\n-\t\tif (list->head == e)\n-\t\t\tlist->head = e->next;\n-\t\tif (list->tail == e)\n-\t\t\tlist->tail = prev;\n-\n-\t\treturn e;\n-\t}\n-\n-\treturn NULL;\n-}\n-\n-void packfile_list_remove(struct packfile_list *list, struct packed_git *pack)\n-{\n-\tfree(packfile_list_remove_internal(list, pack));\n-}\n-\n-void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack)\n-{\n-\tstruct packfile_list_entry *entry;\n-\n-\tentry = packfile_list_remove_internal(list, pack);\n-\tif (!entry) {\n-\t\tentry = xmalloc(sizeof(*entry));\n-\t\tentry->pack = pack;\n-\t}\n-\tentry->next = list->head;\n-\n-\tlist->head = entry;\n-\tif (!list->tail)\n-\t\tlist->tail = entry;\n-}\n-\n-void packfile_list_append(struct packfile_list *list, struct packed_git *pack)\n-{\n-\tstruct packfile_list_entry *entry;\n-\n-\tentry = packfile_list_remove_internal(list, pack);\n-\tif (!entry) {\n-\t\tentry = xmalloc(sizeof(*entry));\n-\t\tentry->pack = pack;\n-\t}\n-\tentry->next = NULL;\n-\n-\tif (list->tail) {\n-\t\tlist->tail->next = entry;\n-\t\tlist->tail = entry;\n-\t} else {\n-\t\tlist->head = list->tail = entry;\n-\t}\n-}\n-\n-struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n-\t\t\t\t\t  const struct object_id *oid)\n-{\n-\tfor (; packs; packs = packs->next)\n-\t\tif (find_pack_entry_one(oid, packs->pack))\n-\t\t\treturn packs->pack;\n-\treturn NULL;\n-}\n-\n void pack_report(struct repository *repo)\n {\n \tfprintf(stderr,\ndiff --git a/packfile.h b/packfile.h\nindex 9cec15bc50..4e3d701a3a 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -6,6 +6,7 @@\n #include \"odb.h\"\n #include \"odb/source-files.h\"\n #include \"oidset.h\"\n+#include \"packfile-list.h\"\n #include \"repository.h\"\n #include \"strmap.h\"\n \n@@ -54,28 +55,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-struct packfile_list {\n-\tstruct packfile_list_entry *head, *tail;\n-};\n-\n-struct packfile_list_entry {\n-\tstruct packfile_list_entry *next;\n-\tstruct packed_git *pack;\n-};\n-\n-void packfile_list_clear(struct packfile_list *list);\n-void packfile_list_remove(struct packfile_list *list, struct packed_git *pack);\n-void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack);\n-void packfile_list_append(struct packfile_list *list, struct packed_git *pack);\n-\n-/*\n- * Find the pack within the \"packs\" list whose index contains the object\n- * \"oid\". For general object lookups, you probably don't want this; use\n- * find_pack_entry() instead.\n- */\n-struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs,\n-\t\t\t\t\t  const struct object_id *oid);\n-\n /*\n  * A store that manages packfiles for a given object database.\n  */\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545714","messageId":"20260617-pks-odb-source-packed-v3-3-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 03/17] packfile: move packed source into \"odb/\" subsystem","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:46Z","receivedAt":"2026-06-17T06:40:07Z","isPatch":true,"body":"In subsequent patches we'll be turning `struct odb_source_packed` into a\nproper `struct odb_source`. As a first step towards this goal, move its\nstruct out of \"packfile.{c,h}\" and into \"odb/source-packed.{c,h}\".\n\nThis detaches the implementation of the packfile object source from the\ngeneric packfile code, following the same convention already used by the\n\"files\" and \"in-memory\" sources.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Makefile            |  1 +\n meson.build         |  1 +\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 11 ++++++++\n odb/source-packed.h | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.c          |  9 -------\n packfile.h          | 66 +-----------------------------------------------\n 7 files changed, 87 insertions(+), 75 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex ed1731548e..113fa45993 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1218,6 +1218,7 @@ LIB_OBJS += odb/source.o\n LIB_OBJS += odb/source-files.o\n LIB_OBJS += odb/source-inmemory.o\n LIB_OBJS += odb/source-loose.o\n+LIB_OBJS += odb/source-packed.o\n LIB_OBJS += odb/streaming.o\n LIB_OBJS += odb/transaction.o\n LIB_OBJS += oid-array.o\ndiff --git a/meson.build b/meson.build\nindex 12913fc948..ca235801cf 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -406,6 +406,7 @@ libgit_sources = [\n   'odb/source-files.c',\n   'odb/source-inmemory.c',\n   'odb/source-loose.c',\n+  'odb/source-packed.c',\n   'odb/streaming.c',\n   'odb/transaction.c',\n   'oid-array.c',\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 5bdd042922..191562f316 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tCALLOC_ARRAY(files, 1);\n \todb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);\n \tfiles->loose = odb_source_loose_new(odb, path, local);\n-\tfiles->packed = packfile_store_new(&files->base);\n+\tfiles->packed = odb_source_packed_new(&files->base);\n \n \tfiles->base.free = odb_source_files_free;\n \tfiles->base.close = odb_source_files_close;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nnew file mode 100644\nindex 0000000000..1e94b47ea0\n--- /dev/null\n+++ b/odb/source-packed.c\n@@ -0,0 +1,11 @@\n+#include \"git-compat-util.h\"\n+#include \"odb/source-packed.h\"\n+\n+struct odb_source_packed *odb_source_packed_new(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *store;\n+\tCALLOC_ARRAY(store, 1);\n+\tstore->source = source;\n+\tstrmap_init(&store->packs_by_path);\n+\treturn store;\n+}\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nnew file mode 100644\nindex 0000000000..327be4ad65\n--- /dev/null\n+++ b/odb/source-packed.h\n@@ -0,0 +1,72 @@\n+#ifndef ODB_SOURCE_PACKED_H\n+#define ODB_SOURCE_PACKED_H\n+\n+#include \"odb/source.h\"\n+#include \"packfile-list.h\"\n+#include \"strmap.h\"\n+\n+/*\n+ * A store that manages packfiles for a given object database.\n+ */\n+struct odb_source_packed {\n+\tstruct odb_source *source;\n+\n+\t/*\n+\t * The list of packfiles in the order in which they have been most\n+\t * recently used.\n+\t */\n+\tstruct packfile_list packs;\n+\n+\t/*\n+\t * Cache of packfiles which are marked as \"kept\", either because there\n+\t * is an on-disk \".keep\" file or because they are marked as \"kept\" in\n+\t * memory.\n+\t *\n+\t * Should not be accessed directly, but via\n+\t * `packfile_store_get_kept_pack_cache()`. The list of packs gets\n+\t * invalidated when the stored flags and the flags passed to\n+\t * `packfile_store_get_kept_pack_cache()` mismatch.\n+\t */\n+\tstruct {\n+\t\tstruct packed_git **packs;\n+\t\tunsigned flags;\n+\t} kept_cache;\n+\n+\t/* The multi-pack index that belongs to this specific packfile store. */\n+\tstruct multi_pack_index *midx;\n+\n+\t/*\n+\t * A map of packfile names to packed_git structs for tracking which\n+\t * packs have been loaded already.\n+\t */\n+\tstruct strmap packs_by_path;\n+\n+\t/*\n+\t * Whether packfiles have already been populated with this store's\n+\t * packs.\n+\t */\n+\tbool initialized;\n+\n+\t/*\n+\t * Usually, packfiles will be reordered to the front of the `packs`\n+\t * list whenever an object is looked up via them. This has the effect\n+\t * that packs that contain a lot of accessed objects will be located\n+\t * towards the front.\n+\t *\n+\t * This is usually desireable, but there are exceptions. One exception\n+\t * is when the looking up multiple objects in a loop for each packfile.\n+\t * In that case, we may easily end up with an infinite loop as the\n+\t * packfiles get reordered to the front repeatedly.\n+\t *\n+\t * Setting this field to `true` thus disables these reorderings.\n+\t */\n+\tbool skip_mru_updates;\n+};\n+\n+/*\n+ * Allocate and initialize a new empty packfile store for the given object\n+ * database source.\n+ */\n+struct odb_source_packed *odb_source_packed_new(struct odb_source *source);\n+\n+#endif\ndiff --git a/packfile.c b/packfile.c\nindex 27ea4a8436..99be5789ef 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2749,15 +2749,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-struct odb_source_packed *packfile_store_new(struct odb_source *source)\n-{\n-\tstruct odb_source_packed *store;\n-\tCALLOC_ARRAY(store, 1);\n-\tstore->source = source;\n-\tstrmap_init(&store->packs_by_path);\n-\treturn store;\n-}\n-\n void packfile_store_free(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\ndiff --git a/packfile.h b/packfile.h\nindex 4e3d701a3a..2d0bb7adbe 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -5,10 +5,10 @@\n #include \"object.h\"\n #include \"odb.h\"\n #include \"odb/source-files.h\"\n+#include \"odb/source-packed.h\"\n #include \"oidset.h\"\n #include \"packfile-list.h\"\n #include \"repository.h\"\n-#include \"strmap.h\"\n \n /* in odb.h */\n struct object_info;\n@@ -55,70 +55,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-/*\n- * A store that manages packfiles for a given object database.\n- */\n-struct odb_source_packed {\n-\tstruct odb_source *source;\n-\n-\t/*\n-\t * The list of packfiles in the order in which they have been most\n-\t * recently used.\n-\t */\n-\tstruct packfile_list packs;\n-\n-\t/*\n-\t * Cache of packfiles which are marked as \"kept\", either because there\n-\t * is an on-disk \".keep\" file or because they are marked as \"kept\" in\n-\t * memory.\n-\t *\n-\t * Should not be accessed directly, but via\n-\t * `packfile_store_get_kept_pack_cache()`. The list of packs gets\n-\t * invalidated when the stored flags and the flags passed to\n-\t * `packfile_store_get_kept_pack_cache()` mismatch.\n-\t */\n-\tstruct {\n-\t\tstruct packed_git **packs;\n-\t\tunsigned flags;\n-\t} kept_cache;\n-\n-\t/* The multi-pack index that belongs to this specific packfile store. */\n-\tstruct multi_pack_index *midx;\n-\n-\t/*\n-\t * A map of packfile names to packed_git structs for tracking which\n-\t * packs have been loaded already.\n-\t */\n-\tstruct strmap packs_by_path;\n-\n-\t/*\n-\t * Whether packfiles have already been populated with this store's\n-\t * packs.\n-\t */\n-\tbool initialized;\n-\n-\t/*\n-\t * Usually, packfiles will be reordered to the front of the `packs`\n-\t * list whenever an object is looked up via them. This has the effect\n-\t * that packs that contain a lot of accessed objects will be located\n-\t * towards the front.\n-\t *\n-\t * This is usually desireable, but there are exceptions. One exception\n-\t * is when the looking up multiple objects in a loop for each packfile.\n-\t * In that case, we may easily end up with an infinite loop as the\n-\t * packfiles get reordered to the front repeatedly.\n-\t *\n-\t * Setting this field to `true` thus disables these reorderings.\n-\t */\n-\tbool skip_mru_updates;\n-};\n-\n-/*\n- * Allocate and initialize a new empty packfile store for the given object\n- * database source.\n- */\n-struct odb_source_packed *packfile_store_new(struct odb_source *source);\n-\n /*\n  * Free the packfile store and all its associated state. All packfiles\n  * tracked by the store will be closed.\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545715","messageId":"20260617-pks-odb-source-packed-v3-4-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 04/17] odb/source-packed: store pointer to \"files\" instead of generic source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:47Z","receivedAt":"2026-06-17T06:40:08Z","isPatch":true,"body":"The `struct odb_source_packed` holds a pointer to its owning parent\nsource. The way that Git is currently structured, this parent is always\nthe \"files\" source. In subsequent commits we're going to detangle that\nso that the \"packed\" source doesn't have any owning parent source at\nall, which makes it usable as a completely standalone source.\n\nDetangling this mess is somewhat intricate though, and is made even more\nintricate because it's not always clear which kind of source one is\nholding at a specific point in time -- either the parent \"files\" source,\nor the child \"packed\" source.\n\nMake this relationship more explicit by storing a pointer to the \"files\"\nsource instead of storing a pointer to a generic `struct odb_source`.\nThis will help make subsequent steps a bit clearer by making it more\nobvious whether we're using the generic \"base\" source or the owning\n\"files\" source.\n\nNote that this is a temporary step, only. At the end of this series\nwe will have dropped the \"files\" pointer completely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c |  4 ++--\n odb/source-packed.h |  4 ++--\n packfile.c          | 12 ++++++------\n 4 files changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 191562f316..e04525fb08 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tCALLOC_ARRAY(files, 1);\n \todb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);\n \tfiles->loose = odb_source_loose_new(odb, path, local);\n-\tfiles->packed = odb_source_packed_new(&files->base);\n+\tfiles->packed = odb_source_packed_new(files);\n \n \tfiles->base.free = odb_source_files_free;\n \tfiles->base.close = odb_source_files_close;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 1e94b47ea0..12e785be48 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,11 +1,11 @@\n #include \"git-compat-util.h\"\n #include \"odb/source-packed.h\"\n \n-struct odb_source_packed *odb_source_packed_new(struct odb_source *source)\n+struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n {\n \tstruct odb_source_packed *store;\n \tCALLOC_ARRAY(store, 1);\n-\tstore->source = source;\n+\tstore->files = parent;\n \tstrmap_init(&store->packs_by_path);\n \treturn store;\n }\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 327be4ad65..3c2d229a17 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -9,7 +9,7 @@\n  * A store that manages packfiles for a given object database.\n  */\n struct odb_source_packed {\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *files;\n \n \t/*\n \t * The list of packfiles in the order in which they have been most\n@@ -67,6 +67,6 @@ struct odb_source_packed {\n  * Allocate and initialize a new empty packfile store for the given object\n  * database source.\n  */\n-struct odb_source_packed *odb_source_packed_new(struct odb_source *source);\n+struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent);\n \n #endif\ndiff --git a/packfile.c b/packfile.c\nindex 99be5789ef..862a24ad49 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -802,7 +802,7 @@ struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \n \tp = strmap_get(&store->packs_by_path, key.buf);\n \tif (!p) {\n-\t\tp = add_packed_git(store->source->odb->repo, idx_path,\n+\t\tp = add_packed_git(store->files->base.odb->repo, idx_path,\n \t\t\t\t   strlen(idx_path), local);\n \t\tif (p)\n \t\t\tpackfile_store_add_pack(store, p);\n@@ -990,8 +990,8 @@ void packfile_store_prepare(struct odb_source_packed *store)\n \tif (store->initialized)\n \t\treturn;\n \n-\tprepare_multi_pack_index_one(store->source);\n-\tprepare_packed_git_one(store->source);\n+\tprepare_multi_pack_index_one(&store->files->base);\n+\tprepare_packed_git_one(&store->files->base);\n \n \tsort_packs(&store->packs.head, sort_pack);\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n@@ -1029,7 +1029,7 @@ int packfile_store_count_objects(struct odb_source_packed *store,\n \tunsigned long count = 0;\n \tint ret;\n \n-\tm = get_multi_pack_index(store->source);\n+\tm = get_multi_pack_index(&store->files->base);\n \tif (m)\n \t\tcount += m->num_objects + m->num_objects_in_base;\n \n@@ -2450,7 +2450,7 @@ static int packfile_store_for_each_prefixed_object(\n \n \tstore->skip_mru_updates = true;\n \n-\tm = get_multi_pack_index(store->source);\n+\tm = get_multi_pack_index(&store->files->base);\n \tif (m) {\n \t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n \t\tif (ret)\n@@ -2632,7 +2632,7 @@ int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \tstruct packfile_list_entry *e;\n \tstruct multi_pack_index *m;\n \n-\tm = get_multi_pack_index(store->source);\n+\tm = get_multi_pack_index(&store->files->base);\n \tif (m)\n \t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n \n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545716","messageId":"20260617-pks-odb-source-packed-v3-5-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 05/17] odb/source-packed: start converting to a proper `struct odb_source`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:48Z","receivedAt":"2026-06-17T06:40:11Z","isPatch":true,"body":"Start converting `struct odb_source_packed` into a proper pluggable\n`struct odb_source` by embedding the base struct and assigning it the\nnew `ODB_SOURCE_PACKED` type. Furthermore, wire up lifecycle management\nof this source by implementing the `free` callback and taking ownership\nof the chdir notifications.\n\nNote that the packed source is not yet functional as a standalone `struct\nodb_source`, as it's missing all of the callback implementations. These\nwill be wired up in subsequent commits.\n\nFurther note that we're also registering a `chdir_notify` callback to\nreparent our path. This wasn't previously necessary (and still isn't at\nthis point in time) because all paths are taken from the owning \"files\"\nsource, and that source already handles the reparenting for us. But a\nsubsequent commit will change that so that we're using the path of the\n\"packed\" source, and once that happens we'll need it to be updated when\nchanging the working directory.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 49 ++++++++++++++++++++++++++++++++++++++++++++-----\n odb/source-packed.h | 12 ++++++++++++\n odb/source.h        |  3 +++\n packfile.c          | 10 ----------\n packfile.h          |  6 ------\n 6 files changed, 60 insertions(+), 22 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex e04525fb08..3608808e7c 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -29,7 +29,7 @@ static void odb_source_files_free(struct odb_source *source)\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \tchdir_notify_unregister(NULL, odb_source_files_reparent, files);\n \todb_source_free(&files->loose->base);\n-\tpackfile_store_free(files->packed);\n+\todb_source_free(&files->packed->base);\n \todb_source_release(&files->base);\n \tfree(files);\n }\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 12e785be48..f81a990cbd 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,11 +1,50 @@\n #include \"git-compat-util.h\"\n+#include \"abspath.h\"\n+#include \"chdir-notify.h\"\n #include \"odb/source-packed.h\"\n+#include \"packfile.h\"\n+\n+static void odb_source_packed_reparent(const char *name UNUSED,\n+\t\t\t\t       const char *old_cwd,\n+\t\t\t\t       const char *new_cwd,\n+\t\t\t\t       void *cb_data)\n+{\n+\tstruct odb_source_packed *packed = cb_data;\n+\tchar *path = reparent_relative_path(old_cwd, new_cwd,\n+\t\t\t\t\t    packed->base.path);\n+\tfree(packed->base.path);\n+\tpacked->base.path = path;\n+}\n+\n+static void odb_source_packed_free(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\n+\tchdir_notify_unregister(NULL, odb_source_packed_reparent, packed);\n+\n+\tfor (struct packfile_list_entry *e = packed->packs.head; e; e = e->next)\n+\t\tfree(e->pack);\n+\tpackfile_list_clear(&packed->packs);\n+\n+\tstrmap_clear(&packed->packs_by_path, 0);\n+\todb_source_release(&packed->base);\n+\tfree(packed);\n+}\n \n struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n {\n-\tstruct odb_source_packed *store;\n-\tCALLOC_ARRAY(store, 1);\n-\tstore->files = parent;\n-\tstrmap_init(&store->packs_by_path);\n-\treturn store;\n+\tstruct odb_source_packed *packed;\n+\n+\tCALLOC_ARRAY(packed, 1);\n+\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n+\t\t\tparent->base.path, parent->base.local);\n+\tpacked->files = parent;\n+\tstrmap_init(&packed->packs_by_path);\n+\n+\tpacked->base.free = odb_source_packed_free;\n+\n+\tif (!is_absolute_path(parent->base.path))\n+\t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n+\n+\treturn packed;\n }\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 3c2d229a17..68e64cabab 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -9,6 +9,7 @@\n  * A store that manages packfiles for a given object database.\n  */\n struct odb_source_packed {\n+\tstruct odb_source base;\n \tstruct odb_source_files *files;\n \n \t/*\n@@ -69,4 +70,15 @@ struct odb_source_packed {\n  */\n struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent);\n \n+/*\n+ * Cast the given object database source to the packed backend. This will cause\n+ * a BUG in case the source doesn't use this backend.\n+ */\n+static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_source *source)\n+{\n+\tif (source->type != ODB_SOURCE_PACKED)\n+\t\tBUG(\"trying to downcast source of type '%d' to packed\", source->type);\n+\treturn container_of(source, struct odb_source_packed, base);\n+}\n+\n #endif\ndiff --git a/odb/source.h b/odb/source.h\nindex 8bcb67787e..6865e1f71a 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -17,6 +17,9 @@ enum odb_source_type {\n \t/* The \"loose\" backend that uses loose objects, only. */\n \tODB_SOURCE_LOOSE,\n \n+\t/* The \"packed\" backend that uses packfiles. */\n+\tODB_SOURCE_PACKED,\n+\n \t/* The \"in-memory\" backend that stores objects in memory. */\n \tODB_SOURCE_INMEMORY,\n };\ndiff --git a/packfile.c b/packfile.c\nindex 862a24ad49..6d492216de 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2749,16 +2749,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-void packfile_store_free(struct odb_source_packed *store)\n-{\n-\tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n-\t\tfree(e->pack);\n-\tpackfile_list_clear(&store->packs);\n-\n-\tstrmap_clear(&store->packs_by_path, 0);\n-\tfree(store);\n-}\n-\n void packfile_store_close(struct odb_source_packed *store)\n {\n \tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {\ndiff --git a/packfile.h b/packfile.h\nindex 2d0bb7adbe..e8bc9349f8 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -55,12 +55,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-/*\n- * Free the packfile store and all its associated state. All packfiles\n- * tracked by the store will be closed.\n- */\n-void packfile_store_free(struct odb_source_packed *store);\n-\n /*\n  * Close all packfiles associated with this store. The packfiles won't be\n  * free'd, so they can be re-opened at a later point in time.\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545717","messageId":"20260617-pks-odb-source-packed-v3-6-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 06/17] odb/source-packed: wire up `close()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:49Z","receivedAt":"2026-06-17T06:40:12Z","isPatch":true,"body":"Wire up a new `close()` callback for the packed source and call it from\nthe \"files\" source via the generic `odb_source_close()` interface.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 16 ++++++++++++++++\n packfile.c          | 12 ------------\n packfile.h          |  6 ------\n 4 files changed, 17 insertions(+), 19 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 3608808e7c..9b0fa9ccdc 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -38,7 +38,7 @@ static void odb_source_files_close(struct odb_source *source)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \todb_source_close(&files->loose->base);\n-\tpackfile_store_close(files->packed);\n+\todb_source_close(&files->packed->base);\n }\n \n static void odb_source_files_reprepare(struct odb_source *source)\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex f81a990cbd..74805be1dd 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n+#include \"midx.h\"\n #include \"odb/source-packed.h\"\n #include \"packfile.h\"\n \n@@ -16,6 +17,20 @@ static void odb_source_packed_reparent(const char *name UNUSED,\n \tpacked->base.path = path;\n }\n \n+static void odb_source_packed_close(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\n+\tfor (struct packfile_list_entry *e = packed->packs.head; e; e = e->next) {\n+\t\tif (e->pack->do_not_close)\n+\t\t\tBUG(\"want to close pack marked 'do-not-close'\");\n+\t\tclose_pack(e->pack);\n+\t}\n+\tif (packed->midx)\n+\t\tclose_midx(packed->midx);\n+\tpacked->midx = NULL;\n+}\n+\n static void odb_source_packed_free(struct odb_source *source)\n {\n \tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n@@ -42,6 +57,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tstrmap_init(&packed->packs_by_path);\n \n \tpacked->base.free = odb_source_packed_free;\n+\tpacked->base.close = odb_source_packed_close;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 6d492216de..e5386145a7 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2749,18 +2749,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l\n \treturn 0;\n }\n \n-void packfile_store_close(struct odb_source_packed *store)\n-{\n-\tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next) {\n-\t\tif (e->pack->do_not_close)\n-\t\t\tBUG(\"want to close pack marked 'do-not-close'\");\n-\t\tclose_pack(e->pack);\n-\t}\n-\tif (store->midx)\n-\t\tclose_midx(store->midx);\n-\tstore->midx = NULL;\n-}\n-\n struct odb_packed_read_stream {\n \tstruct odb_read_stream base;\n \tstruct packed_git *pack;\ndiff --git a/packfile.h b/packfile.h\nindex e8bc9349f8..9dc3a13112 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -55,12 +55,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-/*\n- * Close all packfiles associated with this store. The packfiles won't be\n- * free'd, so they can be re-opened at a later point in time.\n- */\n-void packfile_store_close(struct odb_source_packed *store);\n-\n /*\n  * Prepare the packfile store by loading packfiles and multi-pack indices for\n  * all alternates. This becomes a no-op if the store is already prepared.\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545718","messageId":"20260617-pks-odb-source-packed-v3-7-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 07/17] odb/source-packed: wire up `reprepare()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:50Z","receivedAt":"2026-06-17T06:40:14Z","isPatch":true,"body":"Move the logic to prepare and reprepare the \"packed\" source into\n\"odb/source-packed.c\" and wire it up as the `reprepare()` callback.\n\nNote that \"preparing\" a source is not yet generic. Eventually, it would\nprobably make sense to turn the existing `reprepare()` callback into a\n`prepare()` callback with an optional flag to force re-preparing. But\nthis step will be handled in a separate patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/grep.c      |   2 +-\n midx.c              |   2 +-\n odb/source-files.c  |   2 +-\n odb/source-packed.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++\n odb/source-packed.h |   9 +++\n packfile.c          | 160 +---------------------------------------------------\n packfile.h          |  17 ------\n 7 files changed, 172 insertions(+), 177 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 6a09571903..8080d1bf5e 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -1363,7 +1363,7 @@ int cmd_grep(int argc,\n \t\t\todb_prepare_alternates(the_repository->objects);\n \t\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n \t\t\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\t\t\tpackfile_store_prepare(files->packed);\n+\t\t\t\todb_source_packed_prepare(files->packed);\n \t\t\t}\n \t\t}\n \ndiff --git a/midx.c b/midx.c\nindex efbfbb13f4..00bbd137b2 100644\n--- a/midx.c\n+++ b/midx.c\n@@ -102,7 +102,7 @@ static int midx_read_object_offsets(const unsigned char *chunk_start,\n struct multi_pack_index *get_multi_pack_index(struct odb_source *source)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tpackfile_store_prepare(files->packed);\n+\todb_source_packed_prepare(files->packed);\n \treturn files->packed->midx;\n }\n \ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 9b0fa9ccdc..7b1e0ac565 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -45,7 +45,7 @@ static void odb_source_files_reprepare(struct odb_source *source)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \todb_source_reprepare(&files->loose->base);\n-\tpackfile_store_reprepare(files->packed);\n+\todb_source_reprepare(&files->packed->base);\n }\n \n static int odb_source_files_read_object_info(struct odb_source *source,\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 74805be1dd..e8e2e5bb48 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -1,10 +1,166 @@\n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n+#include \"dir.h\"\n+#include \"mergesort.h\"\n #include \"midx.h\"\n #include \"odb/source-packed.h\"\n #include \"packfile.h\"\n \n+void (*report_garbage)(unsigned seen_bits, const char *path);\n+\n+static void report_helper(const struct string_list *list,\n+\t\t\t  int seen_bits, int first, int last)\n+{\n+\tif (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX))\n+\t\treturn;\n+\n+\tfor (; first < last; first++)\n+\t\treport_garbage(seen_bits, list->items[first].string);\n+}\n+\n+static void report_pack_garbage(struct string_list *list)\n+{\n+\tint baselen = -1, first = 0, seen_bits = 0;\n+\n+\tif (!report_garbage)\n+\t\treturn;\n+\n+\tstring_list_sort(list);\n+\n+\tfor (size_t i = 0; i < list->nr; i++) {\n+\t\tconst char *path = list->items[i].string;\n+\t\tif (baselen != -1 &&\n+\t\t    strncmp(path, list->items[first].string, baselen)) {\n+\t\t\treport_helper(list, seen_bits, first, i);\n+\t\t\tbaselen = -1;\n+\t\t\tseen_bits = 0;\n+\t\t}\n+\t\tif (baselen == -1) {\n+\t\t\tconst char *dot = strrchr(path, '.');\n+\t\t\tif (!dot) {\n+\t\t\t\treport_garbage(PACKDIR_FILE_GARBAGE, path);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tbaselen = dot - path + 1;\n+\t\t\tfirst = i;\n+\t\t}\n+\t\tif (!strcmp(path + baselen, \"pack\"))\n+\t\t\tseen_bits |= 1;\n+\t\telse if (!strcmp(path + baselen, \"idx\"))\n+\t\t\tseen_bits |= 2;\n+\t}\n+\treport_helper(list, seen_bits, first, list->nr);\n+}\n+\n+struct prepare_pack_data {\n+\tstruct odb_source *source;\n+\tstruct string_list *garbage;\n+};\n+\n+static void prepare_pack(const char *full_name, size_t full_name_len,\n+\t\t\t const char *file_name, void *_data)\n+{\n+\tstruct prepare_pack_data *data = (struct prepare_pack_data *)_data;\n+\tstruct odb_source_files *files = odb_source_files_downcast(data->source);\n+\tsize_t base_len = full_name_len;\n+\n+\tif (strip_suffix_mem(full_name, &base_len, \".idx\") &&\n+\t    !(files->packed->midx &&\n+\t      midx_contains_pack(files->packed->midx, file_name))) {\n+\t\tchar *trimmed_path = xstrndup(full_name, full_name_len);\n+\t\tpackfile_store_load_pack(files->packed,\n+\t\t\t\t\t trimmed_path, data->source->local);\n+\t\tfree(trimmed_path);\n+\t}\n+\n+\tif (!report_garbage)\n+\t\treturn;\n+\n+\tif (!strcmp(file_name, \"multi-pack-index\") ||\n+\t    !strcmp(file_name, \"multi-pack-index.d\"))\n+\t\treturn;\n+\tif (starts_with(file_name, \"multi-pack-index\") &&\n+\t    (ends_with(file_name, \".bitmap\") || ends_with(file_name, \".rev\")))\n+\t\treturn;\n+\tif (ends_with(file_name, \".idx\") ||\n+\t    ends_with(file_name, \".rev\") ||\n+\t    ends_with(file_name, \".pack\") ||\n+\t    ends_with(file_name, \".bitmap\") ||\n+\t    ends_with(file_name, \".keep\") ||\n+\t    ends_with(file_name, \".promisor\") ||\n+\t    ends_with(file_name, \".mtimes\"))\n+\t\tstring_list_append(data->garbage, full_name);\n+\telse\n+\t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n+}\n+\n+static void prepare_packed_git_one(struct odb_source *source)\n+{\n+\tstruct string_list garbage = STRING_LIST_INIT_DUP;\n+\tstruct prepare_pack_data data = {\n+\t\t.source = source,\n+\t\t.garbage = &garbage,\n+\t};\n+\n+\tfor_each_file_in_pack_dir(source->path, prepare_pack, &data);\n+\n+\treport_pack_garbage(data.garbage);\n+\tstring_list_clear(data.garbage, 0);\n+}\n+\n+DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next);\n+\n+static int sort_pack(const struct packfile_list_entry *a,\n+\t\t     const struct packfile_list_entry *b)\n+{\n+\tint st;\n+\n+\t/*\n+\t * Local packs tend to contain objects specific to our\n+\t * variant of the project than remote ones.  In addition,\n+\t * remote ones could be on a network mounted filesystem.\n+\t * Favor local ones for these reasons.\n+\t */\n+\tst = a->pack->pack_local - b->pack->pack_local;\n+\tif (st)\n+\t\treturn -st;\n+\n+\t/*\n+\t * Younger packs tend to contain more recent objects,\n+\t * and more recent objects tend to get accessed more\n+\t * often.\n+\t */\n+\tif (a->pack->mtime < b->pack->mtime)\n+\t\treturn 1;\n+\telse if (a->pack->mtime == b->pack->mtime)\n+\t\treturn 0;\n+\treturn -1;\n+}\n+\n+void odb_source_packed_prepare(struct odb_source_packed *source)\n+{\n+\tif (source->initialized)\n+\t\treturn;\n+\n+\tprepare_multi_pack_index_one(&source->files->base);\n+\tprepare_packed_git_one(&source->files->base);\n+\n+\tsort_packs(&source->packs.head, sort_pack);\n+\tfor (struct packfile_list_entry *e = source->packs.head; e; e = e->next)\n+\t\tif (!e->next)\n+\t\t\tsource->packs.tail = e;\n+\n+\tsource->initialized = true;\n+}\n+\n+static void odb_source_packed_reprepare(struct odb_source *source)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tpacked->initialized = false;\n+\todb_source_packed_prepare(packed);\n+}\n+\n static void odb_source_packed_reparent(const char *name UNUSED,\n \t\t\t\t       const char *old_cwd,\n \t\t\t\t       const char *new_cwd,\n@@ -58,6 +214,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \n \tpacked->base.free = odb_source_packed_free;\n \tpacked->base.close = odb_source_packed_close;\n+\tpacked->base.reprepare = odb_source_packed_reprepare;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 68e64cabab..9d4796261a 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -81,4 +81,13 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so\n \treturn container_of(source, struct odb_source_packed, base);\n }\n \n+/*\n+ * Prepare the source by loading packfiles and multi-pack indices for\n+ * all alternates. This becomes a no-op if the source is already prepared.\n+ *\n+ * It shouldn't typically be necessary to call this function directly, as\n+ * functions that access the source know to prepare it.\n+ */\n+void odb_source_packed_prepare(struct odb_source_packed *source);\n+\n #endif\ndiff --git a/packfile.c b/packfile.c\nindex e5386145a7..65631f674f 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -8,7 +8,6 @@\n #include \"pack.h\"\n #include \"repository.h\"\n #include \"dir.h\"\n-#include \"mergesort.h\"\n #include \"packfile.h\"\n #include \"delta.h\"\n #include \"hash-lookup.h\"\n@@ -812,52 +811,6 @@ struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \treturn p;\n }\n \n-void (*report_garbage)(unsigned seen_bits, const char *path);\n-\n-static void report_helper(const struct string_list *list,\n-\t\t\t  int seen_bits, int first, int last)\n-{\n-\tif (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX))\n-\t\treturn;\n-\n-\tfor (; first < last; first++)\n-\t\treport_garbage(seen_bits, list->items[first].string);\n-}\n-\n-static void report_pack_garbage(struct string_list *list)\n-{\n-\tint i, baselen = -1, first = 0, seen_bits = 0;\n-\n-\tif (!report_garbage)\n-\t\treturn;\n-\n-\tstring_list_sort(list);\n-\n-\tfor (i = 0; i < list->nr; i++) {\n-\t\tconst char *path = list->items[i].string;\n-\t\tif (baselen != -1 &&\n-\t\t    strncmp(path, list->items[first].string, baselen)) {\n-\t\t\treport_helper(list, seen_bits, first, i);\n-\t\t\tbaselen = -1;\n-\t\t\tseen_bits = 0;\n-\t\t}\n-\t\tif (baselen == -1) {\n-\t\t\tconst char *dot = strrchr(path, '.');\n-\t\t\tif (!dot) {\n-\t\t\t\treport_garbage(PACKDIR_FILE_GARBAGE, path);\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t\tbaselen = dot - path + 1;\n-\t\t\tfirst = i;\n-\t\t}\n-\t\tif (!strcmp(path + baselen, \"pack\"))\n-\t\t\tseen_bits |= 1;\n-\t\telse if (!strcmp(path + baselen, \"idx\"))\n-\t\t\tseen_bits |= 2;\n-\t}\n-\treport_helper(list, seen_bits, first, list->nr);\n-}\n-\n void for_each_file_in_pack_subdir(const char *objdir,\n \t\t\t\t  const char *subdir,\n \t\t\t\t  each_file_in_pack_dir_fn fn,\n@@ -900,116 +853,9 @@ void for_each_file_in_pack_dir(const char *objdir,\n \tfor_each_file_in_pack_subdir(objdir, NULL, fn, data);\n }\n \n-struct prepare_pack_data {\n-\tstruct odb_source *source;\n-\tstruct string_list *garbage;\n-};\n-\n-static void prepare_pack(const char *full_name, size_t full_name_len,\n-\t\t\t const char *file_name, void *_data)\n-{\n-\tstruct prepare_pack_data *data = (struct prepare_pack_data *)_data;\n-\tstruct odb_source_files *files = odb_source_files_downcast(data->source);\n-\tsize_t base_len = full_name_len;\n-\n-\tif (strip_suffix_mem(full_name, &base_len, \".idx\") &&\n-\t    !(files->packed->midx &&\n-\t      midx_contains_pack(files->packed->midx, file_name))) {\n-\t\tchar *trimmed_path = xstrndup(full_name, full_name_len);\n-\t\tpackfile_store_load_pack(files->packed,\n-\t\t\t\t\t trimmed_path, data->source->local);\n-\t\tfree(trimmed_path);\n-\t}\n-\n-\tif (!report_garbage)\n-\t\treturn;\n-\n-\tif (!strcmp(file_name, \"multi-pack-index\") ||\n-\t    !strcmp(file_name, \"multi-pack-index.d\"))\n-\t\treturn;\n-\tif (starts_with(file_name, \"multi-pack-index\") &&\n-\t    (ends_with(file_name, \".bitmap\") || ends_with(file_name, \".rev\")))\n-\t\treturn;\n-\tif (ends_with(file_name, \".idx\") ||\n-\t    ends_with(file_name, \".rev\") ||\n-\t    ends_with(file_name, \".pack\") ||\n-\t    ends_with(file_name, \".bitmap\") ||\n-\t    ends_with(file_name, \".keep\") ||\n-\t    ends_with(file_name, \".promisor\") ||\n-\t    ends_with(file_name, \".mtimes\"))\n-\t\tstring_list_append(data->garbage, full_name);\n-\telse\n-\t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n-}\n-\n-static void prepare_packed_git_one(struct odb_source *source)\n-{\n-\tstruct string_list garbage = STRING_LIST_INIT_DUP;\n-\tstruct prepare_pack_data data = {\n-\t\t.source = source,\n-\t\t.garbage = &garbage,\n-\t};\n-\n-\tfor_each_file_in_pack_dir(source->path, prepare_pack, &data);\n-\n-\treport_pack_garbage(data.garbage);\n-\tstring_list_clear(data.garbage, 0);\n-}\n-\n-DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next);\n-\n-static int sort_pack(const struct packfile_list_entry *a,\n-\t\t     const struct packfile_list_entry *b)\n-{\n-\tint st;\n-\n-\t/*\n-\t * Local packs tend to contain objects specific to our\n-\t * variant of the project than remote ones.  In addition,\n-\t * remote ones could be on a network mounted filesystem.\n-\t * Favor local ones for these reasons.\n-\t */\n-\tst = a->pack->pack_local - b->pack->pack_local;\n-\tif (st)\n-\t\treturn -st;\n-\n-\t/*\n-\t * Younger packs tend to contain more recent objects,\n-\t * and more recent objects tend to get accessed more\n-\t * often.\n-\t */\n-\tif (a->pack->mtime < b->pack->mtime)\n-\t\treturn 1;\n-\telse if (a->pack->mtime == b->pack->mtime)\n-\t\treturn 0;\n-\treturn -1;\n-}\n-\n-void packfile_store_prepare(struct odb_source_packed *store)\n-{\n-\tif (store->initialized)\n-\t\treturn;\n-\n-\tprepare_multi_pack_index_one(&store->files->base);\n-\tprepare_packed_git_one(&store->files->base);\n-\n-\tsort_packs(&store->packs.head, sort_pack);\n-\tfor (struct packfile_list_entry *e = store->packs.head; e; e = e->next)\n-\t\tif (!e->next)\n-\t\t\tstore->packs.tail = e;\n-\n-\tstore->initialized = true;\n-}\n-\n-void packfile_store_reprepare(struct odb_source_packed *store)\n-{\n-\tstore->initialized = false;\n-\tpackfile_store_prepare(store);\n-}\n-\n struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store)\n {\n-\tpackfile_store_prepare(store);\n+\todb_source_packed_prepare(store);\n \n \tif (store->midx) {\n \t\tstruct multi_pack_index *m = store->midx;\n@@ -2083,7 +1929,7 @@ static int find_pack_entry(struct odb_source_packed *store,\n {\n \tstruct packfile_list_entry *l;\n \n-\tpackfile_store_prepare(store);\n+\todb_source_packed_prepare(store);\n \tif (store->midx && fill_midx_entry(store->midx, oid, e))\n \t\treturn 1;\n \n@@ -2130,7 +1976,7 @@ int packfile_store_read_object_info(struct odb_source_packed *store,\n \t * been added since the last time we have prepared the packfile store.\n \t */\n \tif (flags & OBJECT_INFO_SECOND_READ)\n-\t\tpackfile_store_reprepare(store);\n+\t\todb_source_reprepare(&store->base);\n \n \tif (!find_pack_entry(store, oid, &e))\n \t\treturn 1;\ndiff --git a/packfile.h b/packfile.h\nindex 9dc3a13112..9674e573ae 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -55,23 +55,6 @@ struct packed_git {\n \tchar pack_name[FLEX_ARRAY]; /* more */\n };\n \n-/*\n- * Prepare the packfile store by loading packfiles and multi-pack indices for\n- * all alternates. This becomes a no-op if the store is already prepared.\n- *\n- * It shouldn't typically be necessary to call this function directly, as\n- * functions that access the store know to prepare it.\n- */\n-void packfile_store_prepare(struct odb_source_packed *store);\n-\n-/*\n- * Clear the packfile caches and try to look up any new packfiles that have\n- * appeared since last preparing the packfiles store.\n- *\n- * This function must be called under the `odb_read_lock()`.\n- */\n-void packfile_store_reprepare(struct odb_source_packed *store);\n-\n /*\n  * Add the pack to the store so that contained objects become accessible via\n  * the store. This moves ownership into the store.\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545719","messageId":"20260617-pks-odb-source-packed-v3-8-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 08/17] packfile: use higher-level interface to implement `has_object_pack()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:51Z","receivedAt":"2026-06-17T06:40:17Z","isPatch":true,"body":"In `has_object_pack()` we're checking whether a specific object exists\nas part of a packfile. This is done by calling the low-level function\n`find_pack_entry()`, but this function will eventually be moved into\n\"odb/source-packed.c\" and made file-local.\n\nRefactor the code to use `packfile_store_read_object_info()` instead.\nThis refactoring is functionally equivalent as that function will call\n`find_pack_entry()` itself and then return immediately when it ain't got\nno object info pointer as parameter.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 6 ++----\n 1 file changed, 2 insertions(+), 4 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex 65631f674f..b35afd7797 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2049,14 +2049,12 @@ struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed\n int has_object_pack(struct repository *r, const struct object_id *oid)\n {\n \tstruct odb_source *source;\n-\tstruct pack_entry e;\n \n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n \t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tint ret = find_pack_entry(files->packed, oid, &e);\n-\t\tif (ret)\n-\t\t\treturn ret;\n+\t\tif (!packfile_store_read_object_info(files->packed, oid, NULL, 0))\n+\t\t\treturn 1;\n \t}\n \n \treturn 0;\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545720","messageId":"20260617-pks-odb-source-packed-v3-9-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 09/17] odb/source-packed: wire up `read_object_info()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:52Z","receivedAt":"2026-06-17T06:40:20Z","isPatch":true,"body":"Move the logic to read object info from a \"packed\" source into\n\"odb/source-packed.c\" and wire it up as the `read_object_info()`\ncallback.\n\nNote that we also move around the supporting `find_pack_entry()`, but we\nstill have to expose it to other callers that exist in \"packfile.c\".\nThis will be fixed in subsequent commits though, where all callers in\n\"packfile.c\" will have been moved into \"odb/source-packed.c\", and at\nthat point we'll be able to make `find_pack_entry()` file-local again.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 60 +++++++++++++++++++++++++++++++++++++++++++\n odb/source-packed.h |  6 +++++\n packfile.c          | 74 ++++++-----------------------------------------------\n packfile.h          | 15 +++--------\n 5 files changed, 79 insertions(+), 78 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 7b1e0ac565..8cae35d25e 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -55,7 +55,7 @@ static int odb_source_files_read_object_info(struct odb_source *source,\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n \n-\tif (!packfile_store_read_object_info(files->packed, oid, oi, flags) ||\n+\tif (!odb_source_read_object_info(&files->packed->base, oid, oi, flags) ||\n \t    !odb_source_read_object_info(&files->loose->base, oid, oi, flags))\n \t\treturn 0;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex e8e2e5bb48..f71a194739 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -7,6 +7,65 @@\n #include \"odb/source-packed.h\"\n #include \"packfile.h\"\n \n+int find_pack_entry(struct odb_source_packed *store,\n+\t\t    const struct object_id *oid,\n+\t\t    struct pack_entry *e)\n+{\n+\tstruct packfile_list_entry *l;\n+\n+\todb_source_packed_prepare(store);\n+\tif (store->midx && fill_midx_entry(store->midx, oid, e))\n+\t\treturn 1;\n+\n+\tfor (l = store->packs.head; l; l = l->next) {\n+\t\tstruct packed_git *p = l->pack;\n+\n+\t\tif (!p->multi_pack_index && packfile_fill_entry(p, oid, e)) {\n+\t\t\tif (!store->skip_mru_updates)\n+\t\t\t\tpackfile_list_prepend(&store->packs, p);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int odb_source_packed_read_object_info(struct odb_source *source,\n+\t\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t\t      struct object_info *oi,\n+\t\t\t\t\t      enum object_info_flags flags)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct pack_entry e;\n+\tint ret;\n+\n+\t/*\n+\t * In case the first read didn't surface the object, we have to reload\n+\t * packfiles. This may cause us to discover new packfiles that have\n+\t * been added since the last time we have prepared the packfile store.\n+\t */\n+\tif (flags & OBJECT_INFO_SECOND_READ)\n+\t\todb_source_reprepare(source);\n+\n+\tif (!find_pack_entry(packed, oid, &e))\n+\t\treturn 1;\n+\n+\t/*\n+\t * We know that the caller doesn't actually need the\n+\t * information below, so return early.\n+\t */\n+\tif (!oi)\n+\t\treturn 0;\n+\n+\tret = packed_object_info(e.p, e.offset, oi);\n+\tif (ret < 0) {\n+\t\tmark_bad_packed_object(e.p, oid);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -215,6 +274,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.free = odb_source_packed_free;\n \tpacked->base.close = odb_source_packed_close;\n \tpacked->base.reprepare = odb_source_packed_reprepare;\n+\tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 9d4796261a..f430ee0b94 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -90,4 +90,10 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so\n  */\n void odb_source_packed_prepare(struct odb_source_packed *source);\n \n+struct pack_entry;\n+\n+int find_pack_entry(struct odb_source_packed *store,\n+\t\t    const struct object_id *oid,\n+\t\t    struct pack_entry *e);\n+\n #endif\ndiff --git a/packfile.c b/packfile.c\nindex b35afd7797..29530532ba 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1895,9 +1895,9 @@ int is_pack_valid(struct packed_git *p)\n \treturn !open_packed_git(p);\n }\n \n-static int fill_pack_entry(const struct object_id *oid,\n-\t\t\t   struct pack_entry *e,\n-\t\t\t   struct packed_git *p)\n+int packfile_fill_entry(struct packed_git *p,\n+\t\t\tconst struct object_id *oid,\n+\t\t\tstruct pack_entry *e)\n {\n \toff_t offset;\n \n@@ -1923,29 +1923,6 @@ static int fill_pack_entry(const struct object_id *oid,\n \treturn 1;\n }\n \n-static int find_pack_entry(struct odb_source_packed *store,\n-\t\t\t   const struct object_id *oid,\n-\t\t\t   struct pack_entry *e)\n-{\n-\tstruct packfile_list_entry *l;\n-\n-\todb_source_packed_prepare(store);\n-\tif (store->midx && fill_midx_entry(store->midx, oid, e))\n-\t\treturn 1;\n-\n-\tfor (l = store->packs.head; l; l = l->next) {\n-\t\tstruct packed_git *p = l->pack;\n-\n-\t\tif (!p->multi_pack_index && fill_pack_entry(oid, e, p)) {\n-\t\t\tif (!store->skip_mru_updates)\n-\t\t\t\tpackfile_list_prepend(&store->packs, p);\n-\t\t\treturn 1;\n-\t\t}\n-\t}\n-\n-\treturn 0;\n-}\n-\n int packfile_store_freshen_object(struct odb_source_packed *store,\n \t\t\t\t  const struct object_id *oid)\n {\n@@ -1962,41 +1939,6 @@ int packfile_store_freshen_object(struct odb_source_packed *store,\n \treturn 1;\n }\n \n-int packfile_store_read_object_info(struct odb_source_packed *store,\n-\t\t\t\t    const struct object_id *oid,\n-\t\t\t\t    struct object_info *oi,\n-\t\t\t\t    enum object_info_flags flags)\n-{\n-\tstruct pack_entry e;\n-\tint ret;\n-\n-\t/*\n-\t * In case the first read didn't surface the object, we have to reload\n-\t * packfiles. This may cause us to discover new packfiles that have\n-\t * been added since the last time we have prepared the packfile store.\n-\t */\n-\tif (flags & OBJECT_INFO_SECOND_READ)\n-\t\todb_source_reprepare(&store->base);\n-\n-\tif (!find_pack_entry(store, oid, &e))\n-\t\treturn 1;\n-\n-\t/*\n-\t * We know that the caller doesn't actually need the\n-\t * information below, so return early.\n-\t */\n-\tif (!oi)\n-\t\treturn 0;\n-\n-\tret = packed_object_info(e.p, e.offset, oi);\n-\tif (ret < 0) {\n-\t\tmark_bad_packed_object(e.p, oid);\n-\t\treturn -1;\n-\t}\n-\n-\treturn 0;\n-}\n-\n static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t     unsigned flags)\n {\n@@ -2053,7 +1995,7 @@ int has_object_pack(struct repository *r, const struct object_id *oid)\n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n \t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tif (!packfile_store_read_object_info(files->packed, oid, NULL, 0))\n+\t\tif (!odb_source_read_object_info(&files->packed->base, oid, NULL, 0))\n \t\t\treturn 1;\n \t}\n \n@@ -2074,7 +2016,7 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n \t\tfor (; *cache; cache++) {\n \t\t\tstruct packed_git *p = *cache;\n-\t\t\tif (fill_pack_entry(oid, &e, p))\n+\t\t\tif (packfile_fill_entry(p, oid, &e))\n \t\t\t\treturn 1;\n \t\t}\n \t}\n@@ -2208,8 +2150,8 @@ static int for_each_prefixed_object_in_midx(\n \t\t\tif (data->request) {\n \t\t\t\tstruct object_info oi = *data->request;\n \n-\t\t\t\tret = packfile_store_read_object_info(store, current,\n-\t\t\t\t\t\t\t\t      &oi, 0);\n+\t\t\t\tret = odb_source_read_object_info(&store->base, current,\n+\t\t\t\t\t\t\t\t  &oi, 0);\n \t\t\t\tif (ret)\n \t\t\t\t\tgoto out;\n \n@@ -2259,7 +2201,7 @@ static int for_each_prefixed_object_in_pack(\n \t\tif (data->request) {\n \t\t\tstruct object_info oi = *data->request;\n \n-\t\t\tret = packfile_store_read_object_info(store, &oid, &oi, 0);\n+\t\t\tret = odb_source_read_object_info(&store->base, &oid, &oi, 0);\n \t\t\tif (ret)\n \t\t\t\tgoto out;\n \ndiff --git a/packfile.h b/packfile.h\nindex 9674e573ae..25d458beb0 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -128,17 +128,6 @@ int packfile_store_read_object_stream(struct odb_read_stream **out,\n \t\t\t\t      struct odb_source_packed *store,\n \t\t\t\t      const struct object_id *oid);\n \n-/*\n- * Try to read the object identified by its ID from the object store and\n- * populate the object info with its data. Returns 1 in case the object was\n- * not found, 0 if it was and read successfully, and a negative error code in\n- * case the object was corrupted.\n- */\n-int packfile_store_read_object_info(struct odb_source_packed *store,\n-\t\t\t\t    const struct object_id *oid,\n-\t\t\t\t    struct object_info *oi,\n-\t\t\t\t    enum object_info_flags flags);\n-\n /*\n  * Open the packfile and add it to the store if it isn't yet known. Returns\n  * either the newly opened packfile or the preexisting packfile. Returns a\n@@ -340,6 +329,10 @@ off_t nth_packed_object_offset(const struct packed_git *, uint32_t n);\n  */\n off_t find_pack_entry_one(const struct object_id *oid, struct packed_git *);\n \n+int packfile_fill_entry(struct packed_git *p,\n+\t\t\tconst struct object_id *oid,\n+\t\t\tstruct pack_entry *e);\n+\n int is_pack_valid(struct packed_git *);\n void *unpack_entry(struct repository *r, struct packed_git *, off_t, enum object_type *, unsigned long *);\n unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, size_t *sizep);\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545721","messageId":"20260617-pks-odb-source-packed-v3-10-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 10/17] odb/source-packed: wire up `read_object_stream()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:53Z","receivedAt":"2026-06-17T06:40:22Z","isPatch":true,"body":"Wire up the `read_object_stream()` callback for the packed source and\ncall it in the \"files\" source via the `odb_source_read_object_stream()`\ninterface.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 16 ++++++++++++++++\n packfile.c          | 12 ------------\n packfile.h          |  4 ----\n 4 files changed, 17 insertions(+), 17 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 8cae35d25e..dff69d0e4e 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -67,7 +67,7 @@ static int odb_source_files_read_object_stream(struct odb_read_stream **out,\n \t\t\t\t\t       const struct object_id *oid)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tif (!packfile_store_read_object_stream(out, files->packed, oid) ||\n+\tif (!odb_source_read_object_stream(out, &files->packed->base, oid) ||\n \t    !odb_source_read_object_stream(out, &files->loose->base, oid))\n \t\treturn 0;\n \treturn -1;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex f71a194739..23d7149fe3 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -2,9 +2,11 @@\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n #include \"dir.h\"\n+#include \"git-zlib.h\"\n #include \"mergesort.h\"\n #include \"midx.h\"\n #include \"odb/source-packed.h\"\n+#include \"odb/streaming.h\"\n #include \"packfile.h\"\n \n int find_pack_entry(struct odb_source_packed *store,\n@@ -66,6 +68,19 @@ static int odb_source_packed_read_object_info(struct odb_source *source,\n \treturn 0;\n }\n \n+static int odb_source_packed_read_object_stream(struct odb_read_stream **out,\n+\t\t\t\t\t\tstruct odb_source *source,\n+\t\t\t\t\t\tconst struct object_id *oid)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct pack_entry e;\n+\n+\tif (!find_pack_entry(packed, oid, &e))\n+\t\treturn -1;\n+\n+\treturn packfile_read_object_stream(out, oid, e.p, e.offset);\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -275,6 +290,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.close = odb_source_packed_close;\n \tpacked->base.reprepare = odb_source_packed_reprepare;\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n+\tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 29530532ba..42c84397eb 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2658,15 +2658,3 @@ int packfile_read_object_stream(struct odb_read_stream **out,\n \n \treturn 0;\n }\n-\n-int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct odb_source_packed *store,\n-\t\t\t\t      const struct object_id *oid)\n-{\n-\tstruct pack_entry e;\n-\n-\tif (!find_pack_entry(store, oid, &e))\n-\t\treturn -1;\n-\n-\treturn packfile_read_object_stream(out, oid, e.p, e.offset);\n-}\ndiff --git a/packfile.h b/packfile.h\nindex 25d458beb0..dd97684e70 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -124,10 +124,6 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *\n \t     ((p) = (eack_pack_data.entry ? eack_pack_data.entry->pack : NULL)); \\\n \t     repo_for_each_pack_data_next(&eack_pack_data))\n \n-int packfile_store_read_object_stream(struct odb_read_stream **out,\n-\t\t\t\t      struct odb_source_packed *store,\n-\t\t\t\t      const struct object_id *oid);\n-\n /*\n  * Open the packfile and add it to the store if it isn't yet known. Returns\n  * either the newly opened packfile or the preexisting packfile. Returns a\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545722","messageId":"20260617-pks-odb-source-packed-v3-11-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 11/17] odb/source-packed: wire up `for_each_object()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:54Z","receivedAt":"2026-06-17T06:40:24Z","isPatch":true,"body":"Move `packfile_store_for_each_object()` and its associated helpers from\n\"packfile.c\" into \"odb/source-packed.c\" and wire it up as the\n`for_each_object()` callback of the \"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     |   4 +-\n builtin/pack-objects.c |   4 +-\n commit-graph.c         |   4 +-\n odb/source-files.c     |   2 +-\n odb/source-packed.c    | 258 ++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.c             | 260 +------------------------------------------------\n packfile.h             |  17 +---\n 7 files changed, 269 insertions(+), 280 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 04b64006a5..d997011531 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -916,8 +916,8 @@ static void batch_each_object(struct batch_options *opt,\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);\n-\t\t\tint ret = packfile_store_for_each_object(files->packed, &oi,\n-\t\t\t\t\t\t\t\t batch_one_object_oi, &payload, &opts);\n+\t\t\tint ret = odb_source_for_each_object(&files->packed->base, &oi,\n+\t\t\t\t\t\t\t     batch_one_object_oi, &payload, &opts);\n \t\t\tif (ret)\n \t\t\t\tbreak;\n \t\t}\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 50675481e1..5e94805478 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4503,8 +4503,8 @@ static void add_objects_in_unpacked_packs(void)\n \t\tif (!source->local)\n \t\t\tcontinue;\n \n-\t\tif (packfile_store_for_each_object(files->packed, &oi,\n-\t\t\t\t\t\t   add_object_in_unpacked_pack, NULL, &opts))\n+\t\tif (odb_source_for_each_object(&files->packed->base, &oi,\n+\t\t\t\t\t       add_object_in_unpacked_pack, NULL, &opts))\n \t\t\tdie(_(\"cannot open pack index\"));\n \t}\n }\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 9abe62bd5a..1e4038baf3 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -2016,8 +2016,8 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n \todb_prepare_alternates(ctx->r->objects);\n \tfor (source = ctx->r->objects->sources; source; source = source->next) {\n \t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tpackfile_store_for_each_object(files->packed, &oi, add_packed_commits_oi,\n-\t\t\t\t\t       ctx, &opts);\n+\t\todb_source_for_each_object(&files->packed->base, &oi, add_packed_commits_oi,\n+\t\t\t\t\t   ctx, &opts);\n \t}\n \n \tif (ctx->progress_done < ctx->approx_nr_objects)\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex dff69d0e4e..c73a7e5f90 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -88,7 +88,7 @@ static int odb_source_files_for_each_object(struct odb_source *source,\n \t\t\treturn ret;\n \t}\n \n-\tret = packfile_store_for_each_object(files->packed, request, cb, cb_data, opts);\n+\tret = odb_source_for_each_object(&files->packed->base, request, cb, cb_data, opts);\n \tif (ret)\n \t\treturn ret;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 23d7149fe3..a61c809c8c 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -81,6 +81,263 @@ static int odb_source_packed_read_object_stream(struct odb_read_stream **out,\n \treturn packfile_read_object_stream(out, oid, e.p, e.offset);\n }\n \n+struct odb_source_packed_for_each_object_wrapper_data {\n+\tstruct odb_source_packed *store;\n+\tconst struct object_info *request;\n+\todb_for_each_object_cb cb;\n+\tvoid *cb_data;\n+};\n+\n+static int odb_source_packed_for_each_object_wrapper(const struct object_id *oid,\n+\t\t\t\t\t\t     struct packed_git *pack,\n+\t\t\t\t\t\t     uint32_t index_pos,\n+\t\t\t\t\t\t     void *cb_data)\n+{\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data = cb_data;\n+\n+\tif (data->request) {\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\t\t\t\t\t      &index_pos, &oi) < 0) {\n+\t\t\tmark_bad_packed_object(pack, oid);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\treturn data->cb(oid, &oi, data->cb_data);\n+\t} else {\n+\t\treturn data->cb(oid, NULL, data->cb_data);\n+\t}\n+}\n+\n+static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b)\n+{\n+\tdo {\n+\t\tif (*a != *b)\n+\t\t\treturn 0;\n+\t\ta++;\n+\t\tb++;\n+\t\tlen -= 2;\n+\t} while (len > 1);\n+\tif (len)\n+\t\tif ((*a ^ *b) & 0xf0)\n+\t\t\treturn 0;\n+\treturn 1;\n+}\n+\n+static int for_each_prefixed_object_in_midx(\n+\tstruct odb_source_packed *store,\n+\tstruct multi_pack_index *m,\n+\tconst struct odb_for_each_object_options *opts,\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n+{\n+\tint ret;\n+\n+\tfor (; m; m = m->base_midx) {\n+\t\tuint32_t num, i, first = 0;\n+\t\tint len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ?\n+\t\t\tm->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n+\n+\t\tif (!m->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tnum = m->num_objects + m->num_objects_in_base;\n+\n+\t\tbsearch_one_midx(opts->prefix, m, &first);\n+\n+\t\t/*\n+\t\t * At this point, \"first\" is the location of the lowest\n+\t\t * object with an object name that could match \"opts->prefix\".\n+\t\t * See if we have 0, 1 or more objects that actually match(es).\n+\t\t */\n+\t\tfor (i = first; i < num; i++) {\n+\t\t\tconst struct object_id *current = NULL;\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tcurrent = nth_midxed_object_oid(&oid, m, i);\n+\n+\t\t\tif (!match_hash(len, opts->prefix->hash, current->hash))\n+\t\t\t\tbreak;\n+\n+\t\t\tif (data->request) {\n+\t\t\t\tstruct object_info oi = *data->request;\n+\n+\t\t\t\tret = odb_source_read_object_info(&store->base, current,\n+\t\t\t\t\t\t\t\t  &oi, 0);\n+\t\t\t\tif (ret)\n+\t\t\t\t\tgoto out;\n+\n+\t\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n+\t\t\t\tif (ret)\n+\t\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n+\t\t\t\tif (ret)\n+\t\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\treturn ret;\n+}\n+\n+static int for_each_prefixed_object_in_pack(\n+\tstruct odb_source_packed *store,\n+\tstruct packed_git *p,\n+\tconst struct odb_for_each_object_options *opts,\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n+{\n+\tuint32_t num, i, first = 0;\n+\tint len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ?\n+\t\tp->repo->hash_algo->hexsz : opts->prefix_hex_len;\n+\tint ret;\n+\n+\tnum = p->num_objects;\n+\tbsearch_pack(opts->prefix, p, &first);\n+\n+\t/*\n+\t * At this point, \"first\" is the location of the lowest object\n+\t * with an object name that could match \"bin_pfx\".  See if we have\n+\t * 0, 1 or more objects that actually match(es).\n+\t */\n+\tfor (i = first; i < num; i++) {\n+\t\tstruct object_id oid;\n+\n+\t\tnth_packed_object_id(&oid, p, i);\n+\t\tif (!match_hash(len, opts->prefix->hash, oid.hash))\n+\t\t\tbreak;\n+\n+\t\tif (data->request) {\n+\t\t\tstruct object_info oi = *data->request;\n+\n+\t\t\tret = odb_source_read_object_info(&store->base, &oid, &oi, 0);\n+\t\t\tif (ret)\n+\t\t\t\tgoto out;\n+\n+\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n+\t\t\tif (ret)\n+\t\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n+\t\t\tif (ret)\n+\t\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\treturn ret;\n+}\n+\n+static int odb_source_packed_for_each_prefixed_object(\n+\tstruct odb_source_packed *store,\n+\tconst struct odb_for_each_object_options *opts,\n+\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n+{\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n+\tbool pack_errors = false;\n+\tint ret;\n+\n+\tif (opts->flags)\n+\t\tBUG(\"flags unsupported\");\n+\n+\tstore->skip_mru_updates = true;\n+\n+\tm = get_multi_pack_index(&store->files->base);\n+\tif (m) {\n+\t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n+\t\tif (e->pack->multi_pack_index)\n+\t\t\tcontinue;\n+\n+\t\tif (open_pack_index(e->pack)) {\n+\t\t\tpack_errors = true;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!e->pack->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tret = for_each_prefixed_object_in_pack(store, e->pack, opts, data);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\tstore->skip_mru_updates = false;\n+\tif (!ret && pack_errors)\n+\t\tret = -1;\n+\treturn ret;\n+}\n+\n+static int odb_source_packed_for_each_object(struct odb_source *source,\n+\t\t\t\t\t     const struct object_info *request,\n+\t\t\t\t\t     odb_for_each_object_cb cb,\n+\t\t\t\t\t     void *cb_data,\n+\t\t\t\t\t     const struct odb_for_each_object_options *opts)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct odb_source_packed_for_each_object_wrapper_data data = {\n+\t\t.store = packed,\n+\t\t.request = request,\n+\t\t.cb = cb,\n+\t\t.cb_data = cb_data,\n+\t};\n+\tstruct packfile_list_entry *e;\n+\tint pack_errors = 0, ret;\n+\n+\tif (opts->prefix)\n+\t\treturn odb_source_packed_for_each_prefixed_object(packed, opts, &data);\n+\n+\tpacked->skip_mru_updates = true;\n+\n+\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n+\t\tstruct packed_git *p = e->pack;\n+\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n+\t\t    p->pack_keep_in_core)\n+\t\t\tcontinue;\n+\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n+\t\t    p->pack_keep)\n+\t\t\tcontinue;\n+\t\tif (open_pack_index(p)) {\n+\t\t\tpack_errors = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tret = for_each_object_in_pack(p, odb_source_packed_for_each_object_wrapper,\n+\t\t\t\t\t      &data, opts->flags);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\tpacked->skip_mru_updates = false;\n+\n+\tif (!ret && pack_errors)\n+\t\tret = -1;\n+\treturn ret;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -291,6 +548,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.reprepare = odb_source_packed_reprepare;\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n+\tpacked->base.for_each_object = odb_source_packed_for_each_object;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 42c84397eb..b8d6054c16 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1362,8 +1362,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-static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n-\t\t\t\t\t     uint32_t *maybe_index_pos, struct object_info *oi)\n+int packed_object_info_with_index_pos(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 \tsize_t size;\n@@ -2068,262 +2068,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-struct odb_source_packed_for_each_object_wrapper_data {\n-\tstruct odb_source_packed *store;\n-\tconst struct object_info *request;\n-\todb_for_each_object_cb cb;\n-\tvoid *cb_data;\n-};\n-\n-static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n-\t\t\t\t\t\t  struct packed_git *pack,\n-\t\t\t\t\t\t  uint32_t index_pos,\n-\t\t\t\t\t\t  void *cb_data)\n-{\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data = cb_data;\n-\n-\tif (data->request) {\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\t\t\t\t\t      &index_pos, &oi) < 0) {\n-\t\t\tmark_bad_packed_object(pack, oid);\n-\t\t\treturn -1;\n-\t\t}\n-\n-\t\treturn data->cb(oid, &oi, data->cb_data);\n-\t} else {\n-\t\treturn data->cb(oid, NULL, data->cb_data);\n-\t}\n-}\n-\n-static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b)\n-{\n-\tdo {\n-\t\tif (*a != *b)\n-\t\t\treturn 0;\n-\t\ta++;\n-\t\tb++;\n-\t\tlen -= 2;\n-\t} while (len > 1);\n-\tif (len)\n-\t\tif ((*a ^ *b) & 0xf0)\n-\t\t\treturn 0;\n-\treturn 1;\n-}\n-\n-static int for_each_prefixed_object_in_midx(\n-\tstruct odb_source_packed *store,\n-\tstruct multi_pack_index *m,\n-\tconst struct odb_for_each_object_options *opts,\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n-{\n-\tint ret;\n-\n-\tfor (; m; m = m->base_midx) {\n-\t\tuint32_t num, i, first = 0;\n-\t\tint len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ?\n-\t\t\tm->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n-\n-\t\tif (!m->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tnum = m->num_objects + m->num_objects_in_base;\n-\n-\t\tbsearch_one_midx(opts->prefix, m, &first);\n-\n-\t\t/*\n-\t\t * At this point, \"first\" is the location of the lowest\n-\t\t * object with an object name that could match \"opts->prefix\".\n-\t\t * See if we have 0, 1 or more objects that actually match(es).\n-\t\t */\n-\t\tfor (i = first; i < num; i++) {\n-\t\t\tconst struct object_id *current = NULL;\n-\t\t\tstruct object_id oid;\n-\n-\t\t\tcurrent = nth_midxed_object_oid(&oid, m, i);\n-\n-\t\t\tif (!match_hash(len, opts->prefix->hash, current->hash))\n-\t\t\t\tbreak;\n-\n-\t\t\tif (data->request) {\n-\t\t\t\tstruct object_info oi = *data->request;\n-\n-\t\t\t\tret = odb_source_read_object_info(&store->base, current,\n-\t\t\t\t\t\t\t\t  &oi, 0);\n-\t\t\t\tif (ret)\n-\t\t\t\t\tgoto out;\n-\n-\t\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n-\t\t\t\tif (ret)\n-\t\t\t\t\tgoto out;\n-\t\t\t} else {\n-\t\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n-\t\t\t\tif (ret)\n-\t\t\t\t\tgoto out;\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\treturn ret;\n-}\n-\n-static int for_each_prefixed_object_in_pack(\n-\tstruct odb_source_packed *store,\n-\tstruct packed_git *p,\n-\tconst struct odb_for_each_object_options *opts,\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n-{\n-\tuint32_t num, i, first = 0;\n-\tint len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ?\n-\t\tp->repo->hash_algo->hexsz : opts->prefix_hex_len;\n-\tint ret;\n-\n-\tnum = p->num_objects;\n-\tbsearch_pack(opts->prefix, p, &first);\n-\n-\t/*\n-\t * At this point, \"first\" is the location of the lowest object\n-\t * with an object name that could match \"bin_pfx\".  See if we have\n-\t * 0, 1 or more objects that actually match(es).\n-\t */\n-\tfor (i = first; i < num; i++) {\n-\t\tstruct object_id oid;\n-\n-\t\tnth_packed_object_id(&oid, p, i);\n-\t\tif (!match_hash(len, opts->prefix->hash, oid.hash))\n-\t\t\tbreak;\n-\n-\t\tif (data->request) {\n-\t\t\tstruct object_info oi = *data->request;\n-\n-\t\t\tret = odb_source_read_object_info(&store->base, &oid, &oi, 0);\n-\t\t\tif (ret)\n-\t\t\t\tgoto out;\n-\n-\t\t\tret = data->cb(&oid, &oi, data->cb_data);\n-\t\t\tif (ret)\n-\t\t\t\tgoto out;\n-\t\t} else {\n-\t\t\tret = data->cb(&oid, NULL, data->cb_data);\n-\t\t\tif (ret)\n-\t\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\treturn ret;\n-}\n-\n-static int packfile_store_for_each_prefixed_object(\n-\tstruct odb_source_packed *store,\n-\tconst struct odb_for_each_object_options *opts,\n-\tstruct odb_source_packed_for_each_object_wrapper_data *data)\n-{\n-\tstruct packfile_list_entry *e;\n-\tstruct multi_pack_index *m;\n-\tbool pack_errors = false;\n-\tint ret;\n-\n-\tif (opts->flags)\n-\t\tBUG(\"flags unsupported\");\n-\n-\tstore->skip_mru_updates = true;\n-\n-\tm = get_multi_pack_index(&store->files->base);\n-\tif (m) {\n-\t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n-\t\tif (ret)\n-\t\t\tgoto out;\n-\t}\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tif (e->pack->multi_pack_index)\n-\t\t\tcontinue;\n-\n-\t\tif (open_pack_index(e->pack)) {\n-\t\t\tpack_errors = true;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tif (!e->pack->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tret = for_each_prefixed_object_in_pack(store, e->pack, opts, data);\n-\t\tif (ret)\n-\t\t\tgoto out;\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tstore->skip_mru_updates = false;\n-\tif (!ret && pack_errors)\n-\t\tret = -1;\n-\treturn ret;\n-}\n-\n-int packfile_store_for_each_object(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_info *request,\n-\t\t\t\t   odb_for_each_object_cb cb,\n-\t\t\t\t   void *cb_data,\n-\t\t\t\t   const struct odb_for_each_object_options *opts)\n-{\n-\tstruct odb_source_packed_for_each_object_wrapper_data data = {\n-\t\t.store = store,\n-\t\t.request = request,\n-\t\t.cb = cb,\n-\t\t.cb_data = cb_data,\n-\t};\n-\tstruct packfile_list_entry *e;\n-\tint pack_errors = 0, ret;\n-\n-\tif (opts->prefix)\n-\t\treturn packfile_store_for_each_prefixed_object(store, opts, &data);\n-\n-\tstore->skip_mru_updates = true;\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tstruct packed_git *p = e->pack;\n-\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\tcontinue;\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t    !p->pack_promisor)\n-\t\t\tcontinue;\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n-\t\t    p->pack_keep_in_core)\n-\t\t\tcontinue;\n-\t\tif ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n-\t\t    p->pack_keep)\n-\t\t\tcontinue;\n-\t\tif (open_pack_index(p)) {\n-\t\t\tpack_errors = 1;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tret = for_each_object_in_pack(p, packfile_store_for_each_object_wrapper,\n-\t\t\t\t\t      &data, opts->flags);\n-\t\tif (ret)\n-\t\t\tgoto out;\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tstore->skip_mru_updates = false;\n-\n-\tif (!ret && pack_errors)\n-\t\tret = -1;\n-\treturn ret;\n-}\n-\n static int extend_abbrev_len(const struct object_id *a,\n \t\t\t     const struct object_id *b,\n \t\t\t     unsigned *out)\ndiff --git a/packfile.h b/packfile.h\nindex dd97684e70..0097de0b27 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -227,21 +227,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn, void *data,\n \t\t\t    enum odb_for_each_object_flags flags);\n \n-/*\n- * Iterate through all packed objects in the given packfile store and invoke\n- * the callback function for each of them. If an object info request is given,\n- * then the object info will be read for every individual object and passed to\n- * the callback as if `packfile_store_read_object_info()` was called for the\n- * object.\n- *\n- * The flags parameter is a combination of `odb_for_each_object_flags`.\n- */\n-int packfile_store_for_each_object(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_info *request,\n-\t\t\t\t   odb_for_each_object_cb cb,\n-\t\t\t\t   void *cb_data,\n-\t\t\t\t   const struct odb_for_each_object_options *opts);\n-\n int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n \t\t\t\t   const struct object_id *oid,\n \t\t\t\t   unsigned min_len,\n@@ -354,6 +339,8 @@ extern int do_check_packed_object_crc;\n  */\n int packed_object_info(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+\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 *);\n const struct packed_git *has_packed_and_bad(struct repository *, const struct object_id *);\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545723","messageId":"20260617-pks-odb-source-packed-v3-12-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 12/17] odb/source-packed: wire up `count_objects()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:55Z","receivedAt":"2026-06-17T06:40:27Z","isPatch":true,"body":"Move `packfile_store_count_objects()` from \"packfile.c\" into\n\"odb/source-packed.c\" and wire it up as the `count_objects()` callback\nof the \"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 33 +++++++++++++++++++++++++++++++++\n packfile.c          | 31 -------------------------------\n packfile.h          | 10 ----------\n 4 files changed, 34 insertions(+), 42 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex c73a7e5f90..274923e0ba 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -103,7 +103,7 @@ static int odb_source_files_count_objects(struct odb_source *source,\n \tunsigned long count;\n \tint ret;\n \n-\tret = packfile_store_count_objects(files->packed, flags, &count);\n+\tret = odb_source_count_objects(&files->packed->base, flags, &count);\n \tif (ret < 0)\n \t\tgoto out;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex a61c809c8c..070a4e3958 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -338,6 +338,38 @@ static int odb_source_packed_for_each_object(struct odb_source *source,\n \treturn ret;\n }\n \n+static int odb_source_packed_count_objects(struct odb_source *source,\n+\t\t\t\t\t   enum odb_count_objects_flags flags UNUSED,\n+\t\t\t\t\t   unsigned long *out)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n+\tunsigned long count = 0;\n+\tint ret;\n+\n+\tm = get_multi_pack_index(&packed->files->base);\n+\tif (m)\n+\t\tcount += m->num_objects + m->num_objects_in_base;\n+\n+\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n+\t\tif (e->pack->multi_pack_index)\n+\t\t\tcontinue;\n+\t\tif (open_pack_index(e->pack)) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tcount += e->pack->num_objects;\n+\t}\n+\n+\t*out = count;\n+\tret = 0;\n+\n+out:\n+\treturn ret;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -549,6 +581,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n+\tpacked->base.count_objects = odb_source_packed_count_objects;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex b8d6054c16..2da6bbe2b5 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -866,37 +866,6 @@ struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *s\n \treturn store->packs.head;\n }\n \n-int packfile_store_count_objects(struct odb_source_packed *store,\n-\t\t\t\t enum odb_count_objects_flags flags UNUSED,\n-\t\t\t\t unsigned long *out)\n-{\n-\tstruct packfile_list_entry *e;\n-\tstruct multi_pack_index *m;\n-\tunsigned long count = 0;\n-\tint ret;\n-\n-\tm = get_multi_pack_index(&store->files->base);\n-\tif (m)\n-\t\tcount += m->num_objects + m->num_objects_in_base;\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tif (e->pack->multi_pack_index)\n-\t\t\tcontinue;\n-\t\tif (open_pack_index(e->pack)) {\n-\t\t\tret = -1;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tcount += e->pack->num_objects;\n-\t}\n-\n-\t*out = count;\n-\tret = 0;\n-\n-out:\n-\treturn ret;\n-}\n-\n unsigned long unpack_object_header_buffer(const unsigned char *buf,\n \t\tunsigned long len, enum object_type *type, size_t *sizep)\n {\ndiff --git a/packfile.h b/packfile.h\nindex 0097de0b27..0613fd3c63 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -141,16 +141,6 @@ enum kept_pack_type {\n \tKEPT_PACK_IN_CORE_OPEN = (1 << 2),\n };\n \n-/*\n- * Count the number objects contained in the given packfile store. If\n- * successful, the number of objects will be written to the `out` pointer.\n- *\n- * Return 0 on success, a negative error code otherwise.\n- */\n-int packfile_store_count_objects(struct odb_source_packed *store,\n-\t\t\t\t enum odb_count_objects_flags flags,\n-\t\t\t\t unsigned long *out);\n-\n /*\n  * Retrieve the cache of kept packs from the given packfile store. Accepts a\n  * combination of `kept_pack_type` flags. The cache is computed on demand and\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545724","messageId":"20260617-pks-odb-source-packed-v3-13-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 13/17] odb/source-packed: wire up `find_abbrev_len()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:56Z","receivedAt":"2026-06-17T06:40:30Z","isPatch":true,"body":"Move `packfile_store_find_abbrev_len()` and its associated helpers from\n\"packfile.c\" into \"odb/source-packed.c\" and wire it up as the\n`find_abbrev_len()` callback of the \"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |   2 +-\n odb/source-packed.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.c          | 111 ---------------------------------------------------\n packfile.h          |   5 ---\n 4 files changed, 114 insertions(+), 117 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 274923e0ba..8ad782dc7b 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -133,7 +133,7 @@ static int odb_source_files_find_abbrev_len(struct odb_source *source,\n \tunsigned len = min_len;\n \tint ret;\n \n-\tret = packfile_store_find_abbrev_len(files->packed, oid, len, &len);\n+\tret = odb_source_find_abbrev_len(&files->packed->base, oid, len, &len);\n \tif (ret < 0)\n \t\tgoto out;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 070a4e3958..b801b62023 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -370,6 +370,118 @@ static int odb_source_packed_count_objects(struct odb_source *source,\n \treturn ret;\n }\n \n+static int extend_abbrev_len(const struct object_id *a,\n+\t\t\t     const struct object_id *b,\n+\t\t\t     unsigned *out)\n+{\n+\tunsigned len = oid_common_prefix_hexlen(a, b);\n+\tif (len != hash_algos[a->algo].hexsz && len >= *out)\n+\t\t*out = len + 1;\n+\treturn 0;\n+}\n+\n+static void find_abbrev_len_for_midx(struct multi_pack_index *m,\n+\t\t\t\t     const struct object_id *oid,\n+\t\t\t\t     unsigned min_len,\n+\t\t\t\t     unsigned *out)\n+{\n+\tunsigned len = min_len;\n+\n+\tfor (; m; m = m->base_midx) {\n+\t\tint match = 0;\n+\t\tuint32_t num, first = 0;\n+\t\tstruct object_id found_oid;\n+\n+\t\tif (!m->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tnum = m->num_objects + m->num_objects_in_base;\n+\t\tmatch = bsearch_one_midx(oid, m, &first);\n+\n+\t\t/*\n+\t\t * first is now the position in the packfile where we\n+\t\t * would insert the object ID if it does not exist (or the\n+\t\t * position of the object ID if it does exist). Hence, we\n+\t\t * consider a maximum of two objects nearby for the\n+\t\t * abbreviation length.\n+\t\t */\n+\n+\t\tif (!match) {\n+\t\t\tif (nth_midxed_object_oid(&found_oid, m, first))\n+\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t\t} else if (first < num - 1) {\n+\t\t\tif (nth_midxed_object_oid(&found_oid, m, first + 1))\n+\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t\t}\n+\t\tif (first > 0) {\n+\t\t\tif (nth_midxed_object_oid(&found_oid, m, first - 1))\n+\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t\t}\n+\t}\n+\n+\t*out = len;\n+}\n+\n+static void find_abbrev_len_for_pack(struct packed_git *p,\n+\t\t\t\t     const struct object_id *oid,\n+\t\t\t\t     unsigned min_len,\n+\t\t\t\t     unsigned *out)\n+{\n+\tint match;\n+\tuint32_t num, first = 0;\n+\tstruct object_id found_oid;\n+\tunsigned len = min_len;\n+\n+\tnum = p->num_objects;\n+\tmatch = bsearch_pack(oid, p, &first);\n+\n+\t/*\n+\t * first is now the position in the packfile where we would insert\n+\t * the object ID if it does not exist (or the position of mad->hash if\n+\t * it does exist). Hence, we consider a maximum of two objects\n+\t * nearby for the abbreviation length.\n+\t */\n+\tif (!match) {\n+\t\tif (!nth_packed_object_id(&found_oid, p, first))\n+\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t} else if (first < num - 1) {\n+\t\tif (!nth_packed_object_id(&found_oid, p, first + 1))\n+\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t}\n+\tif (first > 0) {\n+\t\tif (!nth_packed_object_id(&found_oid, p, first - 1))\n+\t\t\textend_abbrev_len(&found_oid, oid, &len);\n+\t}\n+\n+\t*out = len;\n+}\n+\n+static int odb_source_packed_find_abbrev_len(struct odb_source *source,\n+\t\t\t\t\t     const struct object_id *oid,\n+\t\t\t\t\t     unsigned min_len,\n+\t\t\t\t\t     unsigned *out)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n+\n+\tm = get_multi_pack_index(&packed->files->base);\n+\tif (m)\n+\t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n+\n+\tfor (e = packfile_store_get_packs(packed); e; e = e->next) {\n+\t\tif (e->pack->multi_pack_index)\n+\t\t\tcontinue;\n+\t\tif (open_pack_index(e->pack) || !e->pack->num_objects)\n+\t\t\tcontinue;\n+\n+\t\tfind_abbrev_len_for_pack(e->pack, oid, min_len, &min_len);\n+\t}\n+\n+\t*out = min_len;\n+\treturn 0;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -582,6 +694,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n \tpacked->base.count_objects = odb_source_packed_count_objects;\n+\tpacked->base.find_abbrev_len = odb_source_packed_find_abbrev_len;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/packfile.c b/packfile.c\nindex 2da6bbe2b5..7f84094e53 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2037,117 +2037,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-static int extend_abbrev_len(const struct object_id *a,\n-\t\t\t     const struct object_id *b,\n-\t\t\t     unsigned *out)\n-{\n-\tunsigned len = oid_common_prefix_hexlen(a, b);\n-\tif (len != hash_algos[a->algo].hexsz && len >= *out)\n-\t\t*out = len + 1;\n-\treturn 0;\n-}\n-\n-static void find_abbrev_len_for_midx(struct multi_pack_index *m,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     unsigned min_len,\n-\t\t\t\t     unsigned *out)\n-{\n-\tunsigned len = min_len;\n-\n-\tfor (; m; m = m->base_midx) {\n-\t\tint match = 0;\n-\t\tuint32_t num, first = 0;\n-\t\tstruct object_id found_oid;\n-\n-\t\tif (!m->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tnum = m->num_objects + m->num_objects_in_base;\n-\t\tmatch = bsearch_one_midx(oid, m, &first);\n-\n-\t\t/*\n-\t\t * first is now the position in the packfile where we\n-\t\t * would insert the object ID if it does not exist (or the\n-\t\t * position of the object ID if it does exist). Hence, we\n-\t\t * consider a maximum of two objects nearby for the\n-\t\t * abbreviation length.\n-\t\t */\n-\n-\t\tif (!match) {\n-\t\t\tif (nth_midxed_object_oid(&found_oid, m, first))\n-\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t\t} else if (first < num - 1) {\n-\t\t\tif (nth_midxed_object_oid(&found_oid, m, first + 1))\n-\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t\t}\n-\t\tif (first > 0) {\n-\t\t\tif (nth_midxed_object_oid(&found_oid, m, first - 1))\n-\t\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t\t}\n-\t}\n-\n-\t*out = len;\n-}\n-\n-static void find_abbrev_len_for_pack(struct packed_git *p,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     unsigned min_len,\n-\t\t\t\t     unsigned *out)\n-{\n-\tint match;\n-\tuint32_t num, first = 0;\n-\tstruct object_id found_oid;\n-\tunsigned len = min_len;\n-\n-\tnum = p->num_objects;\n-\tmatch = bsearch_pack(oid, p, &first);\n-\n-\t/*\n-\t * first is now the position in the packfile where we would insert\n-\t * the object ID if it does not exist (or the position of mad->hash if\n-\t * it does exist). Hence, we consider a maximum of two objects\n-\t * nearby for the abbreviation length.\n-\t */\n-\tif (!match) {\n-\t\tif (!nth_packed_object_id(&found_oid, p, first))\n-\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t} else if (first < num - 1) {\n-\t\tif (!nth_packed_object_id(&found_oid, p, first + 1))\n-\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t}\n-\tif (first > 0) {\n-\t\tif (!nth_packed_object_id(&found_oid, p, first - 1))\n-\t\t\textend_abbrev_len(&found_oid, oid, &len);\n-\t}\n-\n-\t*out = len;\n-}\n-\n-int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t   unsigned min_len,\n-\t\t\t\t   unsigned *out)\n-{\n-\tstruct packfile_list_entry *e;\n-\tstruct multi_pack_index *m;\n-\n-\tm = get_multi_pack_index(&store->files->base);\n-\tif (m)\n-\t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n-\n-\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n-\t\tif (e->pack->multi_pack_index)\n-\t\t\tcontinue;\n-\t\tif (open_pack_index(e->pack) || !e->pack->num_objects)\n-\t\t\tcontinue;\n-\n-\t\tfind_abbrev_len_for_pack(e->pack, oid, min_len, &min_len);\n-\t}\n-\n-\t*out = min_len;\n-\treturn 0;\n-}\n-\n struct add_promisor_object_data {\n \tstruct repository *repo;\n \tstruct oidset *set;\ndiff --git a/packfile.h b/packfile.h\nindex 0613fd3c63..79324e4010 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -217,11 +217,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn, void *data,\n \t\t\t    enum odb_for_each_object_flags flags);\n \n-int packfile_store_find_abbrev_len(struct odb_source_packed *store,\n-\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t   unsigned min_len,\n-\t\t\t\t   unsigned *out);\n-\n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\n #define PACKDIR_FILE_IDX 2\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545725","messageId":"20260617-pks-odb-source-packed-v3-14-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 14/17] odb/source-packed: wire up `freshen_object()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:57Z","receivedAt":"2026-06-17T06:40:32Z","isPatch":true,"body":"Move `packfile_store_freshen_object()` and from \"packfile.c\" into\n\"odb/source-packed.c\" and wire it up as the `freshen_object()` callback\nof the \"packed\" source.\n\nNote that this removes the last external caller of `find_pack_entry()`\nfrom \"packfile.c\", which means that we can now make this function\nstatic.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 26 +++++++++++++++++++++++---\n odb/source-packed.h |  6 ------\n packfile.c          | 16 ----------------\n packfile.h          |  3 ---\n 5 files changed, 24 insertions(+), 29 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 8ad782dc7b..fa2e18e71b 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -152,7 +152,7 @@ static int odb_source_files_freshen_object(struct odb_source *source,\n \t\t\t\t\t   const struct object_id *oid)\n {\n \tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tif (packfile_store_freshen_object(files->packed, oid) ||\n+\tif (odb_source_freshen_object(&files->packed->base, oid) ||\n \t    odb_source_freshen_object(&files->loose->base, oid))\n \t\treturn 1;\n \treturn 0;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex b801b62023..e40b52e445 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -9,9 +9,9 @@\n #include \"odb/streaming.h\"\n #include \"packfile.h\"\n \n-int find_pack_entry(struct odb_source_packed *store,\n-\t\t    const struct object_id *oid,\n-\t\t    struct pack_entry *e)\n+static int find_pack_entry(struct odb_source_packed *store,\n+\t\t\t   const struct object_id *oid,\n+\t\t\t   struct pack_entry *e)\n {\n \tstruct packfile_list_entry *l;\n \n@@ -482,6 +482,25 @@ static int odb_source_packed_find_abbrev_len(struct odb_source *source,\n \treturn 0;\n }\n \n+static int odb_source_packed_freshen_object(struct odb_source *source,\n+\t\t\t\t\t    const struct object_id *oid)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tstruct pack_entry e;\n+\n+\tif (!find_pack_entry(packed, oid, &e))\n+\t\treturn 0;\n+\tif (e.p->is_cruft)\n+\t\treturn 0;\n+\tif (e.p->freshened)\n+\t\treturn 1;\n+\tif (utime(e.p->pack_name, NULL))\n+\t\treturn 0;\n+\te.p->freshened = 1;\n+\n+\treturn 1;\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -695,6 +714,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\n \tpacked->base.count_objects = odb_source_packed_count_objects;\n \tpacked->base.find_abbrev_len = odb_source_packed_find_abbrev_len;\n+\tpacked->base.freshen_object = odb_source_packed_freshen_object;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex f430ee0b94..9d4796261a 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -90,10 +90,4 @@ static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_so\n  */\n void odb_source_packed_prepare(struct odb_source_packed *source);\n \n-struct pack_entry;\n-\n-int find_pack_entry(struct odb_source_packed *store,\n-\t\t    const struct object_id *oid,\n-\t\t    struct pack_entry *e);\n-\n #endif\ndiff --git a/packfile.c b/packfile.c\nindex 7f84094e53..a577275d4f 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1892,22 +1892,6 @@ int packfile_fill_entry(struct packed_git *p,\n \treturn 1;\n }\n \n-int packfile_store_freshen_object(struct odb_source_packed *store,\n-\t\t\t\t  const struct object_id *oid)\n-{\n-\tstruct pack_entry e;\n-\tif (!find_pack_entry(store, oid, &e))\n-\t\treturn 0;\n-\tif (e.p->is_cruft)\n-\t\treturn 0;\n-\tif (e.p->freshened)\n-\t\treturn 1;\n-\tif (utime(e.p->pack_name, NULL))\n-\t\treturn 0;\n-\te.p->freshened = 1;\n-\treturn 1;\n-}\n-\n static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store,\n \t\t\t\t\t     unsigned flags)\n {\ndiff --git a/packfile.h b/packfile.h\nindex 79324e4010..71a71017ee 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -132,9 +132,6 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *\n struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \t\t\t\t\t    const char *idx_path, int local);\n \n-int packfile_store_freshen_object(struct odb_source_packed *store,\n-\t\t\t\t  const struct object_id *oid);\n-\n enum kept_pack_type {\n \tKEPT_PACK_ON_DISK = (1 << 0),\n \tKEPT_PACK_IN_CORE = (1 << 1),\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545726","messageId":"20260617-pks-odb-source-packed-v3-15-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 15/17] odb/source-packed: stub out remaining functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:58Z","receivedAt":"2026-06-17T06:40:35Z","isPatch":true,"body":"Stub out remaining functions that we either don't need or that are\nbasically no-ops.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-packed.c | 42 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 42 insertions(+)\n\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex e40b52e445..08a2de9bc5 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -501,6 +501,43 @@ static int odb_source_packed_freshen_object(struct odb_source *source,\n \treturn 1;\n }\n \n+static int odb_source_packed_write_object(struct odb_source *source UNUSED,\n+\t\t\t\t\t  const void *buf UNUSED,\n+\t\t\t\t\t  unsigned long len UNUSED,\n+\t\t\t\t\t  enum object_type type UNUSED,\n+\t\t\t\t\t  struct object_id *oid UNUSED,\n+\t\t\t\t\t  struct object_id *compat_oid UNUSED,\n+\t\t\t\t\t  unsigned flags UNUSED)\n+{\n+\treturn error(\"packed backend cannot write objects\");\n+}\n+\n+static int odb_source_packed_write_object_stream(struct odb_source *source UNUSED,\n+\t\t\t\t\t\t struct odb_write_stream *stream UNUSED,\n+\t\t\t\t\t\t size_t len UNUSED,\n+\t\t\t\t\t\t struct object_id *oid UNUSED)\n+{\n+\treturn error(\"packed backend cannot write object streams\");\n+}\n+\n+static int odb_source_packed_begin_transaction(struct odb_source *source UNUSED,\n+\t\t\t\t\t       struct odb_transaction **out UNUSED)\n+{\n+\treturn error(\"packed backend cannot begin transactions\");\n+}\n+\n+static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,\n+\t\t\t\t\t     struct strvec *out UNUSED)\n+{\n+\treturn 0;\n+}\n+\n+static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,\n+\t\t\t\t\t     const char *alternate UNUSED)\n+{\n+\treturn error(\"packed backend cannot write alternates\");\n+}\n+\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -715,6 +752,11 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.count_objects = odb_source_packed_count_objects;\n \tpacked->base.find_abbrev_len = odb_source_packed_find_abbrev_len;\n \tpacked->base.freshen_object = odb_source_packed_freshen_object;\n+\tpacked->base.write_object = odb_source_packed_write_object;\n+\tpacked->base.write_object_stream = odb_source_packed_write_object_stream;\n+\tpacked->base.begin_transaction = odb_source_packed_begin_transaction;\n+\tpacked->base.read_alternates = odb_source_packed_read_alternates;\n+\tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n \tif (!is_absolute_path(parent->base.path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545727","messageId":"20260617-pks-odb-source-packed-v3-16-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 16/17] midx: refactor interfaces to work on \"packed\" source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:39:59Z","receivedAt":"2026-06-17T06:40:38Z","isPatch":true,"body":"Our interfaces used to interact with MIDXs all work on top of the\ngeneric `struct odb_source`. This doesn't make much sense though: a MIDX\nis strictly tied to the \"packed\" source, so passing in a generic source\ngives the false sense that it may also work with a different type of\nsource.\n\nFix this conceptual weirdness and instead require the caller to pass in\na \"packed\" source explicitly. This also makes the next commit easier to\nimplement, where we drop the pointer to the \"files\" source in the\n\"packed\" source.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/multi-pack-index.c |  29 +++++------\n builtin/pack-objects.c     |   3 +-\n builtin/repack.c           |   8 ++-\n midx-write.c               |  34 ++++++-------\n midx.c                     | 118 ++++++++++++++++++++++-----------------------\n midx.h                     |  30 ++++++------\n odb/source-packed.c        |  12 ++---\n pack-bitmap.c              |   8 +--\n pack-revindex.c            |   6 +--\n repack-geometry.c          |   3 +-\n repack-midx.c              |   9 ++--\n repack.c                   |   6 +--\n t/helper/test-read-midx.c  |   7 ++-\n 13 files changed, 144 insertions(+), 129 deletions(-)\n\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 00ffb36394..6e73c85cde 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -10,6 +10,7 @@\n #include \"trace2.h\"\n #include \"odb.h\"\n #include \"odb/source.h\"\n+#include \"odb/source-files.h\"\n #include \"replace-object.h\"\n #include \"repository.h\"\n \n@@ -85,12 +86,12 @@ static int parse_object_dir(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n-static struct odb_source *handle_object_dir_option(struct repository *repo)\n+static struct odb_source_files *handle_object_dir_option(struct repository *repo)\n {\n \tstruct odb_source *source = odb_find_source(repo->objects, opts.object_dir);\n \tif (!source)\n \t\tsource = odb_add_to_alternates_memory(repo->objects, opts.object_dir);\n-\treturn source;\n+\treturn odb_source_files_downcast(source);\n }\n \n static struct option common_opts[] = {\n@@ -167,7 +168,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \t\t\t     N_(\"refs snapshot for selecting bitmap commits\")),\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \tint ret;\n \n \topts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;\n@@ -211,7 +212,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \n \t\tread_packs_from_stdin(&packs);\n \n-\t\tret = write_midx_file_only(source, &packs,\n+\t\tret = write_midx_file_only(source->packed, &packs,\n \t\t\t\t\t   opts.preferred_pack,\n \t\t\t\t\t   opts.refs_snapshot,\n \t\t\t\t\t   opts.incremental_base, opts.flags);\n@@ -223,7 +224,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \n \t}\n \n-\tret = write_midx_file(source, opts.preferred_pack,\n+\tret = write_midx_file(source->packed, opts.preferred_pack,\n \t\t\t      opts.refs_snapshot, opts.flags);\n \n \tfree(opts.refs_snapshot);\n@@ -237,7 +238,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,\n \tstruct multi_pack_index *m, *cur;\n \tstruct multi_pack_index *from_midx = NULL;\n \tstruct multi_pack_index *to_midx = NULL;\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \tint ret;\n \n \tstruct option *options;\n@@ -282,7 +283,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\tm = get_multi_pack_index(source);\n+\tm = get_multi_pack_index(source->packed);\n \n \tfor (cur = m; cur && !(from_midx && to_midx); cur = cur->base_midx) {\n \t\tconst char *midx_csum = midx_get_checksum_hex(cur);\n@@ -305,7 +306,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv,\n \t\t\tdie(_(\"MIDX %s must be an ancestor of %s\"), argv[0], argv[1]);\n \t}\n \n-\tret = write_midx_file_compact(source, from_midx, to_midx,\n+\tret = write_midx_file_compact(source->packed, from_midx, to_midx,\n \t\t\t\t      opts.incremental_base, opts.flags);\n \n \treturn ret;\n@@ -319,7 +320,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,\n \tstatic struct option builtin_multi_pack_index_verify_options[] = {\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \n \toptions = add_common_options(builtin_multi_pack_index_verify_options);\n \n@@ -337,7 +338,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\treturn verify_midx_file(source, opts.flags);\n+\treturn verify_midx_file(source->packed, opts.flags);\n }\n \n static int cmd_multi_pack_index_expire(int argc, const char **argv,\n@@ -348,7 +349,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,\n \tstatic struct option builtin_multi_pack_index_expire_options[] = {\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \n \toptions = add_common_options(builtin_multi_pack_index_expire_options);\n \n@@ -366,7 +367,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\treturn expire_midx_packs(source, opts.flags);\n+\treturn expire_midx_packs(source->packed, opts.flags);\n }\n \n static int cmd_multi_pack_index_repack(int argc, const char **argv,\n@@ -379,7 +380,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,\n \t\t  N_(\"during repack, collect pack-files of smaller size into a batch that is larger than this size\")),\n \t\tOPT_END(),\n \t};\n-\tstruct odb_source *source;\n+\tstruct odb_source_files *source;\n \n \toptions = add_common_options(builtin_multi_pack_index_repack_options);\n \n@@ -398,7 +399,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,\n \n \tFREE_AND_NULL(options);\n \n-\treturn midx_repack(source, (size_t)opts.batch_size, opts.flags);\n+\treturn midx_repack(source->packed, (size_t)opts.batch_size, opts.flags);\n }\n \n int cmd_multi_pack_index(int argc,\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 5e94805478..424c92cc29 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -1775,7 +1775,8 @@ static int want_object_in_pack_mtime(const struct object_id *oid,\n \todb_prepare_alternates(the_repository->objects);\n \n \tfor (source = the_repository->objects->sources; source; source = source->next) {\n-\t\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \t\tstruct pack_entry e;\n \n \t\tif (m && fill_midx_entry(m, oid, &e)) {\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 1524a9c13a..47966a686b 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -458,6 +458,8 @@ int cmd_repack(int argc,\n \t}\n \n \tif (!names.nr) {\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(existing.source);\n+\n \t\tif (!po_args.quiet)\n \t\t\tprintf_ln(_(\"Nothing new to pack.\"));\n \t\t/*\n@@ -473,7 +475,7 @@ int cmd_repack(int argc,\n \t\t * midx_has_unknown_packs() will make the decision for\n \t\t * us.\n \t\t */\n-\t\tif (!get_multi_pack_index(existing.source))\n+\t\tif (!get_multi_pack_index(files->packed))\n \t\t\tmidx_must_contain_cruft = 1;\n \t}\n \n@@ -626,10 +628,12 @@ int cmd_repack(int argc,\n \t\tupdate_server_info(repo, 0);\n \n \tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) {\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(existing.source);\n \t\tunsigned flags = 0;\n+\n \t\tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0))\n \t\t\tflags |= MIDX_WRITE_INCREMENTAL;\n-\t\twrite_midx_file(existing.source, NULL, NULL, flags);\n+\t\twrite_midx_file(files->packed, NULL, NULL, flags);\n \t}\n \n cleanup:\ndiff --git a/midx-write.c b/midx-write.c\nindex 561e9eedc0..7cafc49fb8 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -25,9 +25,9 @@\n #define NO_PREFERRED_PACK (~((uint32_t)0))\n \n extern int midx_checksum_valid(struct multi_pack_index *m);\n-extern void clear_midx_files_ext(struct odb_source *source, const char *ext,\n+extern void clear_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t\t const char *keep_hash);\n-extern void clear_incremental_midx_files_ext(struct odb_source *source,\n+extern void clear_incremental_midx_files_ext(struct odb_source_packed *source,\n \t\t\t\t\t     const char *ext,\n \t\t\t\t\t     const struct strvec *keep_hashes);\n extern int cmp_idx_or_pack_name(const char *idx_or_pack_name,\n@@ -119,7 +119,7 @@ struct write_midx_context {\n \tstruct string_list *to_include;\n \n \tstruct repository *repo;\n-\tstruct odb_source *source;\n+\tstruct odb_source_packed *source;\n };\n \n static uint32_t midx_pack_perm(struct write_midx_context *ctx,\n@@ -1107,7 +1107,7 @@ static int link_midx_to_chain(struct multi_pack_index *m)\n \treturn ret;\n }\n \n-static void clear_midx_files(struct odb_source *source,\n+static void clear_midx_files(struct odb_source_packed *source,\n \t\t\t     const struct strvec *hashes, unsigned incremental)\n {\n \t/*\n@@ -1237,7 +1237,7 @@ static int midx_hashcmp(const struct multi_pack_index *a,\n }\n \n struct write_midx_opts {\n-\tstruct odb_source *source; /* non-optional */\n+\tstruct odb_source_packed *source; /* non-optional */\n \n \tstruct string_list *packs_to_include;\n \tstruct string_list *packs_to_drop;\n@@ -1253,7 +1253,7 @@ struct write_midx_opts {\n \n static int write_midx_internal(struct write_midx_opts *opts)\n {\n-\tstruct repository *r = opts->source->odb->repo;\n+\tstruct repository *r = opts->source->base.odb->repo;\n \tstruct strbuf midx_name = STRBUF_INIT;\n \tunsigned char midx_hash[GIT_MAX_RAWSZ];\n \tuint32_t start_pack;\n@@ -1301,7 +1301,7 @@ static int write_midx_internal(struct write_midx_opts *opts)\n \tif (ctx.incremental)\n \t\tstrbuf_addf(&midx_name,\n \t\t\t    \"%s/pack/multi-pack-index.d/tmp_midx_XXXXXX\",\n-\t\t\t    opts->source->path);\n+\t\t\t    opts->source->base.path);\n \telse\n \t\tget_midx_filename(opts->source, &midx_name);\n \tif (safe_create_leading_directories(r, midx_name.buf))\n@@ -1396,7 +1396,7 @@ static int write_midx_internal(struct write_midx_opts *opts)\n \t\tfill_packs_from_midx_range(&ctx, bitmap_order);\n \t} else {\n \t\tctx.to_include = opts->packs_to_include;\n-\t\tfor_each_file_in_pack_dir(opts->source->path, add_pack_to_midx, &ctx);\n+\t\tfor_each_file_in_pack_dir(opts->source->base.path, add_pack_to_midx, &ctx);\n \t}\n \tstop_progress(&ctx.progress);\n \n@@ -1847,7 +1847,7 @@ static int write_midx_internal(struct write_midx_opts *opts)\n \treturn result;\n }\n \n-int write_midx_file(struct odb_source *source,\n+int write_midx_file(struct odb_source_packed *source,\n \t\t    const char *preferred_pack_name,\n \t\t    const char *refs_snapshot,\n \t\t    unsigned flags)\n@@ -1862,7 +1862,7 @@ int write_midx_file(struct odb_source *source,\n \treturn write_midx_internal(&opts);\n }\n \n-int write_midx_file_only(struct odb_source *source,\n+int write_midx_file_only(struct odb_source_packed *source,\n \t\t\t struct string_list *packs_to_include,\n \t\t\t const char *preferred_pack_name,\n \t\t\t const char *refs_snapshot,\n@@ -1881,7 +1881,7 @@ int write_midx_file_only(struct odb_source *source,\n \treturn write_midx_internal(&opts);\n }\n \n-int write_midx_file_compact(struct odb_source *source,\n+int write_midx_file_compact(struct odb_source_packed *source,\n \t\t\t    struct multi_pack_index *from,\n \t\t\t    struct multi_pack_index *to,\n \t\t\t    const char *incremental_base,\n@@ -1898,7 +1898,7 @@ int write_midx_file_compact(struct odb_source *source,\n \treturn write_midx_internal(&opts);\n }\n \n-int expire_midx_packs(struct odb_source *source, unsigned flags)\n+int expire_midx_packs(struct odb_source_packed *source, unsigned flags)\n {\n \tuint32_t i, *count, result = 0;\n \tstruct string_list packs_to_drop = STRING_LIST_INIT_DUP;\n@@ -1915,7 +1915,7 @@ int expire_midx_packs(struct odb_source *source, unsigned flags)\n \n \tif (flags & MIDX_PROGRESS)\n \t\tprogress = start_delayed_progress(\n-\t\t\t\t\t  source->odb->repo,\n+\t\t\t\t\t  source->base.odb->repo,\n \t\t\t\t\t  _(\"Counting referenced objects\"),\n \t\t\t\t\t  m->num_objects);\n \tfor (i = 0; i < m->num_objects; i++) {\n@@ -1927,7 +1927,7 @@ int expire_midx_packs(struct odb_source *source, unsigned flags)\n \n \tif (flags & MIDX_PROGRESS)\n \t\tprogress = start_delayed_progress(\n-\t\t\t\t\t  source->odb->repo,\n+\t\t\t\t\t  source->base.odb->repo,\n \t\t\t\t\t  _(\"Finding and deleting unreferenced packfiles\"),\n \t\t\t\t\t  m->num_packs);\n \tfor (i = 0; i < m->num_packs; i++) {\n@@ -2085,9 +2085,9 @@ static void fill_included_packs_batch(struct repository *r,\n \tfree(pack_info);\n }\n \n-int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags)\n+int midx_repack(struct odb_source_packed *source, size_t batch_size, unsigned flags)\n {\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \tint result = 0;\n \tuint32_t i, packs_to_repack = 0;\n \tunsigned char *include_pack;\n@@ -2131,7 +2131,7 @@ int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags)\n \n \tstrvec_push(&cmd.args, \"pack-objects\");\n \n-\tstrvec_pushf(&cmd.args, \"%s/pack/pack\", source->path);\n+\tstrvec_pushf(&cmd.args, \"%s/pack/pack\", source->base.path);\n \n \tif (delta_base_offset)\n \t\tstrvec_push(&cmd.args, \"--delta-base-offset\");\ndiff --git a/midx.c b/midx.c\nindex 00bbd137b2..cc6b94f9dd 100644\n--- a/midx.c\n+++ b/midx.c\n@@ -17,9 +17,9 @@\n #define MIDX_PACK_ERROR ((void *)(intptr_t)-1)\n \n int midx_checksum_valid(struct multi_pack_index *m);\n-void clear_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t  const char *keep_hash);\n-void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t\t      const struct strvec *keep_hashes);\n int cmp_idx_or_pack_name(const char *idx_or_pack_name,\n \t\t\t const char *idx_name);\n@@ -27,25 +27,25 @@ int cmp_idx_or_pack_name(const char *idx_or_pack_name,\n const char *midx_get_checksum_hex(const struct multi_pack_index *m)\n {\n \treturn hash_to_hex_algop(midx_get_checksum_hash(m),\n-\t\t\t\t m->source->odb->repo->hash_algo);\n+\t\t\t\t m->source->base.odb->repo->hash_algo);\n }\n \n const unsigned char *midx_get_checksum_hash(const struct multi_pack_index *m)\n {\n-\treturn m->data + m->data_len - m->source->odb->repo->hash_algo->rawsz;\n+\treturn m->data + m->data_len - m->source->base.odb->repo->hash_algo->rawsz;\n }\n \n-void get_midx_filename(struct odb_source *source, struct strbuf *out)\n+void get_midx_filename(struct odb_source_packed *source, struct strbuf *out)\n {\n \tget_midx_filename_ext(source, out, NULL, NULL);\n }\n \n-void get_midx_filename_ext(struct odb_source *source, struct strbuf *out,\n+void get_midx_filename_ext(struct odb_source_packed *source, struct strbuf *out,\n \t\t\t   const unsigned char *hash, const char *ext)\n {\n-\tstrbuf_addf(out, \"%s/pack/multi-pack-index\", source->path);\n+\tstrbuf_addf(out, \"%s/pack/multi-pack-index\", source->base.path);\n \tif (ext)\n-\t\tstrbuf_addf(out, \"-%s.%s\", hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext);\n+\t\tstrbuf_addf(out, \"-%s.%s\", hash_to_hex_algop(hash, source->base.odb->repo->hash_algo), ext);\n }\n \n static int midx_read_oid_fanout(const unsigned char *chunk_start,\n@@ -99,17 +99,16 @@ static int midx_read_object_offsets(const unsigned char *chunk_start,\n \treturn 0;\n }\n \n-struct multi_pack_index *get_multi_pack_index(struct odb_source *source)\n+struct multi_pack_index *get_multi_pack_index(struct odb_source_packed *source)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\todb_source_packed_prepare(files->packed);\n-\treturn files->packed->midx;\n+\todb_source_packed_prepare(source);\n+\treturn source->midx;\n }\n \n-static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *source,\n+static struct multi_pack_index *load_multi_pack_index_one(struct odb_source_packed *source,\n \t\t\t\t\t\t\t  const char *midx_name)\n {\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \tstruct multi_pack_index *m = NULL;\n \tint fd;\n \tstruct stat st;\n@@ -234,23 +233,23 @@ static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *sou\n \treturn NULL;\n }\n \n-void get_midx_chain_dirname(struct odb_source *source, struct strbuf *buf)\n+void get_midx_chain_dirname(struct odb_source_packed *source, struct strbuf *buf)\n {\n-\tstrbuf_addf(buf, \"%s/pack/multi-pack-index.d\", source->path);\n+\tstrbuf_addf(buf, \"%s/pack/multi-pack-index.d\", source->base.path);\n }\n \n-void get_midx_chain_filename(struct odb_source *source, struct strbuf *buf)\n+void get_midx_chain_filename(struct odb_source_packed *source, struct strbuf *buf)\n {\n \tget_midx_chain_dirname(source, buf);\n \tstrbuf_addstr(buf, \"/multi-pack-index-chain\");\n }\n \n-void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf,\n+void get_split_midx_filename_ext(struct odb_source_packed *source, struct strbuf *buf,\n \t\t\t\t const unsigned char *hash, const char *ext)\n {\n \tget_midx_chain_dirname(source, buf);\n \tstrbuf_addf(buf, \"/multi-pack-index-%s.%s\",\n-\t\t    hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext);\n+\t\t    hash_to_hex_algop(hash, source->base.odb->repo->hash_algo), ext);\n }\n \n static int open_multi_pack_index_chain(const struct git_hash_algo *hash_algo,\n@@ -306,11 +305,11 @@ static int add_midx_to_chain(struct multi_pack_index *midx,\n \treturn 1;\n }\n \n-static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source,\n+static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source_packed *source,\n \t\t\t\t\t\t      int fd, struct stat *st,\n \t\t\t\t\t\t      int *incomplete_chain)\n {\n-\tconst struct git_hash_algo *hash_algo = source->odb->repo->hash_algo;\n+\tconst struct git_hash_algo *hash_algo = source->base.odb->repo->hash_algo;\n \tstruct multi_pack_index *midx_chain = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint valid = 1;\n@@ -362,7 +361,7 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source,\n \treturn midx_chain;\n }\n \n-static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *source)\n+static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source_packed *source)\n {\n \tstruct strbuf chain_file = STRBUF_INIT;\n \tstruct stat st;\n@@ -370,7 +369,8 @@ static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *s\n \tstruct multi_pack_index *m = NULL;\n \n \tget_midx_chain_filename(source, &chain_file);\n-\tif (open_multi_pack_index_chain(source->odb->repo->hash_algo, chain_file.buf, &fd, &st)) {\n+\tif (open_multi_pack_index_chain(source->base.odb->repo->hash_algo,\n+\t\t\t\t\tchain_file.buf, &fd, &st)) {\n \t\tint incomplete;\n \t\t/* ownership of fd is taken over by load function */\n \t\tm = load_midx_chain_fd_st(source, fd, &st, &incomplete);\n@@ -380,7 +380,7 @@ static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *s\n \treturn m;\n }\n \n-struct multi_pack_index *load_multi_pack_index(struct odb_source *source)\n+struct multi_pack_index *load_multi_pack_index(struct odb_source_packed *source)\n {\n \tstruct strbuf midx_name = STRBUF_INIT;\n \tstruct multi_pack_index *m;\n@@ -456,7 +456,7 @@ static uint32_t midx_for_pack(struct multi_pack_index **_m,\n int prepare_midx_pack(struct multi_pack_index *m,\n \t\t      uint32_t pack_int_id)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(m->source);\n+\tstruct odb_source_packed *packed = m->source;\n \tstruct strbuf pack_name = STRBUF_INIT;\n \tstruct packed_git *p;\n \n@@ -467,10 +467,10 @@ int prepare_midx_pack(struct multi_pack_index *m,\n \tif (m->packs[pack_int_id])\n \t\treturn 0;\n \n-\tstrbuf_addf(&pack_name, \"%s/pack/%s\", files->base.path,\n+\tstrbuf_addf(&pack_name, \"%s/pack/%s\", packed->base.path,\n \t\t    m->pack_names[pack_int_id]);\n-\tp = packfile_store_load_pack(files->packed,\n-\t\t\t\t     pack_name.buf, files->base.local);\n+\tp = packfile_store_load_pack(packed,\n+\t\t\t\t     pack_name.buf, packed->base.local);\n \tstrbuf_release(&pack_name);\n \n \tif (!p) {\n@@ -523,7 +523,7 @@ int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m,\n {\n \tint ret = bsearch_hash(oid->hash, m->chunk_oid_fanout,\n \t\t\t       m->chunk_oid_lookup,\n-\t\t\t       m->source->odb->repo->hash_algo->rawsz,\n+\t\t\t       m->source->base.odb->repo->hash_algo->rawsz,\n \t\t\t       result);\n \tif (result)\n \t\t*result += m->num_objects_in_base;\n@@ -554,7 +554,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,\n \tn = midx_for_object(&m, n);\n \n \toidread(oid, m->chunk_oid_lookup + st_mult(m->hash_len, n),\n-\t\tm->source->odb->repo->hash_algo);\n+\t\tm->source->base.odb->repo->hash_algo);\n \treturn oid;\n }\n \n@@ -734,26 +734,25 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id)\n \treturn 0;\n }\n \n-int prepare_multi_pack_index_one(struct odb_source *source)\n+int prepare_multi_pack_index_one(struct odb_source_packed *source)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \n \tprepare_repo_settings(r);\n \tif (!r->settings.core_multi_pack_index)\n \t\treturn 0;\n \n-\tif (files->packed->midx)\n+\tif (source->midx)\n \t\treturn 1;\n \n-\tfiles->packed->midx = load_multi_pack_index(source);\n+\tsource->midx = load_multi_pack_index(source);\n \n-\treturn !!files->packed->midx;\n+\treturn !!source->midx;\n }\n \n int midx_checksum_valid(struct multi_pack_index *m)\n {\n-\treturn hashfile_checksum_valid(m->source->odb->repo->hash_algo,\n+\treturn hashfile_checksum_valid(m->source->base.odb->repo->hash_algo,\n \t\t\t\t       m->data, m->data_len);\n }\n \n@@ -776,7 +775,7 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len UNUS\n \t\tdie_errno(_(\"failed to remove %s\"), full_path);\n }\n \n-void clear_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t  const char *keep_hash)\n {\n \tstruct clear_midx_data data = {\n@@ -793,12 +792,12 @@ void clear_midx_files_ext(struct odb_source *source, const char *ext,\n \t\tstrbuf_release(&buf);\n \t}\n \n-\tfor_each_file_in_pack_dir(source->path, clear_midx_file_ext, &data);\n+\tfor_each_file_in_pack_dir(source->base.path, clear_midx_file_ext, &data);\n \n \tstrset_clear(&data.keep);\n }\n \n-void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext,\n+void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext,\n \t\t\t\t      const struct strvec *keep_hashes)\n {\n \tstruct clear_midx_data data = {\n@@ -817,7 +816,7 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext\n \t\t}\n \t}\n \n-\tfor_each_file_in_pack_subdir(source->path, \"multi-pack-index.d\",\n+\tfor_each_file_in_pack_subdir(source->base.path, \"multi-pack-index.d\",\n \t\t\t\t     clear_midx_file_ext, &data);\n \n \tstrbuf_release(&buf);\n@@ -826,26 +825,28 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext\n \n void clear_midx_file(struct repository *r)\n {\n+\tstruct odb_source_files *files;\n \tstruct strbuf midx = STRBUF_INIT;\n \n-\tget_midx_filename(r->objects->sources, &midx);\n-\n \tif (r->objects) {\n \t\tstruct odb_source *source;\n \n \t\tfor (source = r->objects->sources; source; source = source->next) {\n-\t\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\t\tfiles = odb_source_files_downcast(source);\n \t\t\tif (files->packed->midx)\n \t\t\t\tclose_midx(files->packed->midx);\n \t\t\tfiles->packed->midx = NULL;\n \t\t}\n \t}\n \n+\tfiles = odb_source_files_downcast(r->objects->sources);\n+\tget_midx_filename(files->packed, &midx);\n+\n \tif (remove_path(midx.buf))\n \t\tdie(_(\"failed to clear multi-pack-index at %s\"), midx.buf);\n \n-\tclear_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP, NULL);\n-\tclear_midx_files_ext(r->objects->sources, MIDX_EXT_REV, NULL);\n+\tclear_midx_files_ext(files->packed, MIDX_EXT_BITMAP, NULL);\n+\tclear_midx_files_ext(files->packed, MIDX_EXT_REV, NULL);\n \n \tstrbuf_release(&midx);\n }\n@@ -853,28 +854,27 @@ void clear_midx_file(struct repository *r)\n void clear_incremental_midx_files(struct repository *r,\n \t\t\t\t  const struct strvec *keep_hashes)\n {\n-\tstruct odb_source *source = r->objects->sources;\n+\tstruct odb_source_files *files;\n+\tstruct odb_source *source;\n \tstruct strbuf chain = STRBUF_INIT;\n \n-\tget_midx_chain_filename(source, &chain);\n-\n-\tfor (; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\tfor (source = r->objects->sources; source; source = source->next) {\n+\t\tfiles = odb_source_files_downcast(source);\n \t\tif (files->packed->midx)\n \t\t\tclose_midx(files->packed->midx);\n \t\tfiles->packed->midx = NULL;\n \t}\n \n+\tfiles = odb_source_files_downcast(r->objects->sources);\n+\tget_midx_chain_filename(files->packed, &chain);\n+\n \tif (!keep_hashes && remove_path(chain.buf))\n \t\tdie(_(\"failed to clear multi-pack-index chain at %s\"),\n \t\t    chain.buf);\n \n-\tclear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP,\n-\t\t\t\t\t keep_hashes);\n-\tclear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_REV,\n-\t\t\t\t\t keep_hashes);\n-\tclear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_MIDX,\n-\t\t\t\t\t keep_hashes);\n+\tclear_incremental_midx_files_ext(files->packed, MIDX_EXT_BITMAP, keep_hashes);\n+\tclear_incremental_midx_files_ext(files->packed, MIDX_EXT_REV, keep_hashes);\n+\tclear_incremental_midx_files_ext(files->packed, MIDX_EXT_MIDX, keep_hashes);\n \n \tstrbuf_release(&chain);\n }\n@@ -918,9 +918,9 @@ static int compare_pair_pos_vs_id(const void *_a, const void *_b)\n \t\t\tdisplay_progress(progress, _n); \\\n \t} while (0)\n \n-int verify_midx_file(struct odb_source *source, unsigned flags)\n+int verify_midx_file(struct odb_source_packed *source, unsigned flags)\n {\n-\tstruct repository *r = source->odb->repo;\n+\tstruct repository *r = source->base.odb->repo;\n \tstruct pair_pos_vs_id *pairs = NULL;\n \tuint32_t i;\n \tstruct progress *progress = NULL;\ndiff --git a/midx.h b/midx.h\nindex 63853a03a4..939c18e588 100644\n--- a/midx.h\n+++ b/midx.h\n@@ -37,7 +37,7 @@ struct strvec;\n \t\"GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL\"\n \n struct multi_pack_index {\n-\tstruct odb_source *source;\n+\tstruct odb_source_packed *source;\n \n \tconst unsigned char *data;\n \tsize_t data_len;\n@@ -92,16 +92,16 @@ struct multi_pack_index {\n \n const char *midx_get_checksum_hex(const struct multi_pack_index *m) /* static buffer */;\n const unsigned char *midx_get_checksum_hash(const struct multi_pack_index *m);\n-void get_midx_filename(struct odb_source *source, struct strbuf *out);\n-void get_midx_filename_ext(struct odb_source *source, struct strbuf *out,\n+void get_midx_filename(struct odb_source_packed *source, struct strbuf *out);\n+void get_midx_filename_ext(struct odb_source_packed *source, struct strbuf *out,\n \t\t\t   const unsigned char *hash, const char *ext);\n-void get_midx_chain_dirname(struct odb_source *source, struct strbuf *out);\n-void get_midx_chain_filename(struct odb_source *source, struct strbuf *out);\n-void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf,\n+void get_midx_chain_dirname(struct odb_source_packed *source, struct strbuf *out);\n+void get_midx_chain_filename(struct odb_source_packed *source, struct strbuf *out);\n+void get_split_midx_filename_ext(struct odb_source_packed *source, struct strbuf *buf,\n \t\t\t\t const unsigned char *hash, const char *ext);\n \n-struct multi_pack_index *get_multi_pack_index(struct odb_source *source);\n-struct multi_pack_index *load_multi_pack_index(struct odb_source *source);\n+struct multi_pack_index *get_multi_pack_index(struct odb_source_packed *source);\n+struct multi_pack_index *load_multi_pack_index(struct odb_source_packed *source);\n int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id);\n struct packed_git *nth_midxed_pack(struct multi_pack_index *m,\n \t\t\t\t   uint32_t pack_int_id);\n@@ -123,22 +123,22 @@ int midx_contains_pack(struct multi_pack_index *m,\n int midx_layer_contains_pack(struct multi_pack_index *m,\n \t\t\t     const char *idx_or_pack_name);\n int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id);\n-int prepare_multi_pack_index_one(struct odb_source *source);\n+int prepare_multi_pack_index_one(struct odb_source_packed *source);\n \n /*\n  * Variant of write_midx_file which writes a MIDX containing only the packs\n  * specified in packs_to_include.\n  */\n-int write_midx_file(struct odb_source *source,\n+int write_midx_file(struct odb_source_packed *source,\n \t\t    const char *preferred_pack_name, const char *refs_snapshot,\n \t\t    unsigned flags);\n-int write_midx_file_only(struct odb_source *source,\n+int write_midx_file_only(struct odb_source_packed *source,\n \t\t\t struct string_list *packs_to_include,\n \t\t\t const char *preferred_pack_name,\n \t\t\t const char *refs_snapshot,\n \t\t\t const char *incremental_base,\n \t\t\t unsigned flags);\n-int write_midx_file_compact(struct odb_source *source,\n+int write_midx_file_compact(struct odb_source_packed *source,\n \t\t\t    struct multi_pack_index *from,\n \t\t\t    struct multi_pack_index *to,\n \t\t\t    const char *incremental_base,\n@@ -146,9 +146,9 @@ int write_midx_file_compact(struct odb_source *source,\n void clear_midx_file(struct repository *r);\n void clear_incremental_midx_files(struct repository *r,\n \t\t\t\t  const struct strvec *keep_hashes);\n-int verify_midx_file(struct odb_source *source, unsigned flags);\n-int expire_midx_packs(struct odb_source *source, unsigned flags);\n-int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags);\n+int verify_midx_file(struct odb_source_packed *source, unsigned flags);\n+int expire_midx_packs(struct odb_source_packed *source, unsigned flags);\n+int midx_repack(struct odb_source_packed *source, size_t batch_size, unsigned flags);\n \n void close_midx(struct multi_pack_index *m);\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 08a2de9bc5..d513b3efc3 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -136,8 +136,8 @@ static int for_each_prefixed_object_in_midx(\n \n \tfor (; m; m = m->base_midx) {\n \t\tuint32_t num, i, first = 0;\n-\t\tint len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ?\n-\t\t\tm->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n+\t\tint len = opts->prefix_hex_len > m->source->base.odb->repo->hash_algo->hexsz ?\n+\t\t\tm->source->base.odb->repo->hash_algo->hexsz : opts->prefix_hex_len;\n \n \t\tif (!m->num_objects)\n \t\t\tcontinue;\n@@ -249,7 +249,7 @@ static int odb_source_packed_for_each_prefixed_object(\n \n \tstore->skip_mru_updates = true;\n \n-\tm = get_multi_pack_index(&store->files->base);\n+\tm = get_multi_pack_index(store);\n \tif (m) {\n \t\tret = for_each_prefixed_object_in_midx(store, m, opts, data);\n \t\tif (ret)\n@@ -348,7 +348,7 @@ static int odb_source_packed_count_objects(struct odb_source *source,\n \tunsigned long count = 0;\n \tint ret;\n \n-\tm = get_multi_pack_index(&packed->files->base);\n+\tm = get_multi_pack_index(packed);\n \tif (m)\n \t\tcount += m->num_objects + m->num_objects_in_base;\n \n@@ -465,7 +465,7 @@ static int odb_source_packed_find_abbrev_len(struct odb_source *source,\n \tstruct packfile_list_entry *e;\n \tstruct multi_pack_index *m;\n \n-\tm = get_multi_pack_index(&packed->files->base);\n+\tm = get_multi_pack_index(packed);\n \tif (m)\n \t\tfind_abbrev_len_for_midx(m, oid, min_len, &min_len);\n \n@@ -674,7 +674,7 @@ void odb_source_packed_prepare(struct odb_source_packed *source)\n \tif (source->initialized)\n \t\treturn;\n \n-\tprepare_multi_pack_index_one(&source->files->base);\n+\tprepare_multi_pack_index_one(source);\n \tprepare_packed_git_one(&source->files->base);\n \n \tsort_packs(&source->packs.head, sort_pack);\ndiff --git a/pack-bitmap.c b/pack-bitmap.c\nindex f9af8a96bd..6bfcbc8ce6 100644\n--- a/pack-bitmap.c\n+++ b/pack-bitmap.c\n@@ -238,7 +238,7 @@ static uint32_t bitmap_name_hash(struct bitmap_index *index, uint32_t pos)\n static struct repository *bitmap_repo(struct bitmap_index *bitmap_git)\n {\n \tif (bitmap_is_midx(bitmap_git))\n-\t\treturn bitmap_git->midx->source->odb->repo;\n+\t\treturn bitmap_git->midx->source->base.odb->repo;\n \treturn bitmap_git->pack->repo;\n }\n \n@@ -711,7 +711,8 @@ static int open_midx_bitmap(struct repository *r,\n \n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct multi_pack_index *midx = get_multi_pack_index(source);\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct multi_pack_index *midx = get_multi_pack_index(files->packed);\n \t\tif (midx && !open_midx_bitmap_1(bitmap_git, midx))\n \t\t\tret = 0;\n \t}\n@@ -3399,7 +3400,8 @@ int verify_bitmap_files(struct repository *r)\n \n \todb_prepare_alternates(r->objects);\n \tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \t\tchar *midx_bitmap_name;\n \n \t\tif (!m)\ndiff --git a/pack-revindex.c b/pack-revindex.c\nindex 1b67863606..62387ae632 100644\n--- a/pack-revindex.c\n+++ b/pack-revindex.c\n@@ -383,13 +383,13 @@ int load_midx_revindex(struct multi_pack_index *m)\n \t\t * not want to accidentally call munmap() in the middle of the\n \t\t * MIDX.\n \t\t */\n-\t\ttrace2_data_string(\"load_midx_revindex\", m->source->odb->repo,\n+\t\ttrace2_data_string(\"load_midx_revindex\", m->source->base.odb->repo,\n \t\t\t\t   \"source\", \"midx\");\n \t\tm->revindex_data = (const uint32_t *)m->chunk_revindex;\n \t\treturn 0;\n \t}\n \n-\ttrace2_data_string(\"load_midx_revindex\", m->source->odb->repo,\n+\ttrace2_data_string(\"load_midx_revindex\", m->source->base.odb->repo,\n \t\t\t   \"source\", \"rev\");\n \n \tif (m->has_chain)\n@@ -401,7 +401,7 @@ int load_midx_revindex(struct multi_pack_index *m)\n \t\t\t\t      midx_get_checksum_hash(m),\n \t\t\t\t      MIDX_EXT_REV);\n \n-\tret = load_revindex_from_disk(m->source->odb->repo->hash_algo,\n+\tret = load_revindex_from_disk(m->source->base.odb->repo->hash_algo,\n \t\t\t\t      revindex_name.buf,\n \t\t\t\t      m->num_objects,\n \t\t\t\t      &m->revindex_map,\ndiff --git a/repack-geometry.c b/repack-geometry.c\nindex 2064683dcf..15b3412950 100644\n--- a/repack-geometry.c\n+++ b/repack-geometry.c\n@@ -32,7 +32,8 @@ void pack_geometry_init(struct pack_geometry *geometry,\n {\n \tstruct packed_git *p;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct multi_pack_index *m = get_multi_pack_index(existing->source);\n+\tstruct odb_source_files *files = odb_source_files_downcast(existing->source);\n+\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \n \trepo_for_each_pack(existing->repo, p) {\n \t\tif (geometry->midx_layer_threshold_set && m &&\ndiff --git a/repack-midx.c b/repack-midx.c\nindex b6b1de7180..7c7c3620e5 100644\n--- a/repack-midx.c\n+++ b/repack-midx.c\n@@ -557,13 +557,14 @@ static void repack_make_midx_append_plan(struct repack_write_midx_opts *opts,\n \t\t\t\t\t struct midx_compaction_step **steps_p,\n \t\t\t\t\t size_t *steps_nr_p)\n {\n+\tstruct odb_source_files *files = odb_source_files_downcast(opts->existing->source);\n \tstruct multi_pack_index *m;\n \tstruct midx_compaction_step *steps = NULL;\n \tstruct midx_compaction_step *step;\n \tsize_t steps_nr = 0, steps_alloc = 0;\n \n \todb_reprepare(opts->existing->repo->objects);\n-\tm = get_multi_pack_index(opts->existing->source);\n+\tm = get_multi_pack_index(files->packed);\n \n \tif (opts->names->nr) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n@@ -606,6 +607,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,\n \t\t\t\t\t    struct midx_compaction_step **steps_p,\n \t\t\t\t\t    size_t *steps_nr_p)\n {\n+\tstruct odb_source_files *files = odb_source_files_downcast(opts->existing->source);\n \tstruct multi_pack_index *m;\n \tstruct midx_compaction_step *steps = NULL;\n \tstruct midx_compaction_step step = { 0 };\n@@ -618,7 +620,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,\n \t\t\t    opts->existing->repo);\n \n \todb_reprepare(opts->existing->repo->objects);\n-\tm = get_multi_pack_index(opts->existing->source);\n+\tm = get_multi_pack_index(files->packed);\n \n \tfor (i = 0; m && i < m->num_packs + m->num_packs_in_base; i++) {\n \t\tif (prepare_midx_pack(m, i)) {\n@@ -938,6 +940,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts,\n \n static int write_midx_incremental(struct repack_write_midx_opts *opts)\n {\n+\tstruct odb_source_files *files = odb_source_files_downcast(opts->existing->source);\n \tstruct midx_compaction_step *steps = NULL;\n \tstruct strbuf lock_name = STRBUF_INIT;\n \tstruct lock_file lf;\n@@ -946,7 +949,7 @@ static int write_midx_incremental(struct repack_write_midx_opts *opts)\n \tsize_t i;\n \tint ret = 0;\n \n-\tget_midx_chain_filename(opts->existing->source, &lock_name);\n+\tget_midx_chain_filename(files->packed, &lock_name);\n \tif (safe_create_leading_directories(opts->existing->repo,\n \t\t\t\t\t    lock_name.buf))\n \t\tdie_errno(_(\"unable to create leading directories of %s\"),\ndiff --git a/repack.c b/repack.c\nindex 571dabb665..d2aa58e134 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -59,10 +59,10 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \t\t\t\t  bool wrote_incremental_midx)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct odb_source *source = repo->objects->sources;\n-\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\tstruct odb_source_files *files = odb_source_files_downcast(repo->objects->sources);\n+\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n \tstrbuf_addf(&buf, \"%s.pack\", base_name);\n-\tif (m && source->local && midx_contains_pack(m, buf.buf)) {\n+\tif (m && files->base.local && midx_contains_pack(m, buf.buf)) {\n \t\tclear_midx_file(repo);\n \t\tif (!wrote_incremental_midx)\n \t\t\tclear_incremental_midx_files(repo, NULL);\ndiff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c\nindex 790000fb26..fb16ec0176 100644\n--- a/t/helper/test-read-midx.c\n+++ b/t/helper/test-read-midx.c\n@@ -13,13 +13,16 @@\n \n static struct multi_pack_index *setup_midx(const char *object_dir)\n {\n+\tstruct odb_source_files *files;\n \tstruct odb_source *source;\n \tsetup_git_directory(the_repository);\n \tsource = odb_find_source(the_repository->objects, object_dir);\n \tif (!source)\n \t\tsource = odb_add_to_alternates_memory(the_repository->objects,\n \t\t\t\t\t\t      object_dir);\n-\treturn load_multi_pack_index(source);\n+\tfiles = odb_source_files_downcast(source);\n+\n+\treturn load_multi_pack_index(files->packed);\n }\n \n static int read_midx_file(const char *object_dir, const char *checksum,\n@@ -70,7 +73,7 @@ static int read_midx_file(const char *object_dir, const char *checksum,\n \tfor (i = 0; i < m->num_packs; i++)\n \t\tprintf(\"%s\\n\", m->pack_names[i]);\n \n-\tprintf(\"object-dir: %s\\n\", m->source->path);\n+\tprintf(\"object-dir: %s\\n\", m->source->base.path);\n \n \tif (show_objects) {\n \t\tstruct object_id oid;\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545728","messageId":"20260617-pks-odb-source-packed-v3-17-b5c7583cd795@pks.im","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"[PATCH v3 17/17] odb/source-packed: drop pointer to \"files\" parent source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T06:40:00Z","receivedAt":"2026-06-17T06:40:40Z","isPatch":true,"body":"Over the last commits we have turned the packfile store into a proper\nobject database source that can be used as a standalone backend. As\nsuch, it is no longer necessary to have it coupled to the \"files\" parent\nsource.\n\nRemove the pointer to the owning \"files\" source so that the \"packed\"\nsource can be used as a standalone entity.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c  |  2 +-\n odb/source-packed.c | 27 +++++++++++++--------------\n odb/source-packed.h |  7 ++++---\n packfile.c          |  2 +-\n 4 files changed, 19 insertions(+), 19 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex fa2e18e71b..3bc6419dd7 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tCALLOC_ARRAY(files, 1);\n \todb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local);\n \tfiles->loose = odb_source_loose_new(odb, path, local);\n-\tfiles->packed = odb_source_packed_new(files);\n+\tfiles->packed = odb_source_packed_new(odb, path, local);\n \n \tfiles->base.free = odb_source_files_free;\n \tfiles->base.close = odb_source_files_close;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex d513b3efc3..42c28fba0e 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -585,7 +585,7 @@ static void report_pack_garbage(struct string_list *list)\n }\n \n struct prepare_pack_data {\n-\tstruct odb_source *source;\n+\tstruct odb_source_packed *source;\n \tstruct string_list *garbage;\n };\n \n@@ -593,15 +593,14 @@ static void prepare_pack(const char *full_name, size_t full_name_len,\n \t\t\t const char *file_name, void *_data)\n {\n \tstruct prepare_pack_data *data = (struct prepare_pack_data *)_data;\n-\tstruct odb_source_files *files = odb_source_files_downcast(data->source);\n \tsize_t base_len = full_name_len;\n \n \tif (strip_suffix_mem(full_name, &base_len, \".idx\") &&\n-\t    !(files->packed->midx &&\n-\t      midx_contains_pack(files->packed->midx, file_name))) {\n+\t    !(data->source->midx &&\n+\t      midx_contains_pack(data->source->midx, file_name))) {\n \t\tchar *trimmed_path = xstrndup(full_name, full_name_len);\n-\t\tpackfile_store_load_pack(files->packed,\n-\t\t\t\t\t trimmed_path, data->source->local);\n+\t\tpackfile_store_load_pack(data->source,\n+\t\t\t\t\t trimmed_path, data->source->base.local);\n \t\tfree(trimmed_path);\n \t}\n \n@@ -626,7 +625,7 @@ static void prepare_pack(const char *full_name, size_t full_name_len,\n \t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n }\n \n-static void prepare_packed_git_one(struct odb_source *source)\n+static void prepare_packed_git_one(struct odb_source_packed *source)\n {\n \tstruct string_list garbage = STRING_LIST_INIT_DUP;\n \tstruct prepare_pack_data data = {\n@@ -634,7 +633,7 @@ static void prepare_packed_git_one(struct odb_source *source)\n \t\t.garbage = &garbage,\n \t};\n \n-\tfor_each_file_in_pack_dir(source->path, prepare_pack, &data);\n+\tfor_each_file_in_pack_dir(source->base.path, prepare_pack, &data);\n \n \treport_pack_garbage(data.garbage);\n \tstring_list_clear(data.garbage, 0);\n@@ -675,7 +674,7 @@ void odb_source_packed_prepare(struct odb_source_packed *source)\n \t\treturn;\n \n \tprepare_multi_pack_index_one(source);\n-\tprepare_packed_git_one(&source->files->base);\n+\tprepare_packed_git_one(source);\n \n \tsort_packs(&source->packs.head, sort_pack);\n \tfor (struct packfile_list_entry *e = source->packs.head; e; e = e->next)\n@@ -733,14 +732,14 @@ static void odb_source_packed_free(struct odb_source *source)\n \tfree(packed);\n }\n \n-struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n+struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n+\t\t\t\t\t\tconst char *path,\n+\t\t\t\t\t\tbool local)\n {\n \tstruct odb_source_packed *packed;\n \n \tCALLOC_ARRAY(packed, 1);\n-\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n-\t\t\tparent->base.path, parent->base.local);\n-\tpacked->files = parent;\n+\todb_source_init(&packed->base, odb, ODB_SOURCE_PACKED, path, local);\n \tstrmap_init(&packed->packs_by_path);\n \n \tpacked->base.free = odb_source_packed_free;\n@@ -758,7 +757,7 @@ struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n \tpacked->base.read_alternates = odb_source_packed_read_alternates;\n \tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n-\tif (!is_absolute_path(parent->base.path))\n+\tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n \n \treturn packed;\ndiff --git a/odb/source-packed.h b/odb/source-packed.h\nindex 9d4796261a..88994098c1 100644\n--- a/odb/source-packed.h\n+++ b/odb/source-packed.h\n@@ -10,7 +10,6 @@\n  */\n struct odb_source_packed {\n \tstruct odb_source base;\n-\tstruct odb_source_files *files;\n \n \t/*\n \t * The list of packfiles in the order in which they have been most\n@@ -66,9 +65,11 @@ struct odb_source_packed {\n \n /*\n  * Allocate and initialize a new empty packfile store for the given object\n- * database source.\n+ * database.\n  */\n-struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent);\n+struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n+\t\t\t\t\t\tconst char *path,\n+\t\t\t\t\t\tbool local);\n \n /*\n  * Cast the given object database source to the packed backend. This will cause\ndiff --git a/packfile.c b/packfile.c\nindex a577275d4f..59cee7925d 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -801,7 +801,7 @@ struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,\n \n \tp = strmap_get(&store->packs_by_path, key.buf);\n \tif (!p) {\n-\t\tp = add_packed_git(store->files->base.odb->repo, idx_path,\n+\t\tp = add_packed_git(store->base.odb->repo, idx_path,\n \t\t\t\t   strlen(idx_path), local);\n \t\tif (p)\n \t\t\tpackfile_store_add_pack(store, p);\n\n-- \n2.55.0.rc0.786.g65d90a0328.dirty\n\n"},{"id":"545729","messageId":"ajJMHcHdtFdHjD8v@pks.im","threadId":"65752","inReplyTo":"ajG-rP7cRjZa3zJE@denethor","subject":"Re: [PATCH v2 05/17] odb/source-packed: start converting to a proper `struct odb_source`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T07:26:26Z","receivedAt":"2026-06-17T07:26:36Z","isPatch":true,"body":"On Tue, Jun 16, 2026 at 04:36:59PM -0500, Justin Tobler wrote:\n> On 26/06/09 10:50AM, Patrick Steinhardt wrote:\n> >  struct odb_source_packed *odb_source_packed_new(struct odb_source_files *parent)\n> >  {\n> > -\tstruct odb_source_packed *store;\n> > -\tCALLOC_ARRAY(store, 1);\n> > -\tstore->files = parent;\n> > -\tstrmap_init(&store->packs_by_path);\n> > -\treturn store;\n> > +\tstruct odb_source_packed *packed;\n> > +\n> > +\tCALLOC_ARRAY(packed, 1);\n> > +\todb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,\n> > +\t\t\tparent->base.path, parent->base.local);\n> > +\tpacked->files = parent;\n> > +\tstrmap_init(&packed->packs_by_path);\n> > +\n> > +\tpacked->base.free = odb_source_packed_free;\n> > +\n> > +\tif (!is_absolute_path(parent->base.path))\n> > +\t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\n> \n> Out of curiousity, did the packfile store previously not have to worry\n> about changing directories?\n\nNo, it didn't, and at this step here it still doesn't. This is mostly\nbecause we still use the path of the \"files\" source, and that source\nhandles the reparenting for us. But it will be needed in a subsequent\nstep once we get rid of the pointer to the owning \"files\" source.\n\nPatrick\n"},{"id":"545730","messageId":"ajJMLM2T34Gf80ft@pks.im","threadId":"65752","inReplyTo":"ajHDm_pG3Ckca4mp@denethor","subject":"Re: [PATCH v2 07/17] odb/source-packed: wire up `reprepare()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T07:26:36Z","receivedAt":"2026-06-17T07:26:40Z","isPatch":true,"body":"On Tue, Jun 16, 2026 at 04:53:38PM -0500, Justin Tobler wrote:\n> On 26/06/09 10:51AM, Patrick Steinhardt wrote:\n> > Move the logic to prepare and reprepare the \"packed\" source into\n> > \"odb/source-packed.c\" and wire it up as the `reprepare()` callback.\n> > \n> > Note that \"preparing\" a source is not yet generic. Eventually, it would\n> > probably make sense to turn the existing `reprepare()` callback into a\n> > `prepare()` callback with an optional flag to force re-preparing. But\n> > this step will be handled in a separate patch series.\n> \n> I do find the prepare vs reprepare semantics a bit confusing. The\n> mentioned change above would be nice to see in the future. :)\n\nIt is confusing, agreed. I have already written the patch series that\nconverts this.\n\nPatrick\n"},{"id":"545731","messageId":"ajJMMQiCmV6dBfYD@pks.im","threadId":"65752","inReplyTo":"ajHIjrQJvWtbCrZp@denethor","subject":"Re: [PATCH v2 11/17] odb/source-packed: wire up `for_each_object()` callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T07:26:41Z","receivedAt":"2026-06-17T07:26:46Z","isPatch":true,"body":"On Tue, Jun 16, 2026 at 05:10:28PM -0500, Justin Tobler wrote:\n> On 26/06/09 10:51AM, Patrick Steinhardt wrote:\n> > diff --git a/packfile.c b/packfile.c\n> > index 42c84397eb..b8d6054c16 100644\n> > --- a/packfile.c\n> > +++ b/packfile.c\n> > @@ -1362,8 +1362,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> > -static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n> > -\t\t\t\t\t     uint32_t *maybe_index_pos, struct object_info *oi)\n> > +int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset,\n> > +\t\t\t\t      uint32_t *maybe_index_pos, struct object_info *oi)\n> \n> Looks like we are also exposing `packed_object_info_with_index_pos()`\n> now. Not sure yet if this is also intended to be temporary like\n> `find_pack_entry()` in a previous patch though though.\n\nNo, it's not. This function is also required in case you just have a\nsingle packfile to look up objects in there. So there are use cases\noutside of the \"packed\" source where we need it going forward.\n\nPatrick\n"},{"id":"545732","messageId":"ajJMNk5eMYR0CjJx@pks.im","threadId":"65752","inReplyTo":"ajHKmbimHJnFV2IE@denethor","subject":"Re: [PATCH v2 16/17] midx: refactor interfaces to work on \"packed\" source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T07:26:46Z","receivedAt":"2026-06-17T07:26:51Z","isPatch":true,"body":"On Tue, Jun 16, 2026 at 05:37:53PM -0500, Justin Tobler wrote:\n> On 26/06/09 10:51AM, Patrick Steinhardt wrote:\n> > diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\n> > index 00ffb36394..6e73c85cde 100644\n> > --- a/builtin/multi-pack-index.c\n> > +++ b/builtin/multi-pack-index.c\n> > @@ -85,12 +86,12 @@ static int parse_object_dir(const struct option *opt, const char *arg,\n> >  \treturn 0;\n> >  }\n> >  \n> > -static struct odb_source *handle_object_dir_option(struct repository *repo)\n> > +static struct odb_source_files *handle_object_dir_option(struct repository *repo)\n> >  {\n> >  \tstruct odb_source *source = odb_find_source(repo->objects, opts.object_dir);\n> >  \tif (!source)\n> >  \t\tsource = odb_add_to_alternates_memory(repo->objects, opts.object_dir);\n> > -\treturn source;\n> > +\treturn odb_source_files_downcast(source);\n> \n> Now that we are passing around concrete ODB sources to many of these\n> interfaces, callers may have to handle downcasting explicitly. Since now\n> the downcasting may happen a bit ealier than previously in some cases, I\n> wondered if this would alter any error flows. I don't think this would\n> be the case though because ultimately we only have the \"files\" source\n> currently anyways.\n\nWell, we'd be calling `BUG()` all the same. So as we don't have\nconditional logic anywhere (yet?) that checks whether or not we use the\n\"files\" backend all error flows are expected to ultimately be the same.\nIt's only that we may `BUG()` a bit earlier, but I guess the user is\ngoing to be somewhat indifferent about this.\n\nPatrick\n"},{"id":"545733","messageId":"ajJMO65fqU0e-Ez5@pks.im","threadId":"65752","inReplyTo":"ajHRkrsvNtcBMFx2@denethor","subject":"Re: [PATCH v2 17/17] odb/source-packed: drop pointer to \"files\" parent source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T07:26:51Z","receivedAt":"2026-06-17T07:26:56Z","isPatch":true,"body":"On Tue, Jun 16, 2026 at 05:51:43PM -0500, Justin Tobler wrote:\n> On 26/06/09 10:51AM, Patrick Steinhardt wrote:\n> > @@ -626,7 +625,7 @@ static void prepare_pack(const char *full_name, size_t full_name_len,\n> >  \t\treport_garbage(PACKDIR_FILE_GARBAGE, full_name);\n> >  }\n> >  \n> > -static void prepare_packed_git_one(struct odb_source *source)\n> > +static void prepare_packed_git_one(struct odb_source_packed *source)\n> \n> At first I was a bit confused to see this change here, but IIUC\n> previously this function was passed the \"base\" source of the \"files\"\n> ODB. Now that we have a proper \"packed\" source we can use that directly\n> instead.\n\nYes, exactly.\n\nThanks for your review!\n\nPatrick\n"},{"id":"545734","messageId":"ajJMQfXpPNlxqhNa@pks.im","threadId":"65752","inReplyTo":"ajG69JZHx_u2mt7q@denethor","subject":"Re: [PATCH v2 04/17] odb/source-packed: store pointer to \"files\" instead of generic source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-17T07:26:57Z","receivedAt":"2026-06-17T07:27:01Z","isPatch":true,"body":"On Tue, Jun 16, 2026 at 04:14:02PM -0500, Justin Tobler wrote:\n> On 26/06/09 10:50AM, Patrick Steinhardt wrote:\n[snip]\n> > Detangling this mess is somewhat intricate though, and is made even more\n> > intricate because it's not always clear which kind of source one is\n> > holding at a specific point in time -- either the parent \"files\" source,\n> > or the child \"packed\" source.\n> > \n> > Make this relationship more explicit by storing a pointer to the \"files\"\n> > source instead of storing a pointer to a generic `struct odb_source`.\n> > This will help make subsequent steps a bit clearer.\n> > \n> > Note that this is a temporary step, only. At the end of this series\n> > we will have dropped the parent pointer completely.\n> \n> Ok, so IIUC the eventual goal is to get rid of the pointer entirely, but\n> for now we are just making its concrete type explicit without having to\n> downcast. It's not immediately obvious to me how this step gets us\n> closer to that goal, but that may become more obvious in the next\n> patches. :)\n\nThe reason this helps is that during the intermediate steps, it's at\ntimes very hard to see which source you're holding if we have both the\ngeneric `base` and the generic `parent` pointer. That makes it quite\neasy to confuse those two. So by turning the latter into the `files`\nsource, some of the intermediate steps become easier to reason about.\n\nPatrick\n"},{"id":"545767","messageId":"ajK2QKdW-TdflfR0@denethor","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im","subject":"Re: [PATCH v3 00/17] odb: make packed object source a proper `struct odb_source`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-17T15:02:46Z","receivedAt":"2026-06-17T15:02:49Z","isPatch":true,"body":"On 26/06/17 08:39AM, Patrick Steinhardt wrote:\n>  5:  8eb3cb17a1 !  5:  c9b1e1da26 odb/source-packed: start converting to a proper `struct odb_source`\n>     @@ Commit message\n>          odb_source`, as it's missing all of the callback implementations. These\n>          will be wired up in subsequent commits.\n>      \n>     +    Further note that we're also registering a `chdir_notify` callback to\n>     +    reparent our path. This wasn't previously necessary (and still isn't at\n>     +    this point in time) because all paths are taken from the owning \"files\"\n>     +    source, and that source already handles the reparenting for us. But a\n>     +    subsequent commit will change that so that we're using the path of the\n>     +    \"packed\" source, and once that happens we'll need it to be updated when\n>     +    changing the working directory.\n\nAh ok, the \"file\" ODB source already has a `chdir_notify` callback\nregistered to handle this which is why we could get away with using the\npath taken from the parent. Make sense. The explaination here is very\nhelpful.\n\nThis version of the series looks good to me.\n\nThanks\n-Justin\n"},{"id":"545776","messageId":"xmqq1pe5xg58.fsf@gitster.g","threadId":"65752","inReplyTo":"ajK2QKdW-TdflfR0@denethor","subject":"Re: [PATCH v3 00/17] odb: make packed object source a proper `struct odb_source`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-17T16:53:23Z","receivedAt":"2026-06-17T16:53:27Z","isPatch":true,"body":"Justin Tobler <jltobler@gmail.com> writes:\n\n> On 26/06/17 08:39AM, Patrick Steinhardt wrote:\n>>  5:  8eb3cb17a1 !  5:  c9b1e1da26 odb/source-packed: start converting to a proper `struct odb_source`\n>>     @@ Commit message\n>>          odb_source`, as it's missing all of the callback implementations. These\n>>          will be wired up in subsequent commits.\n>>      \n>>     +    Further note that we're also registering a `chdir_notify` callback to\n>>     +    reparent our path. This wasn't previously necessary (and still isn't at\n>>     +    this point in time) because all paths are taken from the owning \"files\"\n>>     +    source, and that source already handles the reparenting for us. But a\n>>     +    subsequent commit will change that so that we're using the path of the\n>>     +    \"packed\" source, and once that happens we'll need it to be updated when\n>>     +    changing the working directory.\n>\n> Ah ok, the \"file\" ODB source already has a `chdir_notify` callback\n> registered to handle this which is why we could get away with using the\n> path taken from the parent. Make sense. The explaination here is very\n> helpful.\n>\n> This version of the series looks good to me.\n\nThanks, all of you.  Very much appreciated.\n"},{"id":"545876","messageId":"xmqqik7frapn.fsf@gitster.g","threadId":"65752","inReplyTo":"20260617-pks-odb-source-packed-v3-15-b5c7583cd795@pks.im","subject":"Re: [PATCH v3 15/17] odb/source-packed: stub out remaining functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-18T17:59:32Z","receivedAt":"2026-06-18T17:59:34Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\nJust FYI (i.e. nothing wrong in this patch)\n\n> +static int odb_source_packed_write_object(struct odb_source *source UNUSED,\n> +\t\t\t\t\t  const void *buf UNUSED,\n> +\t\t\t\t\t  unsigned long len UNUSED,\n\nThe type of this parameter will become size_t via another topic in\nflight; I prepared an evil merge to address it (otherwise winbuild\nwould barf, as expected).\n\n-- >8 --\nAuthor: Junio C Hamano <gitster@pobox.com>\nDate:   Thu Jun 18 10:49:10 2026 -0700\n\n    merge-fix po/hash-object-size-t vs ps/odb-source-packed\n\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 42c28fba0e..decc81aa52 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -503,7 +503,7 @@ static int odb_source_packed_freshen_object(struct odb_source *source,\n \n static int odb_source_packed_write_object(struct odb_source *source UNUSED,\n \t\t\t\t\t  const void *buf UNUSED,\n-\t\t\t\t\t  unsigned long len UNUSED,\n+\t\t\t\t\t  size_t len UNUSED,\n \t\t\t\t\t  enum object_type type UNUSED,\n \t\t\t\t\t  struct object_id *oid UNUSED,\n \t\t\t\t\t  struct object_id *compat_oid UNUSED,\n"},{"id":"545911","messageId":"ajTRK4nBxyv3YIgs@pks.im","threadId":"65752","inReplyTo":"xmqqik7frapn.fsf@gitster.g","subject":"Re: [PATCH v3 15/17] odb/source-packed: stub out remaining functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-19T05:18:35Z","receivedAt":"2026-06-19T05:18:57Z","isPatch":true,"body":"On Thu, Jun 18, 2026 at 10:59:32AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> Just FYI (i.e. nothing wrong in this patch)\n> \n> > +static int odb_source_packed_write_object(struct odb_source *source UNUSED,\n> > +\t\t\t\t\t  const void *buf UNUSED,\n> > +\t\t\t\t\t  unsigned long len UNUSED,\n> \n> The type of this parameter will become size_t via another topic in\n> flight; I prepared an evil merge to address it (otherwise winbuild\n> would barf, as expected).\n> \n> -- >8 --\n> Author: Junio C Hamano <gitster@pobox.com>\n> Date:   Thu Jun 18 10:49:10 2026 -0700\n> \n>     merge-fix po/hash-object-size-t vs ps/odb-source-packed\n> \n> diff --git a/odb/source-packed.c b/odb/source-packed.c\n> index 42c28fba0e..decc81aa52 100644\n> --- a/odb/source-packed.c\n> +++ b/odb/source-packed.c\n> @@ -503,7 +503,7 @@ static int odb_source_packed_freshen_object(struct odb_source *source,\n>  \n>  static int odb_source_packed_write_object(struct odb_source *source UNUSED,\n>  \t\t\t\t\t  const void *buf UNUSED,\n> -\t\t\t\t\t  unsigned long len UNUSED,\n> +\t\t\t\t\t  size_t len UNUSED,\n>  \t\t\t\t\t  enum object_type type UNUSED,\n>  \t\t\t\t\t  struct object_id *oid UNUSED,\n>  \t\t\t\t\t  struct object_id *compat_oid UNUSED,\n\nThanks for the heads up, the change looks obviously correct to me. I'm\nalso happy to send a rebased version -- just give me a nudge and I'll do\nthat.\n\nPatrick\n"},{"id":"546000","messageId":"xmqq1pe2lbzh.fsf@gitster.g","threadId":"65752","inReplyTo":"ajTRK4nBxyv3YIgs@pks.im","subject":"Re: [PATCH v3 15/17] odb/source-packed: stub out remaining functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-19T16:40:50Z","receivedAt":"2026-06-19T16:40:53Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> diff --git a/odb/source-packed.c b/odb/source-packed.c\n>> index 42c28fba0e..decc81aa52 100644\n>> --- a/odb/source-packed.c\n>> +++ b/odb/source-packed.c\n>> @@ -503,7 +503,7 @@ static int odb_source_packed_freshen_object(struct odb_source *source,\n>>  \n>>  static int odb_source_packed_write_object(struct odb_source *source UNUSED,\n>>  \t\t\t\t\t  const void *buf UNUSED,\n>> -\t\t\t\t\t  unsigned long len UNUSED,\n>> +\t\t\t\t\t  size_t len UNUSED,\n>>  \t\t\t\t\t  enum object_type type UNUSED,\n>>  \t\t\t\t\t  struct object_id *oid UNUSED,\n>>  \t\t\t\t\t  struct object_id *compat_oid UNUSED,\n>\n> Thanks for the heads up, the change looks obviously correct to me. I'm\n> also happy to send a rebased version -- just give me a nudge and I'll do\n> that.\n\nNah, I expect the other topic would be ready and among the first\nbatch to graduate post 2.55 final, so it is not too much of a hassle\nto carry the merge-fix around for me.  I do not even expect I need\n\"refs rename\" for this, as the merge order will unlikely to be\nflipped ;-)\n\nTHanks.\n"}]}