{"thread":{"id":"65269","subject":"[PATCH 0/2] odb: add odb_source_files_try() for heterogeneous source iteration","startedAt":"2026-03-16T15:29:46Z","lastAt":"2026-03-19T10:06:00Z","messageCount":4,"participants":["Aaron Paterson via GitGitGadget","Patrick Steinhardt"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"539112","messageId":"pull.2068.git.1773674983.gitgitgadget@gmail.com","threadId":"65269","inReplyTo":null,"subject":"[PATCH 0/2] odb: add odb_source_files_try() for heterogeneous source iteration","fromName":"Aaron Paterson via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-16T15:29:41Z","receivedAt":"2026-03-16T15:29:46Z","isPatch":true,"sender":{"key":"name:Aaron Paterson","avatar":null},"body":"The odb_source vtable introduced in this release allows multiple backend\nimplementations via the odb_source_type enum. However, 22 callsites iterate\nthe source chain and downcast every source to odb_source_files to access\nfiles-specific internals (pack store, loose cache, MIDX). This prevents\nadding any non-files source to the chain without hitting a BUG() assertion.\n\nThis two-patch series adds odb_source_files_try() as a companion to the\nexisting downcast function. It returns NULL for non-files sources instead of\naborting. The 22 source-chain iteration sites are then converted to use this\nnew helper, skipping non-files sources gracefully.\n\nThe existing odb_source_files_downcast() is unchanged and continues to BUG()\non type mismatch, protecting call sites that should only ever receive a\nfiles source (primary source access, vtable callbacks).\n\nThis follows the pattern established by the refs subsystem, where\neliminating backend downcasts was a prerequisite for adding the reftable\nbackend.\n\nPatch 1/2: Add odb_source_files_try() helper (14 lines, 1 file)\n\nPatch 2/2: Convert 22 iteration sites across 11 files + unit test\n\nKnown limitation: repo_approximate_object_count() and has_object_pack() will\nnot account for objects in non-files sources. These are display/optimization\nfunctions. A follow-up series can add vtable callbacks to address this.\n\nAaron Paterson (2):\n  odb: add odb_source_files_try() for heterogeneous source iteration\n  odb: use odb_source_files_try() in source-chain iterations\n\n Makefile                    |  1 +\n builtin/cat-file.c          |  9 ++++++---\n builtin/fast-import.c       |  8 ++++++--\n builtin/grep.c              |  4 +++-\n builtin/pack-objects.c      | 15 +++++++++++----\n commit-graph.c              |  4 +++-\n loose.c                     | 12 +++++++++---\n midx.c                      |  8 ++++++--\n object-file.c               | 20 ++++++++++++++------\n object-name.c               |  8 +++++---\n odb/source-files.h          | 14 ++++++++++++++\n packfile.c                  | 23 +++++++++++++++++------\n packfile.h                  | 14 ++++++++++----\n t/meson.build               |  1 +\n t/unit-tests/u-odb-source.c | 25 +++++++++++++++++++++++++\n 15 files changed, 131 insertions(+), 35 deletions(-)\n create mode 100644 t/unit-tests/u-odb-source.c\n\n\nbase-commit: dc6ecd5354dca88d51b6d6562777fc8fc10d77e1\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2068%2FMayCXC%2Fps%2Fodb-source-type-guards-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2068/MayCXC/ps/odb-source-type-guards-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/2068\n-- \ngitgitgadget\n"},{"id":"539113","messageId":"e6877b30d5dddce5720a15d724d7b381a5595915.1773674983.git.gitgitgadget@gmail.com","threadId":"65269","inReplyTo":"pull.2068.git.1773674983.gitgitgadget@gmail.com","subject":"[PATCH 1/2] odb: add odb_source_files_try() for heterogeneous source iteration","fromName":"Aaron Paterson via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-16T15:29:42Z","receivedAt":"2026-03-16T15:29:48Z","isPatch":true,"sender":{"key":"name:Aaron Paterson","avatar":null},"body":"From: Aaron Paterson <apaterson@pm.me>\n\nThe odb_source vtable introduced in this release allows multiple\nbackend implementations via the odb_source_type enum. However,\nsource-chain iteration sites that need files-specific internals\n(pack store, loose cache, MIDX) currently use\nodb_source_files_downcast(), which calls BUG() for non-files\nsource types. This makes it impossible to add a non-files source\nto the chain without crashing.\n\nAdd odb_source_files_try() as a companion to the existing\ndowncast function. It returns NULL for non-files sources instead\nof aborting. This follows the pattern used elsewhere in git where\na \"try\" or \"maybe\" variant provides a fallback path while the\nstrict version retains its safety guarantee.\n\nThe existing odb_source_files_downcast() is unchanged and\ncontinues to BUG() on type mismatch, protecting call sites that\nshould only ever receive a files source.\n\nA subsequent commit will convert the source-chain iteration sites\nto use this new helper.\n\nSigned-off-by: Aaron Paterson <apaterson@pm.me>\n---\n odb/source-files.h | 14 ++++++++++++++\n 1 file changed, 14 insertions(+)\n\ndiff --git a/odb/source-files.h b/odb/source-files.h\nindex 23a3b4e04b..abd093b23f 100644\n--- a/odb/source-files.h\n+++ b/odb/source-files.h\n@@ -32,4 +32,18 @@ static inline struct odb_source_files *odb_source_files_downcast(struct odb_sour\n \treturn container_of(source, struct odb_source_files, base);\n }\n \n+/*\n+ * Try to cast the given source to the files backend. Returns NULL if\n+ * the source uses a different backend. Use this in loops that iterate\n+ * over heterogeneous source chains (e.g. when alternates may include\n+ * non-files backends). Use odb_source_files_downcast() when the source\n+ * is known to be a files backend.\n+ */\n+static inline struct odb_source_files *odb_source_files_try(struct odb_source *source)\n+{\n+\tif (source->type != ODB_SOURCE_FILES)\n+\t\treturn NULL;\n+\treturn container_of(source, struct odb_source_files, base);\n+}\n+\n #endif\n-- \ngitgitgadget\n\n"},{"id":"539114","messageId":"a8303b1427a41fd4b3ca107eabc49e8ac6d02410.1773674983.git.gitgitgadget@gmail.com","threadId":"65269","inReplyTo":"pull.2068.git.1773674983.gitgitgadget@gmail.com","subject":"[PATCH 2/2] odb: use odb_source_files_try() in source-chain iterations","fromName":"Aaron Paterson via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-16T15:29:43Z","receivedAt":"2026-03-16T15:29:49Z","isPatch":true,"sender":{"key":"name:Aaron Paterson","avatar":null},"body":"From: Aaron Paterson <apaterson@pm.me>\n\nConvert all source-chain iteration sites that access\nfiles-specific internals (pack store, loose cache, MIDX) to use\nodb_source_files_try() instead of odb_source_files_downcast().\n\nThese loops iterate the full source chain, which may include\nalternates. When a non-files backend is added to the chain (as an\nalternate or additional source), these sites must skip it rather\nthan abort. The _try() helper returns NULL for non-files sources,\nand each site checks for NULL before accessing files-specific\nmembers.\n\nCall sites that access odb->sources directly (the primary source,\nwhich is always a files backend) or that are inside files-backend\nvtable callbacks continue to use odb_source_files_downcast() with\nits BUG() safety guarantee.\n\nSites converted (22 across 11 files):\n\n  - loose.c: loose object map loading and oid lookup\n  - midx.c: multi-pack-index access and cleanup\n  - object-file.c: loose cache and reprepare\n  - object-name.c: short object filename search\n  - packfile.c: window/fd management, pack entry lookup\n  - packfile.h: repo_for_each_pack iterator macros\n  - commit-graph.c: packed object enumeration\n  - builtin/cat-file.c: batch object iteration\n  - builtin/fast-import.c: pack lookup in store/stream\n  - builtin/grep.c: pack preparation\n  - builtin/pack-objects.c: cruft/kept pack enumeration\n\nAdd a unit test verifying odb_source_files_try() returns the\ncorrect backend for ODB_SOURCE_FILES and NULL for other types.\n\nNote: repo_approximate_object_count() and has_object_pack() will\nnot account for objects in non-files sources. These are\ndisplay and optimization functions respectively, and a follow-up\nseries can add vtable callbacks to address this.\n\nSigned-off-by: Aaron Paterson <apaterson@pm.me>\n---\n Makefile                    |  1 +\n builtin/cat-file.c          |  9 ++++++---\n builtin/fast-import.c       |  8 ++++++--\n builtin/grep.c              |  4 +++-\n builtin/pack-objects.c      | 15 +++++++++++----\n commit-graph.c              |  4 +++-\n loose.c                     | 12 +++++++++---\n midx.c                      |  8 ++++++--\n object-file.c               | 20 ++++++++++++++------\n object-name.c               |  8 +++++---\n packfile.c                  | 23 +++++++++++++++++------\n packfile.h                  | 14 ++++++++++----\n t/meson.build               |  1 +\n t/unit-tests/u-odb-source.c | 25 +++++++++++++++++++++++++\n 14 files changed, 117 insertions(+), 35 deletions(-)\n create mode 100644 t/unit-tests/u-odb-source.c\n\ndiff --git a/Makefile b/Makefile\nindex 58fb895f4e..3d65093d5a 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1544,6 +1544,7 @@ CLAR_TEST_SUITES += u-strvec\n CLAR_TEST_SUITES += u-trailer\n CLAR_TEST_SUITES += u-urlmatch-normalization\n CLAR_TEST_SUITES += u-utf8-width\n+CLAR_TEST_SUITES += u-odb-source\n CLAR_TEST_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)\n CLAR_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(CLAR_TEST_SUITES))\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex b6f12f41d6..0c63705b72 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -882,9 +882,12 @@ static void batch_each_object(struct batch_options *opt,\n \t\tstruct object_info oi = { 0 };\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, flags);\n+\t\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\t\tint ret;\n+\t\t\tif (!files)\n+\t\t\t\tcontinue;\n+\t\t\tret = packfile_store_for_each_object(files->packed, &oi,\n+\t\t\t\t\t\t\t     batch_one_object_oi, &payload, flags);\n \t\t\tif (ret)\n \t\t\t\tbreak;\n \t\t}\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex a41f95191e..cef77ac937 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -982,7 +982,9 @@ static int store_object(\n \t}\n \n \tfor (source = the_repository->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tif (!files)\n+\t\t\tcontinue;\n \n \t\tif (!packfile_list_find_oid(packfile_store_get_packs(files->packed), &oid))\n \t\t\tcontinue;\n@@ -1189,7 +1191,9 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)\n \t}\n \n \tfor (source = the_repository->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tif (!files)\n+\t\t\tcontinue;\n \n \t\tif (!packfile_list_find_oid(packfile_store_get_packs(files->packed), &oid))\n \t\t\tcontinue;\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex e33285e5e6..ea41236ce5 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -1219,7 +1219,9 @@ int cmd_grep(int argc,\n \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\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\t\t\tif (!files)\n+\t\t\t\t\tcontinue;\n \t\t\t\tpackfile_store_prepare(files->packed);\n \t\t\t}\n \t\t}\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex cd013c0b68..229e3e40b0 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -1531,8 +1531,11 @@ static int want_cruft_object_mtime(struct repository *r,\n \tstruct odb_source *source;\n \n \tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tstruct packed_git **cache = packfile_store_get_kept_pack_cache(files->packed, flags);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tstruct packed_git **cache;\n+\t\tif (!files)\n+\t\t\tcontinue;\n+\t\tcache = packfile_store_get_kept_pack_cache(files->packed, flags);\n \n \t\tfor (; *cache; cache++) {\n \t\t\tstruct packed_git *p = *cache;\n@@ -1754,7 +1757,9 @@ static int want_object_in_pack_mtime(const struct object_id *oid,\n \t}\n \n \tfor (source = the_repository->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tif (!files)\n+\t\t\tcontinue;\n \n \t\tfor (e = files->packed->packs.head; e; e = e->next) {\n \t\t\tstruct packed_git *p = e->pack;\n@@ -4350,7 +4355,9 @@ static void add_objects_in_unpacked_packs(void)\n \n \todb_prepare_alternates(to_pack.repo->objects);\n \tfor (source = to_pack.repo->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tif (!files)\n+\t\t\tcontinue;\n \n \t\tif (!source->local)\n \t\t\tcontinue;\ndiff --git a/commit-graph.c b/commit-graph.c\nindex f8e24145a5..b425417735 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1981,7 +1981,9 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n \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\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tif (!files)\n+\t\t\tcontinue;\n \t\tpackfile_store_for_each_object(files->packed, &oi, add_packed_commits_oi,\n \t\t\t\t\t       ctx, ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t}\ndiff --git a/loose.c b/loose.c\nindex 07333be696..1f036a8c2b 100644\n--- a/loose.c\n+++ b/loose.c\n@@ -64,10 +64,13 @@ static int insert_loose_map(struct odb_source *source,\n \n static int load_one_loose_object_map(struct repository *repo, struct odb_source *source)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\tstruct odb_source_files *files = odb_source_files_try(source);\n \tstruct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;\n \tFILE *fp;\n \n+\tif (!files)\n+\t\treturn 0;\n+\n \tif (!files->loose->map)\n \t\tloose_object_map_init(&files->loose->map);\n \tif (!files->loose->cache) {\n@@ -235,8 +238,11 @@ int repo_loose_object_map_oid(struct repository *repo,\n \tkhiter_t pos;\n \n \tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tstruct loose_object_map *loose_map = files->loose->map;\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tstruct loose_object_map *loose_map;\n+\t\tif (!files)\n+\t\t\tcontinue;\n+\t\tloose_map = files->loose->map;\n \t\tif (!loose_map)\n \t\t\tcontinue;\n \t\tmap = (to == repo->compat_hash_algo) ?\ndiff --git a/midx.c b/midx.c\nindex ab8e2611d1..d29b72f8d9 100644\n--- a/midx.c\n+++ b/midx.c\n@@ -95,7 +95,9 @@ static int midx_read_object_offsets(const unsigned char *chunk_start,\n \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+\tstruct odb_source_files *files = odb_source_files_try(source);\n+\tif (!files)\n+\t\treturn NULL;\n \tpackfile_store_prepare(files->packed);\n \treturn files->packed->midx;\n }\n@@ -806,7 +808,9 @@ void clear_midx_file(struct repository *r)\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\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\t\tif (!files)\n+\t\t\t\tcontinue;\n \t\t\tif (files->packed->midx)\n \t\t\t\tclose_midx(files->packed->midx);\n \t\t\tfiles->packed->midx = NULL;\ndiff --git a/object-file.c b/object-file.c\nindex a3ff7f586c..11136ee199 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1879,14 +1879,20 @@ static int append_loose_object(const struct object_id *oid,\n struct oidtree *odb_source_loose_cache(struct odb_source *source,\n \t\t\t\t       const struct object_id *oid)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\tint subdir_nr = oid->hash[0];\n+\tstruct odb_source_files *files = odb_source_files_try(source);\n+\tint subdir_nr;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tsize_t word_bits = bitsizeof(files->loose->subdir_seen[0]);\n-\tsize_t word_index = subdir_nr / word_bits;\n-\tsize_t mask = (size_t)1u << (subdir_nr % word_bits);\n+\tsize_t word_bits, word_index, mask;\n \tuint32_t *bitmap;\n \n+\tif (!files)\n+\t\treturn NULL;\n+\n+\tsubdir_nr = oid->hash[0];\n+\tword_bits = bitsizeof(files->loose->subdir_seen[0]);\n+\tword_index = subdir_nr / word_bits;\n+\tmask = (size_t)1u << (subdir_nr % word_bits);\n+\n \tif (subdir_nr < 0 ||\n \t    (size_t) subdir_nr >= bitsizeof(files->loose->subdir_seen))\n \t\tBUG(\"subdir_nr out of range\");\n@@ -1919,7 +1925,9 @@ static void odb_source_loose_clear_cache(struct odb_source_loose *loose)\n \n void odb_source_loose_reprepare(struct odb_source *source)\n {\n-\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\tstruct odb_source_files *files = odb_source_files_try(source);\n+\tif (!files)\n+\t\treturn;\n \todb_source_loose_clear_cache(files->loose);\n }\n \ndiff --git a/object-name.c b/object-name.c\nindex 7b14c3bf9b..09835a3225 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -115,9 +115,11 @@ static void find_short_object_filename(struct disambiguate_state *ds)\n {\n \tstruct odb_source *source;\n \n-\tfor (source = ds->repo->objects->sources; source && !ds->ambiguous; source = source->next)\n-\t\toidtree_each(odb_source_loose_cache(source, &ds->bin_pfx),\n-\t\t\t\t&ds->bin_pfx, ds->len, match_prefix, ds);\n+\tfor (source = ds->repo->objects->sources; source && !ds->ambiguous; source = source->next) {\n+\t\tstruct oidtree *tree = odb_source_loose_cache(source, &ds->bin_pfx);\n+\t\tif (tree)\n+\t\t\toidtree_each(tree, &ds->bin_pfx, ds->len, match_prefix, ds);\n+\t}\n }\n \n static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b)\ndiff --git a/packfile.c b/packfile.c\nindex 215a23e42b..d7416e577f 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -363,7 +363,9 @@ static int unuse_one_window(struct object_database *odb)\n \tstruct pack_window *lru_w = NULL, *lru_l = NULL;\n \n \tfor (source = odb->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tif (!files)\n+\t\t\tcontinue;\n \t\tfor (e = files->packed->packs.head; e; e = e->next)\n \t\t\tscan_windows(e->pack, &lru_p, &lru_w, &lru_l);\n \t}\n@@ -539,7 +541,9 @@ static int close_one_pack(struct repository *r)\n \tint accept_windows_inuse = 1;\n \n \tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tif (!files)\n+\t\t\tcontinue;\n \t\tfor (e = files->packed->packs.head; e; e = e->next) {\n \t\t\tif (e->pack->pack_fd == -1)\n \t\t\t\tcontinue;\n@@ -1251,8 +1255,10 @@ const struct packed_git *has_packed_and_bad(struct repository *r,\n \tstruct odb_source *source;\n \n \tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n \t\tstruct packfile_list_entry *e;\n+\t\tif (!files)\n+\t\t\tcontinue;\n \n \t\tfor (e = files->packed->packs.head; e; e = e->next)\n \t\t\tif (oidset_contains(&e->pack->bad_objects, oid))\n@@ -2268,8 +2274,11 @@ int has_object_pack(struct repository *r, const struct object_id *oid)\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\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tint ret;\n+\t\tif (!files)\n+\t\t\tcontinue;\n+\t\tret = find_pack_entry(files->packed, oid, &e);\n \t\tif (ret)\n \t\t\treturn ret;\n \t}\n@@ -2284,8 +2293,10 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \tstruct pack_entry e;\n \n \tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n \t\tstruct packed_git **cache;\n+\t\tif (!files)\n+\t\t\tcontinue;\n \n \t\tcache = packfile_store_get_kept_pack_cache(files->packed, flags);\n \ndiff --git a/packfile.h b/packfile.h\nindex 8b04a258a7..3859415e4f 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -193,8 +193,11 @@ static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct\n \todb_prepare_alternates(repo->objects);\n \n \tfor (struct odb_source *source = repo->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tstruct packfile_list_entry *entry = packfile_store_get_packs(files->packed);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tstruct packfile_list_entry *entry;\n+\t\tif (!files)\n+\t\t\tcontinue;\n+\t\tentry = packfile_store_get_packs(files->packed);\n \t\tif (!entry)\n \t\t\tcontinue;\n \t\tdata.source = source;\n@@ -214,8 +217,11 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *\n \t\treturn;\n \n \tfor (source = data->source->next; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tstruct packfile_list_entry *entry = packfile_store_get_packs(files->packed);\n+\t\tstruct odb_source_files *files = odb_source_files_try(source);\n+\t\tstruct packfile_list_entry *entry;\n+\t\tif (!files)\n+\t\t\tcontinue;\n+\t\tentry = packfile_store_get_packs(files->packed);\n \t\tif (!entry)\n \t\t\tcontinue;\n \t\tdata->source = source;\ndiff --git a/t/meson.build b/t/meson.build\nindex f66a73f8a0..e689ee49c7 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -6,6 +6,7 @@ clar_test_suites = [\n   'unit-tests/u-hashmap.c',\n   'unit-tests/u-list-objects-filter-options.c',\n   'unit-tests/u-mem-pool.c',\n+  'unit-tests/u-odb-source.c',\n   'unit-tests/u-oid-array.c',\n   'unit-tests/u-oidmap.c',\n   'unit-tests/u-oidtree.c',\ndiff --git a/t/unit-tests/u-odb-source.c b/t/unit-tests/u-odb-source.c\nnew file mode 100644\nindex 0000000000..6f11c5342c\n--- /dev/null\n+++ b/t/unit-tests/u-odb-source.c\n@@ -0,0 +1,25 @@\n+#include \"unit-test.h\"\n+#include \"odb/source.h\"\n+#include \"odb/source-files.h\"\n+\n+/*\n+ * Verify that odb_source_files_try() returns the files backend\n+ * for ODB_SOURCE_FILES and NULL for other source types.\n+ */\n+void test_odb_source__try_returns_files_for_files_type(void)\n+{\n+\tstruct odb_source_files files_src;\n+\tmemset(&files_src, 0, sizeof(files_src));\n+\tfiles_src.base.type = ODB_SOURCE_FILES;\n+\n+\tcl_assert(odb_source_files_try(&files_src.base) == &files_src);\n+}\n+\n+void test_odb_source__try_returns_null_for_unknown_type(void)\n+{\n+\tstruct odb_source other_src;\n+\tmemset(&other_src, 0, sizeof(other_src));\n+\tother_src.type = ODB_SOURCE_UNKNOWN;\n+\n+\tcl_assert(odb_source_files_try(&other_src) == NULL);\n+}\n-- \ngitgitgadget\n"},{"id":"539378","messageId":"abvKgsCe1V5g6c0s@pks.im","threadId":"65269","inReplyTo":"a8303b1427a41fd4b3ca107eabc49e8ac6d02410.1773674983.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] odb: use odb_source_files_try() in source-chain iterations","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-03-19T10:05:54Z","receivedAt":"2026-03-19T10:06:00Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Mar 16, 2026 at 03:29:43PM +0000, Aaron Paterson via GitGitGadget wrote:\n> From: Aaron Paterson <apaterson@pm.me>\n> \n> Convert all source-chain iteration sites that access\n> files-specific internals (pack store, loose cache, MIDX) to use\n> odb_source_files_try() instead of odb_source_files_downcast().\n\nI have to wonder what the motivation for this is. We don't have any\nother sources (yet), and we're still in the process of bootstrapping\npluggable object databases. So it's expected that things won't fully\nwork yet that we'll die in lots of places if an alternate backend was in\nuse.\n\nBut the solution to this isn't to simply skip non-files backends, but\nrather to adapt those sites so that they are properly abstracted.\n\n> These loops iterate the full source chain, which may include\n> alternates. When a non-files backend is added to the chain (as an\n> alternate or additional source), these sites must skip it rather\n> than abort. The _try() helper returns NULL for non-files sources,\n> and each site checks for NULL before accessing files-specific\n> members.\n\nThe big question here is whether skipping is actually the correct thing\nto do, and in most cases I would claim it's probably not.\n\nI'm a bit puzzled.\n\nPatrick\n"}]}