{"thread":{"id":"64809","subject":"[PATCH 00/14] odb: introduce `odb_for_each_object()`","startedAt":"2026-01-15T11:04:57Z","lastAt":"2026-02-20T22:59:25Z","messageCount":120,"participants":["Patrick Steinhardt","Junio C Hamano","Justin Tobler","Karthik Nayak","Jeff King","Taylor Blau","Chris Torek"],"isPatch":true,"patchVersion":1,"patchTotal":14},"messages":[{"id":"533927","messageId":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":null,"subject":"[PATCH 00/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:29Z","receivedAt":"2026-01-15T11:04:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis patch series introduces a generic `odb_for_each_object()` function\nto iterate through objects and adapts callers to use it. The intent is\nto make iteration through objects independent of the actual storage\nbackend.\n\nThe series is structured as follows:\n\n  - Commits 1 to 2 do some cleanups for the for-each-object flags.\n\n  - Commits 3 to 7 introduce the infrastructure for\n    `odb_for_each_object()`.\n\n  - Commits 8 to 13 convert a couple of callers to use the new\n    interfaces.\n\n  - Commit 14 drops now-unused functions.\n\nThe patch series is built on top of 8745eae506 (The 17th batch,\n2026-01-11) with the following two series merged into it:\n\n  - ps/read-object-info-improvements at b7f649ca93 (Merge\n    remote-tracking branch 'junio/ps/read-object-info-improvements' into\n    HEAD, 2026-01-15).\n\n  - ps/packfile-store-in-odb-source at 1ff0e42d33 (Merge remote-tracking\n    branch 'junio/ps/packfile-store-in-odb-source' into HEAD,\n    2026-01-15).\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (14):\n      odb: rename `FOR_EACH_OBJECT_*` flags\n      odb: fix flags parameter to be unsigned\n      object-file: extract function to read object info from path\n      object-file: introduce function to iterate through objects\n      packfile: extract function to iterate through objects of a store\n      packfile: introduce function to iterate through objects\n      odb: introduce `odb_for_each_object()`\n      builtin/fsck: refactor to use `odb_for_each_object()`\n      treewide: enumerate promisor objects via `odb_for_each_object()`\n      treewide: drop uses of `for_each_{loose,packed}_object()`\n      odb: introduce mtime fields for object info requests\n      builtin/pack-objects: use `packfile_store_for_each_object()`\n      reachable: convert to use `odb_for_each_object()`\n      odb: drop unused `for_each_{loose,packed}_object()` functions\n\n builtin/cat-file.c     |  30 +++++++--\n builtin/fsck.c         |  57 ++++------------\n builtin/pack-objects.c |  47 +++++++-------\n commit-graph.c         |  46 +++++++++----\n object-file.c          | 120 ++++++++++++++++++++++------------\n object-file.h          |  21 +++---\n odb.c                  |  29 +++++++++\n odb.h                  |  43 ++++++++++--\n packfile.c             | 173 +++++++++++++++++++++++++++++++++----------------\n packfile.h             |  18 ++++-\n reachable.c            | 129 +++++++++++-------------------------\n repack-promisor.c      |   8 +--\n revision.c             |  10 ++-\n 13 files changed, 420 insertions(+), 311 deletions(-)\n\n\n---\nbase-commit: 1ff0e42d332523a11cc3d61b8d8463db5f9f14e8\nchange-id: 20260115-pks-odb-for-each-object-60b78cde09fd\n\n"},{"id":"533928","messageId":"20260115-pks-odb-for-each-object-v1-1-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 01/14] odb: rename `FOR_EACH_OBJECT_*` flags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:30Z","receivedAt":"2026-01-15T11:04:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename the `FOR_EACH_OBJECT_*` flags to have an `ODB_` prefix. This\nprepares us for a new upcoming `odb_for_each_object()` function and\nensures that both the function and its flags have the same prefix.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     |  2 +-\n builtin/pack-objects.c | 10 +++++-----\n commit-graph.c         |  4 ++--\n object-file.c          |  4 ++--\n object-file.h          |  2 +-\n odb.h                  | 13 +++++++------\n packfile.c             | 20 ++++++++++----------\n packfile.h             |  4 ++--\n reachable.c            |  8 ++++----\n repack-promisor.c      |  2 +-\n revision.c             |  2 +-\n 11 files changed, 36 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 2ad712e9f8..6964a5a52c 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -922,7 +922,7 @@ static int batch_objects(struct batch_options *opt)\n \t\t\tcb.seen = &seen;\n \n \t\t\tbatch_each_object(opt, batch_unordered_object,\n-\t\t\t\t\t  FOR_EACH_OBJECT_PACK_ORDER, &cb);\n+\t\t\t\t\t  ODB_FOR_EACH_OBJECT_PACK_ORDER, &cb);\n \n \t\t\toidset_clear(&seen);\n \t\t} else {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 6ee31d48c9..74317051fd 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3912,7 +3912,7 @@ static void read_packs_list_from_stdin(struct rev_info *revs)\n \t\tfor_each_object_in_pack(p,\n \t\t\t\t\tadd_object_entry_from_pack,\n \t\t\t\t\trevs,\n-\t\t\t\t\tFOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\tODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t}\n \n \tstrbuf_release(&buf);\n@@ -4344,10 +4344,10 @@ static void add_objects_in_unpacked_packs(void)\n \tif (for_each_packed_object(to_pack.repo,\n \t\t\t\t   add_object_in_unpacked_pack,\n \t\t\t\t   NULL,\n-\t\t\t\t   FOR_EACH_OBJECT_PACK_ORDER |\n-\t\t\t\t   FOR_EACH_OBJECT_LOCAL_ONLY |\n-\t\t\t\t   FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n-\t\t\t\t   FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n \t\tdie(_(\"cannot open pack index\"));\n }\n \ndiff --git a/commit-graph.c b/commit-graph.c\nindex 6b1f02e179..7f1145a082 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1927,7 +1927,7 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,\n \t\t\tgoto cleanup;\n \t\t}\n \t\tfor_each_object_in_pack(p, add_packed_commits, ctx,\n-\t\t\t\t\tFOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\tODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\tclose_pack(p);\n \t\tfree(p);\n \t}\n@@ -1965,7 +1965,7 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n \t\t\t_(\"Finding commits for commit graph among packed objects\"),\n \t\t\tctx->approx_nr_objects);\n \tfor_each_packed_object(ctx->r, add_packed_commits, ctx,\n-\t\t\t       FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \tif (ctx->progress_done < ctx->approx_nr_objects)\n \t\tdisplay_progress(ctx->progress, ctx->approx_nr_objects);\n \tstop_progress(&ctx->progress);\ndiff --git a/object-file.c b/object-file.c\nindex e7e4c3348f..64e9e239dc 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1789,7 +1789,7 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \n int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn cb, void *data,\n-\t\t\t  enum for_each_object_flags flags)\n+\t\t\t  enum odb_for_each_object_flags flags)\n {\n \tstruct odb_source *source;\n \n@@ -1800,7 +1800,7 @@ int for_each_loose_object(struct object_database *odb,\n \t\tif (r)\n \t\t\treturn r;\n \n-\t\tif (flags & FOR_EACH_OBJECT_LOCAL_ONLY)\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY)\n \t\t\tbreak;\n \t}\n \ndiff --git a/object-file.h b/object-file.h\nindex 1229d5f675..42bb50e10c 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -134,7 +134,7 @@ int for_each_loose_file_in_source(struct odb_source *source,\n  */\n int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn, void *,\n-\t\t\t  enum for_each_object_flags flags);\n+\t\t\t  enum odb_for_each_object_flags flags);\n \n \n /**\ndiff --git a/odb.h b/odb.h\nindex bab07755f4..74503addf1 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -442,24 +442,25 @@ static inline void obj_read_unlock(void)\n \tif(obj_read_use_lock)\n \t\tpthread_mutex_unlock(&obj_read_mutex);\n }\n+\n /* Flags for for_each_*_object(). */\n-enum for_each_object_flags {\n+enum odb_for_each_object_flags {\n \t/* Iterate only over local objects, not alternates. */\n-\tFOR_EACH_OBJECT_LOCAL_ONLY = (1<<0),\n+\tODB_FOR_EACH_OBJECT_LOCAL_ONLY = (1<<0),\n \n \t/* Only iterate over packs obtained from the promisor remote. */\n-\tFOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1),\n+\tODB_FOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1),\n \n \t/*\n \t * Visit objects within a pack in packfile order rather than .idx order\n \t */\n-\tFOR_EACH_OBJECT_PACK_ORDER = (1<<2),\n+\tODB_FOR_EACH_OBJECT_PACK_ORDER = (1<<2),\n \n \t/* Only iterate over packs that are not marked as kept in-core. */\n-\tFOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),\n+\tODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),\n \n \t/* Only iterate over packs that do not have .keep files. */\n-\tFOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n+\tODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n };\n \n enum {\ndiff --git a/packfile.c b/packfile.c\nindex 402c3b5dc7..b65f0b43f1 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2259,12 +2259,12 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn cb, void *data,\n-\t\t\t    enum for_each_object_flags flags)\n+\t\t\t    enum odb_for_each_object_flags flags)\n {\n \tuint32_t i;\n \tint r = 0;\n \n-\tif (flags & FOR_EACH_OBJECT_PACK_ORDER) {\n+\tif (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER) {\n \t\tif (load_pack_revindex(p->repo, p))\n \t\t\treturn -1;\n \t}\n@@ -2285,7 +2285,7 @@ int for_each_object_in_pack(struct packed_git *p,\n \t\t *   - in pack-order, it is pack position, which we must\n \t\t *     convert to an index position in order to get the oid.\n \t\t */\n-\t\tif (flags & FOR_EACH_OBJECT_PACK_ORDER)\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER)\n \t\t\tindex_pos = pack_pos_to_index(p, i);\n \t\telse\n \t\t\tindex_pos = i;\n@@ -2302,7 +2302,7 @@ int for_each_object_in_pack(struct packed_git *p,\n }\n \n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum for_each_object_flags flags)\n+\t\t\t   void *data, enum odb_for_each_object_flags flags)\n {\n \tstruct odb_source *source;\n \tint r = 0;\n@@ -2318,15 +2318,15 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\tfor (e = packfile_store_get_packs(source->packfiles); e; e = e->next) {\n \t\t\tstruct packed_git *p = e->pack;\n \n-\t\t\tif ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n \t\t\t    !p->pack_promisor)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n \t\t\t    p->pack_keep_in_core)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n \t\t\t    p->pack_keep)\n \t\t\t\tcontinue;\n \t\t\tif (open_pack_index(p)) {\n@@ -2413,8 +2413,8 @@ int is_promisor_object(struct repository *r, const struct object_id *oid)\n \t\tif (repo_has_promisor_remote(r)) {\n \t\t\tfor_each_packed_object(r, add_promisor_object,\n \t\t\t\t\t       &promisor_objects,\n-\t\t\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY |\n-\t\t\t\t\t       FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY |\n+\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\t}\n \t\tpromisor_objects_prepared = 1;\n \t}\ndiff --git a/packfile.h b/packfile.h\nindex acc5c55ad5..15551258bd 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -339,9 +339,9 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n \t\t\t\t  void *data);\n 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 for_each_object_flags flags);\n+\t\t\t    enum odb_for_each_object_flags flags);\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum for_each_object_flags flags);\n+\t\t\t   void *data, enum odb_for_each_object_flags flags);\n \n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\ndiff --git a/reachable.c b/reachable.c\nindex 4b532039d5..82676b2668 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -307,7 +307,7 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \t\t\t\t\t   int ignore_in_core_kept_packs)\n {\n \tstruct recent_data data;\n-\tenum for_each_object_flags flags;\n+\tenum odb_for_each_object_flags flags;\n \tint r;\n \n \tdata.revs = revs;\n@@ -319,13 +319,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \tdata.extra_recent_oids_loaded = 0;\n \n \tr = for_each_loose_object(the_repository->objects, add_recent_loose, &data,\n-\t\t\t\t  FOR_EACH_OBJECT_LOCAL_ONLY);\n+\t\t\t\t  ODB_FOR_EACH_OBJECT_LOCAL_ONLY);\n \tif (r)\n \t\tgoto done;\n \n-\tflags = FOR_EACH_OBJECT_LOCAL_ONLY | FOR_EACH_OBJECT_PACK_ORDER;\n+\tflags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER;\n \tif (ignore_in_core_kept_packs)\n-\t\tflags |= FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n+\t\tflags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n \n \tr = for_each_packed_object(revs->repo, add_recent_packed, &data, flags);\n \ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex ee6e0669f6..45c330b9a5 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -56,7 +56,7 @@ void repack_promisor_objects(struct repository *repo,\n \tctx.cmd = &cmd;\n \tctx.algop = repo->hash_algo;\n \tfor_each_packed_object(repo, write_oid, &ctx,\n-\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n \t\t/* No packed objects; cmd was never started */\ndiff --git a/revision.c b/revision.c\nindex b65a763770..5aadf46dac 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3938,7 +3938,7 @@ int prepare_revision_walk(struct rev_info *revs)\n \n \tif (revs->exclude_promisor_objects) {\n \t\tfor_each_packed_object(revs->repo, mark_uninteresting, revs,\n-\t\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \t}\n \n \tif (!revs->reflog_info)\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533929","messageId":"20260115-pks-odb-for-each-object-v1-2-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 02/14] odb: fix flags parameter to be unsigned","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:31Z","receivedAt":"2026-01-15T11:05:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `flags` parameter accepted by various `for_each_object()` functions\nis a bitfield of multiple flags. Such parameters are typically unsigned\nin the Git codebase, but we use `enum odb_for_each_object_flags` in\nsome places.\n\nAdapt these function signatures to use the correct type.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 3 ++-\n object-file.h | 3 ++-\n packfile.c    | 4 ++--\n packfile.h    | 4 ++--\n 4 files changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 64e9e239dc..8fa461dd59 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -414,7 +414,8 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)\n \n int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\t\t      const struct object_id *oid,\n-\t\t\t\t      struct object_info *oi, int flags)\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags)\n {\n \tint ret;\n \tint fd;\ndiff --git a/object-file.h b/object-file.h\nindex 42bb50e10c..2acf19fb91 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -47,7 +47,8 @@ void odb_source_loose_reprepare(struct odb_source *source);\n \n int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\t\t      const struct object_id *oid,\n-\t\t\t\t      struct object_info *oi, int flags);\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags);\n \n int odb_source_loose_read_object_stream(struct odb_read_stream **out,\n \t\t\t\t\tstruct odb_source *source,\ndiff --git a/packfile.c b/packfile.c\nindex b65f0b43f1..79fe64a25b 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2259,7 +2259,7 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn cb, void *data,\n-\t\t\t    enum odb_for_each_object_flags flags)\n+\t\t\t    unsigned flags)\n {\n \tuint32_t i;\n \tint r = 0;\n@@ -2302,7 +2302,7 @@ int for_each_object_in_pack(struct packed_git *p,\n }\n \n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum odb_for_each_object_flags flags)\n+\t\t\t   void *data, unsigned flags)\n {\n \tstruct odb_source *source;\n \tint r = 0;\ndiff --git a/packfile.h b/packfile.h\nindex 15551258bd..447c44c4a7 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -339,9 +339,9 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n \t\t\t\t  void *data);\n 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+\t\t\t    unsigned flags);\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum odb_for_each_object_flags flags);\n+\t\t\t   void *data, unsigned flags);\n \n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533930","messageId":"20260115-pks-odb-for-each-object-v1-3-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 03/14] object-file: extract function to read object info from path","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:32Z","receivedAt":"2026-01-15T11:05:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Extract a new function that allows us to read object info for a specific\nloose object via a user-supplied path. This function will be used in a\nsubsequent commit.\n\nNote that this also allows us to drop `stat_loose_object()`, which is\na simple wrapper around `odb_loose_path()` plus lstat(3p).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 39 ++++++++++++++++-----------------------\n 1 file changed, 16 insertions(+), 23 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 8fa461dd59..a651129426 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -165,30 +165,13 @@ int stream_object_signature(struct repository *r, const struct object_id *oid)\n }\n \n /*\n- * Find \"oid\" as a loose object in given source.\n- * Returns 0 on success, negative on failure.\n+ * Find \"oid\" as a loose object in given source, open the object and return its\n+ * file descriptor. Returns the file descriptor on success, negative on failure.\n  *\n  * The \"path\" out-parameter will give the path of the object we found (if any).\n  * Note that it may point to static storage and is only valid until another\n  * call to stat_loose_object().\n  */\n-static int stat_loose_object(struct odb_source_loose *loose,\n-\t\t\t     const struct object_id *oid,\n-\t\t\t     struct stat *st, const char **path)\n-{\n-\tstatic struct strbuf buf = STRBUF_INIT;\n-\n-\t*path = odb_loose_path(loose->source, &buf, oid);\n-\tif (!lstat(*path, st))\n-\t\treturn 0;\n-\n-\treturn -1;\n-}\n-\n-/*\n- * Like stat_loose_object(), but actually open the object and return the\n- * descriptor. See the caveats on the \"path\" parameter above.\n- */\n static int open_loose_object(struct odb_source_loose *loose,\n \t\t\t     const struct object_id *oid, const char **path)\n {\n@@ -412,7 +395,8 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)\n \treturn 0;\n }\n \n-int odb_source_loose_read_object_info(struct odb_source *source,\n+static int read_object_info_from_path(struct odb_source *source,\n+\t\t\t\t      const char *path,\n \t\t\t\t      const struct object_id *oid,\n \t\t\t\t      struct object_info *oi,\n \t\t\t\t      unsigned flags)\n@@ -420,7 +404,6 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \tint ret;\n \tint fd;\n \tunsigned long mapsize;\n-\tconst char *path;\n \tvoid *map = NULL;\n \tgit_zstream stream, *stream_to_end = NULL;\n \tchar hdr[MAX_HEADER_LEN];\n@@ -443,7 +426,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (stat_loose_object(source->loose, oid, &st, &path) < 0) {\n+\t\tif (lstat(path, &st) < 0) {\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n@@ -455,7 +438,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \t\tgoto out;\n \t}\n \n-\tfd = open_loose_object(source->loose, oid, &path);\n+\tfd = git_open(path);\n \tif (fd < 0) {\n \t\tif (errno != ENOENT)\n \t\t\terror_errno(_(\"unable to open loose object %s\"), oid_to_hex(oid));\n@@ -534,6 +517,16 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \treturn ret;\n }\n \n+int odb_source_loose_read_object_info(struct odb_source *source,\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags)\n+{\n+\tstatic struct strbuf buf = STRBUF_INIT;\n+\todb_loose_path(source, &buf, oid);\n+\treturn read_object_info_from_path(source, buf.buf, oid, oi, flags);\n+}\n+\n static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c,\n \t\t\t     const void *buf, unsigned long len,\n \t\t\t     struct object_id *oid,\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533931","messageId":"20260115-pks-odb-for-each-object-v1-4-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 04/14] object-file: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:33Z","receivedAt":"2026-01-15T11:05:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have multiple divergent interfaces to iterate through objects of a\nspecific backend:\n\n  - `for_each_loose_object()` yields all loose objects.\n\n  - `for_each_packed_object()` (somewhat obviously) yields all packed\n    objects.\n\nThese functions have different function signatures, which makes it hard\nto create a common abstraction layer that covers both of these.\n\nIntroduce a new function `odb_source_loose_for_each_object()` to plug\nthis gap. This function doesn't take any data specific to loose objects,\nbut instead it accepts a `struct object_info` that will be populated the\nexact same as if `odb_source_loose_read_object()` was called.\n\nThe benefit of this new interface is that we can continue to pass\nbackend-specific data, as `struct object_info` contains a union for\nthese exact use cases. This will allow us to unify how we iterate\nthrough objects across both loose and packed objects in a subsequent\ncommit.\n\nThe `for_each_loose_object()` function continues to exist for now, but\nit will be removed at the end of this patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 41 +++++++++++++++++++++++++++++++++++++++++\n object-file.h | 11 +++++++++++\n odb.h         | 12 ++++++++++++\n 3 files changed, 64 insertions(+)\n\ndiff --git a/object-file.c b/object-file.c\nindex a651129426..65e730684b 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1801,6 +1801,47 @@ int for_each_loose_object(struct object_database *odb,\n \treturn 0;\n }\n \n+struct for_each_object_wrapper_data {\n+\tstruct odb_source *source;\n+\tstruct object_info *oi;\n+\todb_for_each_object_cb cb;\n+\tvoid *cb_data;\n+};\n+\n+static int for_each_object_wrapper_cb(const struct object_id *oid,\n+\t\t\t\t      const char *path,\n+\t\t\t\t      void *cb_data)\n+{\n+\tstruct for_each_object_wrapper_data *data = cb_data;\n+\tif (data->oi &&\n+\t    read_object_info_from_path(data->source, path, oid, data->oi, 0) < 0)\n+\t\t\treturn -1;\n+\treturn data->cb(oid, data->oi, data->cb_data);\n+}\n+\n+int odb_source_loose_for_each_object(struct odb_source *source,\n+\t\t\t\t     struct object_info *oi,\n+\t\t\t\t     odb_for_each_object_cb cb,\n+\t\t\t\t     void *cb_data,\n+\t\t\t\t     unsigned flags)\n+{\n+\tstruct for_each_object_wrapper_data data = {\n+\t\t.source = source,\n+\t\t.oi = oi,\n+\t\t.cb = cb,\n+\t\t.cb_data = cb_data,\n+\t};\n+\n+\t/* There are no loose promisor objects, so we can return immediately. */\n+\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY))\n+\t\treturn 0;\n+\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local)\n+\t\treturn 0;\n+\n+\treturn for_each_loose_file_in_source(source, for_each_object_wrapper_cb,\n+\t\t\t\t\t     NULL, NULL, &data);\n+}\n+\n static int append_loose_object(const struct object_id *oid,\n \t\t\t       const char *path UNUSED,\n \t\t\t       void *data)\ndiff --git a/object-file.h b/object-file.h\nindex 2acf19fb91..048b778531 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -137,6 +137,17 @@ int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn, void *,\n \t\t\t  enum odb_for_each_object_flags flags);\n \n+/*\n+ * Iterate through all loose objects in the given object database source and\n+ * invoke the callback function for each of them. If given, the object info\n+ * will be populated with the object's data as if you had called\n+ * `odb_source_loose_read_object_info()` on the object.\n+ */\n+int odb_source_loose_for_each_object(struct odb_source *source,\n+\t\t\t\t     struct object_info *oi,\n+\t\t\t\t     odb_for_each_object_cb cb,\n+\t\t\t\t     void *cb_data,\n+\t\t\t\t     unsigned flags);\n \n /**\n  * format_object_header() is a thin wrapper around s xsnprintf() that\ndiff --git a/odb.h b/odb.h\nindex 74503addf1..f97f249580 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -463,6 +463,18 @@ enum odb_for_each_object_flags {\n \tODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n };\n \n+/*\n+ * A callback function that can be used to iterate through objects. If given,\n+ * the optional `oi` parameter will be populated the same as if you would call\n+ * `odb_read_object_info()`.\n+ *\n+ * Returning a non-zero error code will cause iteration to abort. The error\n+ * code will be propagated.\n+ */\n+typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      void *cb_data);\n+\n enum {\n \t/*\n \t * By default, `odb_write_object()` does not actually write anything\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533932","messageId":"20260115-pks-odb-for-each-object-v1-5-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 05/14] packfile: extract function to iterate through objects of a store","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:34Z","receivedAt":"2026-01-15T11:05:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In the next commit we're about to introduce a new function that knows to\niterate through objects of a given packfile store. Same as with the\nequivalent function for loose objects, this new function will also be\nagnostic of backends by using a `struct object_info`.\n\nPrepare for this by extracting a new shared function to iterate through\na single packfile store.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 78 ++++++++++++++++++++++++++++++++++++--------------------------\n 1 file changed, 45 insertions(+), 33 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex 79fe64a25b..d15a2ce12b 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2301,51 +2301,63 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags)\n+static int packfile_store_for_each_object_internal(struct packfile_store *store,\n+\t\t\t\t\t\t   each_packed_object_fn cb,\n+\t\t\t\t\t\t   void *data,\n+\t\t\t\t\t\t   unsigned flags,\n+\t\t\t\t\t\t   int *pack_errors)\n {\n-\tstruct odb_source *source;\n-\tint r = 0;\n-\tint pack_errors = 0;\n+\tstruct packfile_list_entry *e;\n+\tint ret = 0;\n \n-\todb_prepare_alternates(repo->objects);\n+\tstore->skip_mru_updates = true;\n \n-\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\tstruct packfile_list_entry *e;\n+\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n+\t\tstruct packed_git *p = e->pack;\n \n-\t\tsource->packfiles->skip_mru_updates = true;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((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 ((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\t*pack_errors = 1;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\tfor (e = packfile_store_get_packs(source->packfiles); e; e = e->next) {\n-\t\t\tstruct packed_git *p = e->pack;\n+\t\tret = for_each_object_in_pack(p, cb, data, flags);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n \n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t\t    !p->pack_promisor)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n-\t\t\t    p->pack_keep_in_core)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n-\t\t\t    p->pack_keep)\n-\t\t\t\tcontinue;\n-\t\t\tif (open_pack_index(p)) {\n-\t\t\t\tpack_errors = 1;\n-\t\t\t\tcontinue;\n-\t\t\t}\n+\tstore->skip_mru_updates = false;\n \n-\t\t\tr = for_each_object_in_pack(p, cb, data, flags);\n-\t\t\tif (r)\n-\t\t\t\tbreak;\n-\t\t}\n+\treturn ret;\n+}\n \n-\t\tsource->packfiles->skip_mru_updates = false;\n+int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n+\t\t\t   void *data, unsigned flags)\n+{\n+\tstruct odb_source *source;\n+\tint pack_errors = 0;\n+\tint ret = 0;\n \n-\t\tif (r)\n+\todb_prepare_alternates(repo->objects);\n+\n+\tfor (source = repo->objects->sources; source; source = source->next) {\n+\t\tret = packfile_store_for_each_object_internal(source->packfiles, cb, data,\n+\t\t\t\t\t\t\t      flags, &pack_errors);\n+\t\tif (ret)\n \t\t\tbreak;\n \t}\n \n-\treturn r ? r : pack_errors;\n+\treturn ret ? ret : pack_errors;\n }\n \n static int add_promisor_object(const struct object_id *oid,\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533934","messageId":"20260115-pks-odb-for-each-object-v1-6-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 06/14] packfile: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:35Z","receivedAt":"2026-01-15T11:05:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Introduce a new function `packfile_store_for_each_object()`. This\nfunction is the equivalent to `odb_source_loose_for_each_object()` in\nthat it:\n\n  - Works on a single packfile store and thus per object source.\n\n  - Passes a `struct object_info` to the callback function.\n\nAs such, it provides the same callback interface as we already provide\nfor loose objects now. These functions will be used in a subsequent step\nto implement `odb_for_each_object()`.\n\nThe `for_each_packed_object()` function continues to exist for now, but\nit will be removed at the end of this patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.h | 14 ++++++++++++++\n 2 files changed, 62 insertions(+)\n\ndiff --git a/packfile.c b/packfile.c\nindex d15a2ce12b..cd45c6f21c 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2360,6 +2360,54 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \treturn ret ? ret : pack_errors;\n }\n \n+struct packfile_store_for_each_object_wrapper_data {\n+\tstruct packfile_store *store;\n+\tstruct object_info *oi;\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 packfile_store_for_each_object_wrapper_data *data = cb_data;\n+\n+\tif (data->oi) {\n+\t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n+\n+\t\tif (packed_object_info(pack, offset, data->oi) < 0) {\n+\t\t\tmark_bad_packed_object(pack, oid);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\treturn data->cb(oid, data->oi, data->cb_data);\n+}\n+\n+int packfile_store_for_each_object(struct packfile_store *store,\n+\t\t\t\t   struct object_info *oi,\n+\t\t\t\t   odb_for_each_object_cb cb,\n+\t\t\t\t   void *cb_data,\n+\t\t\t\t   unsigned flags)\n+{\n+\tstruct packfile_store_for_each_object_wrapper_data data = {\n+\t\t.store = store,\n+\t\t.oi = oi,\n+\t\t.cb = cb,\n+\t\t.cb_data = cb_data,\n+\t};\n+\tint pack_errors = 0, ret;\n+\n+\tret = packfile_store_for_each_object_internal(store, packfile_store_for_each_object_wrapper,\n+\t\t\t\t\t\t      &data, flags, &pack_errors);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn pack_errors ? -1 : 0;\n+}\n+\n static int add_promisor_object(const struct object_id *oid,\n \t\t\t       struct packed_git *pack,\n \t\t\t       uint32_t pos UNUSED,\ndiff --git a/packfile.h b/packfile.h\nindex 447c44c4a7..ab0637fbe9 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -343,6 +343,20 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\t\t   void *data, unsigned 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 given, the object info will be\n+ * populated with the object's data as if you had called\n+ * `packfile_store_read_object_info()` on the object.\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+\t\t\t\t   struct object_info *oi,\n+\t\t\t\t   odb_for_each_object_cb cb,\n+\t\t\t\t   void *cb_data,\n+\t\t\t\t   unsigned flags);\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.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533933","messageId":"20260115-pks-odb-for-each-object-v1-7-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 07/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:36Z","receivedAt":"2026-01-15T11:05:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Introduce a new function `odb_for_each_object()` that knows to iterate\nthrough all objects part of a given object database. This function is\nessentially a simple wrapper around the object database sources.\n\nSubsequent commits will adapt callers to use this new function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c | 27 +++++++++++++++++++++++++++\n odb.h | 17 +++++++++++++++++\n 2 files changed, 44 insertions(+)\n\ndiff --git a/odb.c b/odb.c\nindex ac70b6a099..65f0447aa5 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -995,6 +995,33 @@ int odb_freshen_object(struct object_database *odb,\n \treturn 0;\n }\n \n+int odb_for_each_object(struct object_database *odb,\n+\t\t\tstruct object_info *oi,\n+\t\t\todb_for_each_object_cb cb,\n+\t\t\tvoid *cb_data,\n+\t\t\tunsigned flags)\n+{\n+\tint ret;\n+\n+\todb_prepare_alternates(odb);\n+\tfor (struct odb_source *source = odb->sources; source; source = source->next) {\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local)\n+\t\t\tcontinue;\n+\n+\t\tif (!(flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {\n+\t\t\tret = odb_source_loose_for_each_object(source, oi, cb, cb_data, flags);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\n+\t\tret = packfile_store_for_each_object(source->packfiles, oi, cb, cb_data, flags);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n void odb_assert_oid_type(struct object_database *odb,\n \t\t\t const struct object_id *oid, enum object_type expect)\n {\ndiff --git a/odb.h b/odb.h\nindex f97f249580..8f6d95aee5 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -475,6 +475,23 @@ typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n \t\t\t\t      struct object_info *oi,\n \t\t\t\t      void *cb_data);\n \n+/*\n+ * Iterate through all objects contained in the object database. Note that\n+ * objects may be iterated over multiple times in case they are either stored\n+ * in different backends or in case they are stored in multiple sources.\n+ *\n+ * Returning a non-zero error code will cause iteration to abort. The error\n+ * code will be propagated.\n+ *\n+ * Returns 0 on success, a negative error code in case a failure occurred, or\n+ * an arbitrary non-zero error code returned by the callback itself.\n+ */\n+int odb_for_each_object(struct object_database *odb,\n+\t\t\tstruct object_info *oi,\n+\t\t\todb_for_each_object_cb cb,\n+\t\t\tvoid *cb_data,\n+\t\t\tunsigned flags);\n+\n enum {\n \t/*\n \t * By default, `odb_write_object()` does not actually write anything\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533935","messageId":"20260115-pks-odb-for-each-object-v1-8-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 08/14] builtin/fsck: refactor to use `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:37Z","receivedAt":"2026-01-15T11:05:17Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In git-fsck(1) we have two callsites where we iterate over all objects\nvia `for_each_loose_object()` and `for_each_packed_object()`. Both of\nthese are trivially convertible with `odb_for_each_object()`.\n\nRefactor these callsites accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 57 ++++++++++++---------------------------------------------\n 1 file changed, 12 insertions(+), 45 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 4979bc795e..96107695ae 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -218,15 +218,17 @@ static int mark_used(struct object *obj, enum object_type type UNUSED,\n \treturn 0;\n }\n \n-static void mark_unreachable_referents(const struct object_id *oid)\n+static int mark_unreachable_referents(const struct object_id *oid,\n+\t\t\t\t      struct object_info *io UNUSED,\n+\t\t\t\t      void *data UNUSED)\n {\n \tstruct fsck_options options = FSCK_OPTIONS_DEFAULT;\n \tstruct object *obj = lookup_object(the_repository, oid);\n \n \tif (!obj || !(obj->flags & HAS_OBJ))\n-\t\treturn; /* not part of our original set */\n+\t\treturn 0; /* not part of our original set */\n \tif (obj->flags & REACHABLE)\n-\t\treturn; /* reachable objects already traversed */\n+\t\treturn 0; /* reachable objects already traversed */\n \n \t/*\n \t * Avoid passing OBJ_NONE to fsck_walk, which will parse the object\n@@ -243,22 +245,7 @@ static void mark_unreachable_referents(const struct object_id *oid)\n \tfsck_walk(obj, NULL, &options);\n \tif (obj->type == OBJ_TREE)\n \t\tfree_tree_buffer((struct tree *)obj);\n-}\n \n-static int mark_loose_unreachable_referents(const struct object_id *oid,\n-\t\t\t\t\t    const char *path UNUSED,\n-\t\t\t\t\t    void *data UNUSED)\n-{\n-\tmark_unreachable_referents(oid);\n-\treturn 0;\n-}\n-\n-static int mark_packed_unreachable_referents(const struct object_id *oid,\n-\t\t\t\t\t     struct packed_git *pack UNUSED,\n-\t\t\t\t\t     uint32_t pos UNUSED,\n-\t\t\t\t\t     void *data UNUSED)\n-{\n-\tmark_unreachable_referents(oid);\n \treturn 0;\n }\n \n@@ -394,12 +381,8 @@ static void check_connectivity(void)\n \t\t * and ignore any that weren't present in our earlier\n \t\t * traversal.\n \t\t */\n-\t\tfor_each_loose_object(the_repository->objects,\n-\t\t\t\t      mark_loose_unreachable_referents, NULL, 0);\n-\t\tfor_each_packed_object(the_repository,\n-\t\t\t\t       mark_packed_unreachable_referents,\n-\t\t\t\t       NULL,\n-\t\t\t\t       0);\n+\t\todb_for_each_object(the_repository->objects, NULL,\n+\t\t\t\t    mark_unreachable_referents, NULL, 0);\n \t}\n \n \t/* Look up all the requirements, warn about missing objects.. */\n@@ -848,26 +831,12 @@ static void fsck_index(struct index_state *istate, const char *index_path,\n \tfsck_resolve_undo(istate, index_path);\n }\n \n-static void mark_object_for_connectivity(const struct object_id *oid)\n+static int mark_object_for_connectivity(const struct object_id *oid,\n+\t\t\t\t\tstruct object_info *oi UNUSED,\n+\t\t\t\t\tvoid *cb_data UNUSED)\n {\n \tstruct object *obj = lookup_unknown_object(the_repository, oid);\n \tobj->flags |= HAS_OBJ;\n-}\n-\n-static int mark_loose_for_connectivity(const struct object_id *oid,\n-\t\t\t\t       const char *path UNUSED,\n-\t\t\t\t       void *data UNUSED)\n-{\n-\tmark_object_for_connectivity(oid);\n-\treturn 0;\n-}\n-\n-static int mark_packed_for_connectivity(const struct object_id *oid,\n-\t\t\t\t\tstruct packed_git *pack UNUSED,\n-\t\t\t\t\tuint32_t pos UNUSED,\n-\t\t\t\t\tvoid *data UNUSED)\n-{\n-\tmark_object_for_connectivity(oid);\n \treturn 0;\n }\n \n@@ -1001,10 +970,8 @@ int cmd_fsck(int argc,\n \t\tfsck_refs(the_repository);\n \n \tif (connectivity_only) {\n-\t\tfor_each_loose_object(the_repository->objects,\n-\t\t\t\t      mark_loose_for_connectivity, NULL, 0);\n-\t\tfor_each_packed_object(the_repository,\n-\t\t\t\t       mark_packed_for_connectivity, NULL, 0);\n+\t\todb_for_each_object(the_repository->objects, NULL,\n+\t\t\t\t    mark_object_for_connectivity, NULL, 0);\n \t} else {\n \t\todb_prepare_alternates(the_repository->objects);\n \t\tfor (source = the_repository->objects->sources; source; source = source->next)\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533937","messageId":"20260115-pks-odb-for-each-object-v1-9-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 09/14] treewide: enumerate promisor objects via `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:38Z","receivedAt":"2026-01-15T11:05:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have multiple callsites where we enumerate all promisor objects in\nthe object database via `for_each_packed_object()`. This is done by\npassing the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY` flag, which causes us to\nskip over all non-promisor objects.\n\nThese callsites can be trivially converted to `odb_for_each_object()` as\nwe know to skip enumeration of loose objects in case the `PROMISOR_ONLY`\nflag was passed by the caller.\n\nRefactor the sites accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c        | 37 ++++++++++++++++++++++---------------\n repack-promisor.c |  8 ++++----\n revision.c        | 10 ++++------\n 3 files changed, 30 insertions(+), 25 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex cd45c6f21c..4f84bc19d9 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2408,28 +2408,32 @@ int packfile_store_for_each_object(struct packfile_store *store,\n \treturn pack_errors ? -1 : 0;\n }\n \n+struct add_promisor_object_data {\n+\tstruct repository *repo;\n+\tstruct oidset *set;\n+};\n+\n static int add_promisor_object(const struct object_id *oid,\n-\t\t\t       struct packed_git *pack,\n-\t\t\t       uint32_t pos UNUSED,\n-\t\t\t       void *set_)\n+\t\t\t       struct object_info *oi UNUSED,\n+\t\t\t       void *cb_data)\n {\n-\tstruct oidset *set = set_;\n+\tstruct add_promisor_object_data *data = cb_data;\n \tstruct object *obj;\n \tint we_parsed_object;\n \n-\tobj = lookup_object(pack->repo, oid);\n+\tobj = lookup_object(data->repo, oid);\n \tif (obj && obj->parsed) {\n \t\twe_parsed_object = 0;\n \t} else {\n \t\twe_parsed_object = 1;\n-\t\tobj = parse_object_with_flags(pack->repo, oid,\n+\t\tobj = parse_object_with_flags(data->repo, oid,\n \t\t\t\t\t      PARSE_OBJECT_SKIP_HASH_CHECK);\n \t}\n \n \tif (!obj)\n \t\treturn 1;\n \n-\toidset_insert(set, oid);\n+\toidset_insert(data->set, oid);\n \n \t/*\n \t * If this is a tree, commit, or tag, the objects it refers\n@@ -2447,19 +2451,19 @@ static int add_promisor_object(const struct object_id *oid,\n \t\t\t */\n \t\t\treturn 0;\n \t\twhile (tree_entry_gently(&desc, &entry))\n-\t\t\toidset_insert(set, &entry.oid);\n+\t\t\toidset_insert(data->set, &entry.oid);\n \t\tif (we_parsed_object)\n \t\t\tfree_tree_buffer(tree);\n \t} else if (obj->type == OBJ_COMMIT) {\n \t\tstruct commit *commit = (struct commit *) obj;\n \t\tstruct commit_list *parents = commit->parents;\n \n-\t\toidset_insert(set, get_commit_tree_oid(commit));\n+\t\toidset_insert(data->set, get_commit_tree_oid(commit));\n \t\tfor (; parents; parents = parents->next)\n-\t\t\toidset_insert(set, &parents->item->object.oid);\n+\t\t\toidset_insert(data->set, &parents->item->object.oid);\n \t} else if (obj->type == OBJ_TAG) {\n \t\tstruct tag *tag = (struct tag *) obj;\n-\t\toidset_insert(set, get_tagged_oid(tag));\n+\t\toidset_insert(data->set, get_tagged_oid(tag));\n \t}\n \treturn 0;\n }\n@@ -2471,10 +2475,13 @@ int is_promisor_object(struct repository *r, const struct object_id *oid)\n \n \tif (!promisor_objects_prepared) {\n \t\tif (repo_has_promisor_remote(r)) {\n-\t\t\tfor_each_packed_object(r, add_promisor_object,\n-\t\t\t\t\t       &promisor_objects,\n-\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY |\n-\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\tstruct add_promisor_object_data data = {\n+\t\t\t\t.repo = r,\n+\t\t\t\t.set = &promisor_objects,\n+\t\t\t};\n+\n+\t\t\todb_for_each_object(r->objects, NULL, add_promisor_object, &data,\n+\t\t\t\t\t    ODB_FOR_EACH_OBJECT_PROMISOR_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\t}\n \t\tpromisor_objects_prepared = 1;\n \t}\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 45c330b9a5..35c4073632 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -17,8 +17,8 @@ struct write_oid_context {\n  * necessary.\n  */\n static int write_oid(const struct object_id *oid,\n-\t\t     struct packed_git *pack UNUSED,\n-\t\t     uint32_t pos UNUSED, void *data)\n+\t\t     struct object_info *oi UNUSED,\n+\t\t     void *data)\n {\n \tstruct write_oid_context *ctx = data;\n \tstruct child_process *cmd = ctx->cmd;\n@@ -55,8 +55,8 @@ void repack_promisor_objects(struct repository *repo,\n \t */\n \tctx.cmd = &cmd;\n \tctx.algop = repo->hash_algo;\n-\tfor_each_packed_object(repo, write_oid, &ctx,\n-\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\todb_for_each_object(repo->objects, NULL, write_oid, &ctx,\n+\t\t\t    ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n \t\t/* No packed objects; cmd was never started */\ndiff --git a/revision.c b/revision.c\nindex 5aadf46dac..e34bcd8e88 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3626,8 +3626,7 @@ void reset_revision_walk(void)\n }\n \n static int mark_uninteresting(const struct object_id *oid,\n-\t\t\t      struct packed_git *pack UNUSED,\n-\t\t\t      uint32_t pos UNUSED,\n+\t\t\t      struct object_info *oi UNUSED,\n \t\t\t      void *cb)\n {\n \tstruct rev_info *revs = cb;\n@@ -3936,10 +3935,9 @@ int prepare_revision_walk(struct rev_info *revs)\n \t    (revs->limited && limiting_can_increase_treesame(revs)))\n \t\trevs->treesame.name = \"treesame\";\n \n-\tif (revs->exclude_promisor_objects) {\n-\t\tfor_each_packed_object(revs->repo, mark_uninteresting, revs,\n-\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n-\t}\n+\tif (revs->exclude_promisor_objects)\n+\t\todb_for_each_object(revs->repo->objects, NULL, mark_uninteresting,\n+\t\t\t\t    revs, ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (!revs->reflog_info)\n \t\tprepare_to_use_bloom_filter(revs);\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533936","messageId":"20260115-pks-odb-for-each-object-v1-10-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 10/14] treewide: drop uses of `for_each_{loose,packed}_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:39Z","receivedAt":"2026-01-15T11:05:23Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're using `for_each_loose_object()` and `for_each_packed_object()` at\na couple of callsites to enumerate all loose and packed objects,\nrespectively. These functions will be removed in a subsequent commit in\nfavor of the newly introduced `odb_source_loose_for_each_object()` and\n`packfile_store_for_each_object()` replacements.\n\nPrepare for this by refactoring the sites accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c | 28 ++++++++++++++++++++++------\n commit-graph.c     | 44 +++++++++++++++++++++++++++++++-------------\n 2 files changed, 53 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 6964a5a52c..7d16fbc1b8 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -806,11 +806,14 @@ struct for_each_object_payload {\n \tvoid *payload;\n };\n \n-static int batch_one_object_loose(const struct object_id *oid,\n-\t\t\t\t  const char *path UNUSED,\n-\t\t\t\t  void *_payload)\n+static int batch_one_object_oi(const struct object_id *oid,\n+\t\t\t       struct object_info *oi,\n+\t\t\t       void *_payload)\n {\n \tstruct for_each_object_payload *payload = _payload;\n+\tif (oi && oi->whence == OI_PACKED)\n+\t\treturn payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,\n+\t\t\t\t\t payload->payload);\n \treturn payload->callback(oid, NULL, 0, payload->payload);\n }\n \n@@ -846,8 +849,15 @@ static void batch_each_object(struct batch_options *opt,\n \t\t.payload = _payload,\n \t};\n \tstruct bitmap_index *bitmap = prepare_bitmap_git(the_repository);\n+\tstruct odb_source *source;\n \n-\tfor_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);\n+\todb_prepare_alternates(the_repository->objects);\n+\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\t\tint ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi,\n+\t\t\t\t\t\t\t   &payload, flags);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n \n \tif (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,\n \t\t\t\t\t\t batch_one_object_bitmapped, &payload)) {\n@@ -861,8 +871,14 @@ static void batch_each_object(struct batch_options *opt,\n \t\t\t\t\t\t&payload, flags);\n \t\t}\n \t} else {\n-\t\tfor_each_packed_object(the_repository, batch_one_object_packed,\n-\t\t\t\t       &payload, flags);\n+\t\tstruct object_info oi = { 0 };\n+\n+\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\t\t\tint ret = packfile_store_for_each_object(source->packfiles, &oi,\n+\t\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}\n \t}\n \n \tfree_bitmap_index(bitmap);\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 7f1145a082..a3087d7883 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1479,30 +1479,38 @@ static int write_graph_chunk_bloom_data(struct hashfile *f,\n \treturn 0;\n }\n \n+static int add_packed_commits_oi(const struct object_id *oid,\n+\t\t\t\t struct object_info *oi,\n+\t\t\t\t void *data)\n+{\n+\tstruct write_commit_graph_context *ctx = (struct write_commit_graph_context*)data;\n+\n+\tif (ctx->progress)\n+\t\tdisplay_progress(ctx->progress, ++ctx->progress_done);\n+\n+\tif (*oi->typep != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\toid_array_append(&ctx->oids, oid);\n+\tset_commit_pos(ctx->r, oid);\n+\n+\treturn 0;\n+}\n+\n static int add_packed_commits(const struct object_id *oid,\n \t\t\t      struct packed_git *pack,\n \t\t\t      uint32_t pos,\n \t\t\t      void *data)\n {\n-\tstruct write_commit_graph_context *ctx = (struct write_commit_graph_context*)data;\n \tenum object_type type;\n \toff_t offset = nth_packed_object_offset(pack, pos);\n \tstruct object_info oi = OBJECT_INFO_INIT;\n \n-\tif (ctx->progress)\n-\t\tdisplay_progress(ctx->progress, ++ctx->progress_done);\n-\n \toi.typep = &type;\n \tif (packed_object_info(pack, offset, &oi) < 0)\n \t\tdie(_(\"unable to get type of object %s\"), oid_to_hex(oid));\n \n-\tif (type != OBJ_COMMIT)\n-\t\treturn 0;\n-\n-\toid_array_append(&ctx->oids, oid);\n-\tset_commit_pos(ctx->r, oid);\n-\n-\treturn 0;\n+\treturn add_packed_commits_oi(oid, &oi, data);\n }\n \n static void add_missing_parents(struct write_commit_graph_context *ctx, struct commit *commit)\n@@ -1959,13 +1967,23 @@ static int fill_oids_from_commits(struct write_commit_graph_context *ctx,\n \n static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n {\n+\tstruct odb_source *source;\n+\tenum object_type type;\n+\tstruct object_info oi = {\n+\t\t.typep = &type,\n+\t};\n+\n \tif (ctx->report_progress)\n \t\tctx->progress = start_delayed_progress(\n \t\t\tctx->r,\n \t\t\t_(\"Finding commits for commit graph among packed objects\"),\n \t\t\tctx->approx_nr_objects);\n-\tfor_each_packed_object(ctx->r, add_packed_commits, ctx,\n-\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\n+\todb_prepare_alternates(ctx->r->objects);\n+\tfor (source = ctx->r->objects->sources; source; source = source->next)\n+\t\tpackfile_store_for_each_object(source->packfiles, &oi, add_packed_commits_oi,\n+\t\t\t\t\t       ctx, ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\n \tif (ctx->progress_done < ctx->approx_nr_objects)\n \t\tdisplay_progress(ctx->progress, ctx->approx_nr_objects);\n \tstop_progress(&ctx->progress);\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533939","messageId":"20260115-pks-odb-for-each-object-v1-11-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 11/14] odb: introduce mtime fields for object info requests","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:40Z","receivedAt":"2026-01-15T11:05:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are some use cases where we need to figure out the mtime for\nobjects. Most importantly, this is the case when we want to prune\nunreachable objects. But getting at that data requires users to manually\nderive the info either via the loose object's mtime, the packfiles'\nmtime or via the \".mtimes\" file.\n\nIntroduce a new `struct object_info::mtimep` pointer that allows callers\nto request an object's mtime. This new field will be used in a\nsubsequent commit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 29 +++++++++++++++++++++++++----\n odb.c         |  2 ++\n odb.h         |  1 +\n packfile.c    | 40 +++++++++++++++++++++++++++++++++-------\n 4 files changed, 61 insertions(+), 11 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 65e730684b..c0f896673b 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -409,6 +409,7 @@ static int read_object_info_from_path(struct odb_source *source,\n \tchar hdr[MAX_HEADER_LEN];\n \tunsigned long size_scratch;\n \tenum object_type type_scratch;\n+\tstruct stat st;\n \n \t/*\n \t * If we don't care about type or size, then we don't\n@@ -421,7 +422,7 @@ static int read_object_info_from_path(struct odb_source *source,\n \tif (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) {\n \t\tstruct stat st;\n \n-\t\tif ((!oi || !oi->disk_sizep) && (flags & OBJECT_INFO_QUICK)) {\n+\t\tif ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) {\n \t\t\tret = quick_has_loose(source->loose, oid) ? 0 : -1;\n \t\t\tgoto out;\n \t\t}\n@@ -431,8 +432,12 @@ static int read_object_info_from_path(struct odb_source *source,\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (oi && oi->disk_sizep)\n-\t\t\t*oi->disk_sizep = st.st_size;\n+\t\tif (oi) {\n+\t\t\tif (oi->disk_sizep)\n+\t\t\t\t*oi->disk_sizep = st.st_size;\n+\t\t\tif (oi->mtimep)\n+\t\t\t\t*oi->mtimep = st.st_mtime;\n+\t\t}\n \n \t\tret = 0;\n \t\tgoto out;\n@@ -446,7 +451,21 @@ static int read_object_info_from_path(struct odb_source *source,\n \t\tgoto out;\n \t}\n \n-\tmap = map_fd(fd, path, &mapsize);\n+\tif (fstat(fd, &st)) {\n+\t\tclose(fd);\n+\t\tret = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tmapsize = xsize_t(st.st_size);\n+\tif (!mapsize) {\n+\t\tclose(fd);\n+\t\tret = error(_(\"object file %s is empty\"), path);\n+\t\tgoto out;\n+\t}\n+\n+\tmap = xmmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n \tif (!map) {\n \t\tret = -1;\n \t\tgoto out;\n@@ -454,6 +473,8 @@ static int read_object_info_from_path(struct odb_source *source,\n \n \tif (oi->disk_sizep)\n \t\t*oi->disk_sizep = mapsize;\n+\tif (oi->mtimep)\n+\t\t*oi->mtimep = st.st_mtime;\n \n \tstream_to_end = &stream;\n \ndiff --git a/odb.c b/odb.c\nindex 65f0447aa5..67decd3908 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -702,6 +702,8 @@ static int do_oid_object_info_extended(struct object_database *odb,\n \t\t\t\toidclr(oi->delta_base_oid, odb->repo->hash_algo);\n \t\t\tif (oi->contentp)\n \t\t\t\t*oi->contentp = xmemdupz(co->buf, co->size);\n+\t\t\tif (oi->mtimep)\n+\t\t\t\t*oi->mtimep = 0;\n \t\t\toi->whence = OI_CACHED;\n \t\t}\n \t\treturn 0;\ndiff --git a/odb.h b/odb.h\nindex 8f6d95aee5..9e22f79172 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -317,6 +317,7 @@ struct object_info {\n \toff_t *disk_sizep;\n \tstruct object_id *delta_base_oid;\n \tvoid **contentp;\n+\ttime_t *mtimep;\n \n \t/* Response */\n \tenum {\ndiff --git a/packfile.c b/packfile.c\nindex 4f84bc19d9..c96ec21f86 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1578,13 +1578,14 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,\n \thashmap_add(&delta_base_cache, &ent->ent);\n }\n \n-int packed_object_info(struct packed_git *p,\n-\t\t       off_t obj_offset, struct object_info *oi)\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 {\n \tstruct pack_window *w_curs = NULL;\n \tunsigned long size;\n \toff_t curpos = obj_offset;\n \tenum object_type type = OBJ_NONE;\n+\tuint32_t pack_pos;\n \tint ret;\n \n \t/*\n@@ -1619,16 +1620,34 @@ int packed_object_info(struct packed_git *p,\n \t\t}\n \t}\n \n-\tif (oi->disk_sizep) {\n-\t\tuint32_t pos;\n-\t\tif (offset_to_pack_pos(p, obj_offset, &pos) < 0) {\n+\tif (oi->disk_sizep || (oi->mtimep && p->is_cruft)) {\n+\t\tif (offset_to_pack_pos(p, obj_offset, &pack_pos) < 0) {\n \t\t\terror(\"could not find object at offset %\"PRIuMAX\" \"\n \t\t\t      \"in pack %s\", (uintmax_t)obj_offset, p->pack_name);\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n+\t}\n+\n+\tif (oi->disk_sizep)\n+\t\t*oi->disk_sizep = pack_pos_to_offset(p, pack_pos + 1) - obj_offset;\n+\n+\tif (oi->mtimep) {\n+\t\tif (p->is_cruft) {\n+\t\t\tuint32_t index_pos;\n+\n+\t\t\tif (load_pack_mtimes(p) < 0)\n+\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n+\n+\t\t\tif (maybe_index_pos)\n+\t\t\t\tindex_pos = *maybe_index_pos;\n+\t\t\telse\n+\t\t\t\tindex_pos = pack_pos_to_index(p, pack_pos);\n \n-\t\t*oi->disk_sizep = pack_pos_to_offset(p, pos + 1) - obj_offset;\n+\t\t\t*oi->mtimep = nth_packed_mtime(p, index_pos);\n+\t\t} else {\n+\t\t\t*oi->mtimep = p->mtime;\n+\t\t}\n \t}\n \n \tif (oi->typep) {\n@@ -1681,6 +1700,12 @@ int packed_object_info(struct packed_git *p,\n \treturn ret;\n }\n \n+int packed_object_info(struct packed_git *p, off_t obj_offset,\n+\t\t       struct object_info *oi)\n+{\n+\treturn packed_object_info_with_index_pos(p, obj_offset, NULL, oi);\n+}\n+\n static void *unpack_compressed_entry(struct packed_git *p,\n \t\t\t\t    struct pack_window **w_curs,\n \t\t\t\t    off_t curpos,\n@@ -2377,7 +2402,8 @@ static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n \tif (data->oi) {\n \t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n \n-\t\tif (packed_object_info(pack, offset, data->oi) < 0) {\n+\t\tif (packed_object_info_with_index_pos(pack, offset,\n+\t\t\t\t\t\t      &index_pos, data->oi) < 0) {\n \t\t\tmark_bad_packed_object(pack, oid);\n \t\t\treturn -1;\n \t\t}\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533938","messageId":"20260115-pks-odb-for-each-object-v1-12-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 12/14] builtin/pack-objects: use `packfile_store_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:41Z","receivedAt":"2026-01-15T11:05:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When enumerating objects that are supposed to be stored in a new cruft\npack we use `for_each_packed_object()` and then derive each object's\nmtime individually. Refactor this logic to instead use the new\n`packfile_store_for_each_object()` function with an object info request\nthat asks for the respective mtimes.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/pack-objects.c | 45 +++++++++++++++++++++------------------------\n 1 file changed, 21 insertions(+), 24 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 74317051fd..223ec3b49e 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4314,25 +4314,12 @@ static void show_edge(struct commit *commit)\n }\n \n static int add_object_in_unpacked_pack(const struct object_id *oid,\n-\t\t\t\t       struct packed_git *pack,\n-\t\t\t\t       uint32_t pos,\n+\t\t\t\t       struct object_info *oi,\n \t\t\t\t       void *data UNUSED)\n {\n \tif (cruft) {\n-\t\toff_t offset;\n-\t\ttime_t mtime;\n-\n-\t\tif (pack->is_cruft) {\n-\t\t\tif (load_pack_mtimes(pack) < 0)\n-\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n-\t\t\tmtime = nth_packed_mtime(pack, pos);\n-\t\t} else {\n-\t\t\tmtime = pack->mtime;\n-\t\t}\n-\t\toffset = nth_packed_object_offset(pack, pos);\n-\n-\t\tadd_cruft_object_entry(oid, OBJ_NONE, pack, offset,\n-\t\t\t\t       NULL, mtime);\n+\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack,\n+\t\t\t\t       oi->u.packed.offset, NULL, *oi->mtimep);\n \t} else {\n \t\tadd_object_entry(oid, OBJ_NONE, \"\", 0);\n \t}\n@@ -4341,14 +4328,24 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,\n \n static void add_objects_in_unpacked_packs(void)\n {\n-\tif (for_each_packed_object(to_pack.repo,\n-\t\t\t\t   add_object_in_unpacked_pack,\n-\t\t\t\t   NULL,\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n-\t\tdie(_(\"cannot open pack index\"));\n+\tstruct odb_source *source;\n+\ttime_t mtime;\n+\tstruct object_info oi = {\n+\t\t.mtimep = &mtime,\n+\t};\n+\n+\todb_prepare_alternates(to_pack.repo->objects);\n+\tfor (source = to_pack.repo->objects->sources; source; source = source->next) {\n+\t\tif (!source->local)\n+\t\t\tcontinue;\n+\n+\t\tif (packfile_store_for_each_object(source->packfiles, &oi,\n+\t\t\t\t\t\t   add_object_in_unpacked_pack, NULL,\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n+\t\t\tdie(_(\"cannot open pack index\"));\n+\t}\n }\n \n static int add_loose_object(const struct object_id *oid, const char *path,\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533940","messageId":"20260115-pks-odb-for-each-object-v1-13-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 13/14] reachable: convert to use `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:42Z","receivedAt":"2026-01-15T11:05:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"To figure out which objects expired objects we enumerate all loose and\npacked objects individually so that we can figure out their respective\nmtimes. Refactor the code to instead use `odb_for_each_object()` with a\nrequest that ask for the object mtime instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n reachable.c | 125 +++++++++++++++++-------------------------------------------\n 1 file changed, 35 insertions(+), 90 deletions(-)\n\ndiff --git a/reachable.c b/reachable.c\nindex 82676b2668..101cfc2727 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -191,30 +191,27 @@ static int obj_is_recent(const struct object_id *oid, timestamp_t mtime,\n \treturn oidset_contains(&data->extra_recent_oids, oid);\n }\n \n-static void add_recent_object(const struct object_id *oid,\n-\t\t\t      struct packed_git *pack,\n-\t\t\t      off_t offset,\n-\t\t\t      timestamp_t mtime,\n-\t\t\t      struct recent_data *data)\n+static int want_recent_object(struct recent_data *data,\n+\t\t\t      const struct object_id *oid)\n {\n-\tstruct object *obj;\n-\tenum object_type type;\n+\tif (data->ignore_in_core_kept_packs &&\n+\t    has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE))\n+\t\treturn 0;\n+\treturn 1;\n+}\n \n-\tif (!obj_is_recent(oid, mtime, data))\n-\t\treturn;\n+static int add_recent_object(const struct object_id *oid,\n+\t\t\t     struct object_info *oi,\n+\t\t\t     void *cb_data)\n+{\n+\tstruct recent_data *data = cb_data;\n+\tstruct object *obj;\n \n-\t/*\n-\t * We do not want to call parse_object here, because\n-\t * inflating blobs and trees could be very expensive.\n-\t * However, we do need to know the correct type for\n-\t * later processing, and the revision machinery expects\n-\t * commits and tags to have been parsed.\n-\t */\n-\ttype = odb_read_object_info(the_repository->objects, oid, NULL);\n-\tif (type < 0)\n-\t\tdie(\"unable to get object info for %s\", oid_to_hex(oid));\n+\tif (!want_recent_object(data, oid) ||\n+\t    !obj_is_recent(oid, *oi->mtimep, data))\n+\t\treturn 0;\n \n-\tswitch (type) {\n+\tswitch (*oi->typep) {\n \tcase OBJ_TAG:\n \tcase OBJ_COMMIT:\n \t\tobj = parse_object_or_die(the_repository, oid, NULL);\n@@ -227,77 +224,22 @@ static void add_recent_object(const struct object_id *oid,\n \t\tbreak;\n \tdefault:\n \t\tdie(\"unknown object type for %s: %s\",\n-\t\t    oid_to_hex(oid), type_name(type));\n+\t\t    oid_to_hex(oid), type_name(*oi->typep));\n \t}\n \n \tif (!obj)\n \t\tdie(\"unable to lookup %s\", oid_to_hex(oid));\n-\n-\tadd_pending_object(data->revs, obj, \"\");\n-\tif (data->cb)\n-\t\tdata->cb(obj, pack, offset, mtime);\n-}\n-\n-static int want_recent_object(struct recent_data *data,\n-\t\t\t      const struct object_id *oid)\n-{\n-\tif (data->ignore_in_core_kept_packs &&\n-\t    has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE))\n+\tif (obj->flags & SEEN)\n \t\treturn 0;\n-\treturn 1;\n-}\n \n-static int add_recent_loose(const struct object_id *oid,\n-\t\t\t    const char *path, void *data)\n-{\n-\tstruct stat st;\n-\tstruct object *obj;\n-\n-\tif (!want_recent_object(data, oid))\n-\t\treturn 0;\n-\n-\tobj = lookup_object(the_repository, oid);\n-\n-\tif (obj && obj->flags & SEEN)\n-\t\treturn 0;\n-\n-\tif (stat(path, &st) < 0) {\n-\t\t/*\n-\t\t * It's OK if an object went away during our iteration; this\n-\t\t * could be due to a simultaneous repack. But anything else\n-\t\t * we should abort, since we might then fail to mark objects\n-\t\t * which should not be pruned.\n-\t\t */\n-\t\tif (errno == ENOENT)\n-\t\t\treturn 0;\n-\t\treturn error_errno(\"unable to stat %s\", oid_to_hex(oid));\n+\tadd_pending_object(data->revs, obj, \"\");\n+\tif (data->cb) {\n+\t\tif (oi->whence == OI_PACKED)\n+\t\t\tdata->cb(obj, oi->u.packed.pack, oi->u.packed.offset, *oi->mtimep);\n+\t\telse\n+\t\t\tdata->cb(obj, NULL, 0, *oi->mtimep);\n \t}\n \n-\tadd_recent_object(oid, NULL, 0, st.st_mtime, data);\n-\treturn 0;\n-}\n-\n-static int add_recent_packed(const struct object_id *oid,\n-\t\t\t     struct packed_git *p,\n-\t\t\t     uint32_t pos,\n-\t\t\t     void *data)\n-{\n-\tstruct object *obj;\n-\ttimestamp_t mtime = p->mtime;\n-\n-\tif (!want_recent_object(data, oid))\n-\t\treturn 0;\n-\n-\tobj = lookup_object(the_repository, oid);\n-\n-\tif (obj && obj->flags & SEEN)\n-\t\treturn 0;\n-\tif (p->is_cruft) {\n-\t\tif (load_pack_mtimes(p) < 0)\n-\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n-\t\tmtime = nth_packed_mtime(p, pos);\n-\t}\n-\tadd_recent_object(oid, p, nth_packed_object_offset(p, pos), mtime, data);\n \treturn 0;\n }\n \n@@ -307,7 +249,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \t\t\t\t\t   int ignore_in_core_kept_packs)\n {\n \tstruct recent_data data;\n-\tenum odb_for_each_object_flags flags;\n+\tunsigned flags;\n+\tenum object_type type;\n+\ttime_t mtime;\n+\tstruct object_info oi = {\n+\t\t.mtimep = &mtime,\n+\t\t.typep = &type,\n+\t};\n \tint r;\n \n \tdata.revs = revs;\n@@ -318,16 +266,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \toidset_init(&data.extra_recent_oids, 0);\n \tdata.extra_recent_oids_loaded = 0;\n \n-\tr = for_each_loose_object(the_repository->objects, add_recent_loose, &data,\n-\t\t\t\t  ODB_FOR_EACH_OBJECT_LOCAL_ONLY);\n-\tif (r)\n-\t\tgoto done;\n-\n \tflags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER;\n \tif (ignore_in_core_kept_packs)\n \t\tflags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n \n-\tr = for_each_packed_object(revs->repo, add_recent_packed, &data, flags);\n+\tr = odb_for_each_object(revs->repo->objects, &oi, add_recent_object, &data, flags);\n+\tif (r)\n+\t\tgoto done;\n \n done:\n \toidset_clear(&data.extra_recent_oids);\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533941","messageId":"20260115-pks-odb-for-each-object-v1-14-5418a91d5d99@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH 14/14] odb: drop unused `for_each_{loose,packed}_object()` functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-15T11:04:43Z","receivedAt":"2026-01-15T11:05:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have converted all callers of `for_each_loose_object()` and\n`for_each_packed_object()` to use their new replacement functions\ninstead. We can thus remove them now.\n\nDo so and inline `packfile_store_for_each_object_internal()` now that it\nonly has a single callsite again. This makes it a bit easier to follow\nthe callback indirection that is happening there.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 20 -------------\n object-file.h | 11 -------\n packfile.c    | 92 +++++++++++++++++++----------------------------------------\n packfile.h    |  2 --\n 4 files changed, 29 insertions(+), 96 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex c0f896673b..bc5209f2fe 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1802,26 +1802,6 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \treturn r;\n }\n \n-int for_each_loose_object(struct object_database *odb,\n-\t\t\t  each_loose_object_fn cb, void *data,\n-\t\t\t  enum odb_for_each_object_flags flags)\n-{\n-\tstruct odb_source *source;\n-\n-\todb_prepare_alternates(odb);\n-\tfor (source = odb->sources; source; source = source->next) {\n-\t\tint r = for_each_loose_file_in_source(source, cb, NULL,\n-\t\t\t\t\t\t      NULL, data);\n-\t\tif (r)\n-\t\t\treturn r;\n-\n-\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY)\n-\t\t\tbreak;\n-\t}\n-\n-\treturn 0;\n-}\n-\n struct for_each_object_wrapper_data {\n \tstruct odb_source *source;\n \tstruct object_info *oi;\ndiff --git a/object-file.h b/object-file.h\nindex 048b778531..af7f57d2a1 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -126,17 +126,6 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \t\t\t\t  each_loose_subdir_fn subdir_cb,\n \t\t\t\t  void *data);\n \n-/*\n- * Iterate over all accessible loose objects without respect to\n- * reachability. By default, this includes both local and alternate objects.\n- * The order in which objects are visited is unspecified.\n- *\n- * Any flags specific to packs are ignored.\n- */\n-int for_each_loose_object(struct object_database *odb,\n-\t\t\t  each_loose_object_fn, void *,\n-\t\t\t  enum odb_for_each_object_flags flags);\n-\n /*\n  * Iterate through all loose objects in the given object database source and\n  * invoke the callback function for each of them. If given, the object info\ndiff --git a/packfile.c b/packfile.c\nindex c96ec21f86..493d81fdca 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2326,65 +2326,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-static int packfile_store_for_each_object_internal(struct packfile_store *store,\n-\t\t\t\t\t\t   each_packed_object_fn cb,\n-\t\t\t\t\t\t   void *data,\n-\t\t\t\t\t\t   unsigned flags,\n-\t\t\t\t\t\t   int *pack_errors)\n-{\n-\tstruct packfile_list_entry *e;\n-\tint ret = 0;\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 ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\tcontinue;\n-\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t    !p->pack_promisor)\n-\t\t\tcontinue;\n-\t\tif ((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 ((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\t*pack_errors = 1;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tret = for_each_object_in_pack(p, cb, data, flags);\n-\t\tif (ret)\n-\t\t\tbreak;\n-\t}\n-\n-\tstore->skip_mru_updates = false;\n-\n-\treturn ret;\n-}\n-\n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags)\n-{\n-\tstruct odb_source *source;\n-\tint pack_errors = 0;\n-\tint ret = 0;\n-\n-\todb_prepare_alternates(repo->objects);\n-\n-\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\tret = packfile_store_for_each_object_internal(source->packfiles, cb, data,\n-\t\t\t\t\t\t\t      flags, &pack_errors);\n-\t\tif (ret)\n-\t\t\tbreak;\n-\t}\n-\n-\treturn ret ? ret : pack_errors;\n-}\n-\n struct packfile_store_for_each_object_wrapper_data {\n \tstruct packfile_store *store;\n \tstruct object_info *oi;\n@@ -2424,12 +2365,37 @@ int packfile_store_for_each_object(struct packfile_store *store,\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-\tret = packfile_store_for_each_object_internal(store, packfile_store_for_each_object_wrapper,\n-\t\t\t\t\t\t      &data, flags, &pack_errors);\n-\tif (ret)\n-\t\treturn ret;\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 ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((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 ((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, flags);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n+\n+\tstore->skip_mru_updates = false;\n \n \treturn pack_errors ? -1 : 0;\n }\ndiff --git a/packfile.h b/packfile.h\nindex ab0637fbe9..8e0d2b7661 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -340,8 +340,6 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn, void *data,\n \t\t\t    unsigned flags);\n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags);\n \n /*\n  * Iterate through all packed objects in the given packfile store and invoke\n\n-- \n2.52.0.660.gd05f3a8ea5.dirty\n\n"},{"id":"533957","messageId":"xmqqy0lzc7e4.fsf@gitster.g","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"Re: [PATCH 00/14] odb: introduce `odb_for_each_object()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-15T13:50:11Z","receivedAt":"2026-01-15T13:50:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The patch series is built on top of 8745eae506 (The 17th batch,\n> 2026-01-11) with the following two series merged into it:\n>\n>   - ps/read-object-info-improvements at b7f649ca93 (Merge\n>     remote-tracking branch 'junio/ps/read-object-info-improvements' into\n>     HEAD, 2026-01-15).\n>\n>   - ps/packfile-store-in-odb-source at 1ff0e42d33 (Merge remote-tracking\n>     branch 'junio/ps/packfile-store-in-odb-source' into HEAD,\n>     2026-01-15).\n\nThese two commit objects you cite have never been at the tip of\nthese branches in my tree; I'll go by the branch name for now ;-)\n"},{"id":"533978","messageId":"aWkq7j2f3VunsBPL@denethor","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-1-5418a91d5d99@pks.im","subject":"Re: [PATCH 01/14] odb: rename `FOR_EACH_OBJECT_*` flags","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-01-15T18:00:30Z","receivedAt":"2026-01-15T18:00:38Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> Rename the `FOR_EACH_OBJECT_*` flags to have an `ODB_` prefix. This\n> prepares us for a new upcoming `odb_for_each_object()` function and\n> ensures that both the function and its flags have the same prefix.\n\nMakes sense. All the changes in this patch are just trivial renames.\nLooks good.\n\n-Justin\n"},{"id":"533979","messageId":"aWkvucfZy7e2Rd6t@denethor","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-3-5418a91d5d99@pks.im","subject":"Re: [PATCH 03/14] object-file: extract function to read object info from path","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-01-15T18:31:13Z","receivedAt":"2026-01-15T18:31:18Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> Extract a new function that allows us to read object info for a specific\n> loose object via a user-supplied path. This function will be used in a\n> subsequent commit.\n\nOk, I assume that the new function we are talking about here is\nread_object_info_from_path(). This new function does the same thing as\nthe previous version of odb_source_loose_read_object_info(), but now\nrequires the path to be provided.\n\n> Note that this also allows us to drop `stat_loose_object()`, which is\n> a simple wrapper around `odb_loose_path()` plus lstat(3p).\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  object-file.c | 39 ++++++++++++++++-----------------------\n>  1 file changed, 16 insertions(+), 23 deletions(-)\n> \n> diff --git a/object-file.c b/object-file.c\n> index 8fa461dd59..a651129426 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n[snip]\n> @@ -455,7 +438,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n>  \t\tgoto out;\n>  \t}\n>  \n> -\tfd = open_loose_object(source->loose, oid, &path);\n> +\tfd = git_open(path);\n\nHere we already have the path, so there is no need to invoke\nodb_loose_path() again via open_loose_object(). We can instead call\ngit_open() directly. Looks good.\n\nIf I understand correctly, even before this change the path was already\navailable so using open_loose_object() here was already redundant.\n\n>  \tif (fd < 0) {\n>  \t\tif (errno != ENOENT)\n>  \t\t\terror_errno(_(\"unable to open loose object %s\"), oid_to_hex(oid));\n> @@ -534,6 +517,16 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n>  \treturn ret;\n>  }\n>  \n> +int odb_source_loose_read_object_info(struct odb_source *source,\n> +\t\t\t\t      const struct object_id *oid,\n> +\t\t\t\t      struct object_info *oi,\n> +\t\t\t\t      unsigned flags)\n> +{\n> +\tstatic struct strbuf buf = STRBUF_INIT;\n> +\todb_loose_path(source, &buf, oid);\n> +\treturn read_object_info_from_path(source, buf.buf, oid, oi, flags);\n\nLooks good.\n\n-Justin\n"},{"id":"533986","messageId":"aWlSYGIe5izqWwte@denethor","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-4-5418a91d5d99@pks.im","subject":"Re: [PATCH 04/14] object-file: introduce function to iterate through objects","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-01-15T20:54:23Z","receivedAt":"2026-01-15T20:54:28Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> We have multiple divergent interfaces to iterate through objects of a\n> specific backend:\n> \n>   - `for_each_loose_object()` yields all loose objects.\n> \n>   - `for_each_packed_object()` (somewhat obviously) yields all packed\n>     objects.\n> \n> These functions have different function signatures, which makes it hard\n> to create a common abstraction layer that covers both of these.\n\nI assume that the intention is to eventually have a generic\nfor_each_object() function that can iterate across objects regardless of\nthe source. Is the end goal to have each source define the appropriate\nfor_each_object callback?\n\n> Introduce a new function `odb_source_loose_for_each_object()` to plug\n> this gap. This function doesn't take any data specific to loose objects,\n> but instead it accepts a `struct object_info` that will be populated the\n> exact same as if `odb_source_loose_read_object()` was called.\n> \n> The benefit of this new interface is that we can continue to pass\n> backend-specific data, as `struct object_info` contains a union for\n> these exact use cases. This will allow us to unify how we iterate\n> through objects across both loose and packed objects in a subsequent\n> commit.\n\nNaive question: in a future where we have additional ODB backends, does\nthis mean that `struct object_info` would also need to be updated to\ninclude them?\n\n> The `for_each_loose_object()` function continues to exist for now, but\n> it will be removed at the end of this patch series.\n"},{"id":"533990","messageId":"aWlXu9ogFnv0GGBo@denethor","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-7-5418a91d5d99@pks.im","subject":"Re: [PATCH 07/14] odb: introduce `odb_for_each_object()`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-01-15T21:17:07Z","receivedAt":"2026-01-15T21:17:12Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> Introduce a new function `odb_for_each_object()` that knows to iterate\n> through all objects part of a given object database. This function is\n> essentially a simple wrapper around the object database sources.\n> \n> Subsequent commits will adapt callers to use this new function.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  odb.c | 27 +++++++++++++++++++++++++++\n>  odb.h | 17 +++++++++++++++++\n>  2 files changed, 44 insertions(+)\n> \n> diff --git a/odb.c b/odb.c\n> index ac70b6a099..65f0447aa5 100644\n> --- a/odb.c\n> +++ b/odb.c\n> @@ -995,6 +995,33 @@ int odb_freshen_object(struct object_database *odb,\n>  \treturn 0;\n>  }\n>  \n> +int odb_for_each_object(struct object_database *odb,\n> +\t\t\tstruct object_info *oi,\n> +\t\t\todb_for_each_object_cb cb,\n> +\t\t\tvoid *cb_data,\n> +\t\t\tunsigned flags)\n> +{\n> +\tint ret;\n> +\n> +\todb_prepare_alternates(odb);\n> +\tfor (struct odb_source *source = odb->sources; source; source = source->next) {\n> +\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local)\n> +\t\t\tcontinue;\n> +\n> +\t\tif (!(flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {\n> +\t\t\tret = odb_source_loose_for_each_object(source, oi, cb, cb_data, flags);\n> +\t\t\tif (ret)\n> +\t\t\t\treturn ret;\n> +\t\t}\n> +\n> +\t\tret = packfile_store_for_each_object(source->packfiles, oi, cb, cb_data, flags);\n> +\t\tif (ret)\n> +\t\t\treturn ret;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n\nOk, I think I understand a bit more clearly now. As implemented here,\nodb_for_each_object() iterates across each the objects (loose and\npacked) in each source. Object iteration is not handled transparently\nfor each source yet though and we still explicitly iterate both loose\nand packed objects. If I understand correctly, this current\nimplementation will become specific to the \"files\" backend/source in the\nfuture.\n\n-Justin\n"},{"id":"533991","messageId":"aWlaA2cwDS39pvRx@denethor","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-8-5418a91d5d99@pks.im","subject":"Re: [PATCH 08/14] builtin/fsck: refactor to use `odb_for_each_object()`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-01-15T21:24:25Z","receivedAt":"2026-01-15T21:24:29Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> In git-fsck(1) we have two callsites where we iterate over all objects\n> via `for_each_loose_object()` and `for_each_packed_object()`. Both of\n> these are trivially convertible with `odb_for_each_object()`.\n> \n> Refactor these callsites accordingly.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/fsck.c | 57 ++++++++++++---------------------------------------------\n>  1 file changed, 12 insertions(+), 45 deletions(-)\n> \n> diff --git a/builtin/fsck.c b/builtin/fsck.c\n> index 4979bc795e..96107695ae 100644\n> --- a/builtin/fsck.c\n> +++ b/builtin/fsck.c\n> @@ -218,15 +218,17 @@ static int mark_used(struct object *obj, enum object_type type UNUSED,\n>  \treturn 0;\n>  }\n>  \n> -static void mark_unreachable_referents(const struct object_id *oid)\n> +static int mark_unreachable_referents(const struct object_id *oid,\n> +\t\t\t\t      struct object_info *io UNUSED,\n> +\t\t\t\t      void *data UNUSED)\n>  {\n>  \tstruct fsck_options options = FSCK_OPTIONS_DEFAULT;\n>  \tstruct object *obj = lookup_object(the_repository, oid);\n>  \n>  \tif (!obj || !(obj->flags & HAS_OBJ))\n> -\t\treturn; /* not part of our original set */\n> +\t\treturn 0; /* not part of our original set */\n>  \tif (obj->flags & REACHABLE)\n> -\t\treturn; /* reachable objects already traversed */\n> +\t\treturn 0; /* reachable objects already traversed */\n>  \n>  \t/*\n>  \t * Avoid passing OBJ_NONE to fsck_walk, which will parse the object\n> @@ -243,22 +245,7 @@ static void mark_unreachable_referents(const struct object_id *oid)\n>  \tfsck_walk(obj, NULL, &options);\n>  \tif (obj->type == OBJ_TREE)\n>  \t\tfree_tree_buffer((struct tree *)obj);\n> -}\n>  \n> -static int mark_loose_unreachable_referents(const struct object_id *oid,\n> -\t\t\t\t\t    const char *path UNUSED,\n> -\t\t\t\t\t    void *data UNUSED)\n> -{\n> -\tmark_unreachable_referents(oid);\n> -\treturn 0;\n> -}\n> -\n> -static int mark_packed_unreachable_referents(const struct object_id *oid,\n> -\t\t\t\t\t     struct packed_git *pack UNUSED,\n> -\t\t\t\t\t     uint32_t pos UNUSED,\n> -\t\t\t\t\t     void *data UNUSED)\n> -{\n> -\tmark_unreachable_referents(oid);\n\nAh ok, now that object iteration is unified, we don't need the two\nseparate callbacks. Makes sense. :)\n\n>  \treturn 0;\n>  }\n>  \n> @@ -394,12 +381,8 @@ static void check_connectivity(void)\n>  \t\t * and ignore any that weren't present in our earlier\n>  \t\t * traversal.\n>  \t\t */\n> -\t\tfor_each_loose_object(the_repository->objects,\n> -\t\t\t\t      mark_loose_unreachable_referents, NULL, 0);\n> -\t\tfor_each_packed_object(the_repository,\n> -\t\t\t\t       mark_packed_unreachable_referents,\n> -\t\t\t\t       NULL,\n> -\t\t\t\t       0);\n> +\t\todb_for_each_object(the_repository->objects, NULL,\n> +\t\t\t\t    mark_unreachable_referents, NULL, 0);\n\nNice! Now we no longer have to explicitly handle the various object\nbackends while iterating. This patch looks good.\n\n-Justin\n"},{"id":"533995","messageId":"aWlemFAu9HwKgpOe@denethor","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-10-5418a91d5d99@pks.im","subject":"Re: [PATCH 10/14] treewide: drop uses of `for_each_{loose,packed}_object()`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-01-15T21:44:50Z","receivedAt":"2026-01-15T21:44:55Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> We're using `for_each_loose_object()` and `for_each_packed_object()` at\n> a couple of callsites to enumerate all loose and packed objects,\n> respectively. These functions will be removed in a subsequent commit in\n> favor of the newly introduced `odb_source_loose_for_each_object()` and\n> `packfile_store_for_each_object()` replacements.\n> \n> Prepare for this by refactoring the sites accordingly.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/cat-file.c | 28 ++++++++++++++++++++++------\n>  commit-graph.c     | 44 +++++++++++++++++++++++++++++++-------------\n>  2 files changed, 53 insertions(+), 19 deletions(-)\n> \n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 6964a5a52c..7d16fbc1b8 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -806,11 +806,14 @@ struct for_each_object_payload {\n>  \tvoid *payload;\n>  };\n>  \n> -static int batch_one_object_loose(const struct object_id *oid,\n> -\t\t\t\t  const char *path UNUSED,\n> -\t\t\t\t  void *_payload)\n> +static int batch_one_object_oi(const struct object_id *oid,\n> +\t\t\t       struct object_info *oi,\n> +\t\t\t       void *_payload)\n>  {\n>  \tstruct for_each_object_payload *payload = _payload;\n> +\tif (oi && oi->whence == OI_PACKED)\n> +\t\treturn payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,\n> +\t\t\t\t\t payload->payload);\n>  \treturn payload->callback(oid, NULL, 0, payload->payload);\n>  }\n>  \n> @@ -846,8 +849,15 @@ static void batch_each_object(struct batch_options *opt,\n>  \t\t.payload = _payload,\n>  \t};\n>  \tstruct bitmap_index *bitmap = prepare_bitmap_git(the_repository);\n> +\tstruct odb_source *source;\n>  \n> -\tfor_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);\n> +\todb_prepare_alternates(the_repository->objects);\n> +\tfor (source = the_repository->objects->sources; source; source = source->next) {\n> +\t\tint ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi,\n> +\t\t\t\t\t\t\t   &payload, flags);\n> +\t\tif (ret)\n> +\t\t\tbreak;\n> +\t}\n>  \n>  \tif (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,\n>  \t\t\t\t\t\t batch_one_object_bitmapped, &payload)) {\n> @@ -861,8 +871,14 @@ static void batch_each_object(struct batch_options *opt,\n>  \t\t\t\t\t\t&payload, flags);\n>  \t\t}\n>  \t} else {\n> -\t\tfor_each_packed_object(the_repository, batch_one_object_packed,\n> -\t\t\t\t       &payload, flags);\n> +\t\tstruct object_info oi = { 0 };\n> +\n> +\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n> +\t\t\tint ret = packfile_store_for_each_object(source->packfiles, &oi,\n> +\t\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}\n\nHuh, I was a bit surprised to see that we are still handling object\niteration in a backend specific banner here. I would assume ideally we\nwould want to transparently iterate across objects wherever possible. I\nassume the reason here has something to do with how iteration is handled\nwith bitmaps?\n\n-Justin\n"},{"id":"534016","messageId":"aWnisVFbgXIG492W@pks.im","threadId":"64809","inReplyTo":"xmqqy0lzc7e4.fsf@gitster.g","subject":"Re: [PATCH 00/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-16T07:03:13Z","receivedAt":"2026-01-16T07:03:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 15, 2026 at 05:50:11AM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The patch series is built on top of 8745eae506 (The 17th batch,\n> > 2026-01-11) with the following two series merged into it:\n> >\n> >   - ps/read-object-info-improvements at b7f649ca93 (Merge\n> >     remote-tracking branch 'junio/ps/read-object-info-improvements' into\n> >     HEAD, 2026-01-15).\n> >\n> >   - ps/packfile-store-in-odb-source at 1ff0e42d33 (Merge remote-tracking\n> >     branch 'junio/ps/packfile-store-in-odb-source' into HEAD,\n> >     2026-01-15).\n> \n> These two commit objects you cite have never been at the tip of\n> these branches in my tree; I'll go by the branch name for now ;-)\n\nUgh, yeah. I referenced the merge commits in my tree, which is of course\ndumb. Will fix the cover letter to point to what you have now.\n\nPatrick\n"},{"id":"534017","messageId":"aWnivHGQMeTEMZux@pks.im","threadId":"64809","inReplyTo":"aWkvucfZy7e2Rd6t@denethor","subject":"Re: [PATCH 03/14] object-file: extract function to read object info from path","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-16T07:03:24Z","receivedAt":"2026-01-16T07:03:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 15, 2026 at 12:31:13PM -0600, Justin Tobler wrote:\n> On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> > diff --git a/object-file.c b/object-file.c\n> > index 8fa461dd59..a651129426 100644\n> > --- a/object-file.c\n> > +++ b/object-file.c\n> [snip]\n> > @@ -455,7 +438,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n> >  \t\tgoto out;\n> >  \t}\n> >  \n> > -\tfd = open_loose_object(source->loose, oid, &path);\n> > +\tfd = git_open(path);\n> \n> Here we already have the path, so there is no need to invoke\n> odb_loose_path() again via open_loose_object(). We can instead call\n> git_open() directly. Looks good.\n> \n> If I understand correctly, even before this change the path was already\n> available so using open_loose_object() here was already redundant.\n\nIt actually wasn't. `open_loose_object()` was responsible for calling\n`odb_loose_path()`, and that path was then also assigned to the out\npointer.\n\nPatrick\n"},{"id":"534018","messageId":"aWniwzCL5S6FD3N9@pks.im","threadId":"64809","inReplyTo":"aWlSYGIe5izqWwte@denethor","subject":"Re: [PATCH 04/14] object-file: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-16T07:03:31Z","receivedAt":"2026-01-16T07:03:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 15, 2026 at 02:54:23PM -0600, Justin Tobler wrote:\n> On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> > We have multiple divergent interfaces to iterate through objects of a\n> > specific backend:\n> > \n> >   - `for_each_loose_object()` yields all loose objects.\n> > \n> >   - `for_each_packed_object()` (somewhat obviously) yields all packed\n> >     objects.\n> > \n> > These functions have different function signatures, which makes it hard\n> > to create a common abstraction layer that covers both of these.\n> \n> I assume that the intention is to eventually have a generic\n> for_each_object() function that can iterate across objects regardless of\n> the source. Is the end goal to have each source define the appropriate\n> for_each_object callback?\n\nYup.\n\n> > Introduce a new function `odb_source_loose_for_each_object()` to plug\n> > this gap. This function doesn't take any data specific to loose objects,\n> > but instead it accepts a `struct object_info` that will be populated the\n> > exact same as if `odb_source_loose_read_object()` was called.\n> > \n> > The benefit of this new interface is that we can continue to pass\n> > backend-specific data, as `struct object_info` contains a union for\n> > these exact use cases. This will allow us to unify how we iterate\n> > through objects across both loose and packed objects in a subsequent\n> > commit.\n> \n> Naive question: in a future where we have additional ODB backends, does\n> this mean that `struct object_info` would also need to be updated to\n> include them?\n\nYes. We'd introduce a new `OI_*` type to signifiy the specific backend\nvia the `whence` fieldand will (optionally) have a new member in the\nunion of backend-specific data.\n\nPatrick\n"},{"id":"534019","messageId":"aWniyrg-a7FTnkvE@pks.im","threadId":"64809","inReplyTo":"aWlXu9ogFnv0GGBo@denethor","subject":"Re: [PATCH 07/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-16T07:03:38Z","receivedAt":"2026-01-16T07:03:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 15, 2026 at 03:17:07PM -0600, Justin Tobler wrote:\n> On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> > diff --git a/odb.c b/odb.c\n> > index ac70b6a099..65f0447aa5 100644\n> > --- a/odb.c\n> > +++ b/odb.c\n> > @@ -995,6 +995,33 @@ int odb_freshen_object(struct object_database *odb,\n> >  \treturn 0;\n> >  }\n> >  \n> > +int odb_for_each_object(struct object_database *odb,\n> > +\t\t\tstruct object_info *oi,\n> > +\t\t\todb_for_each_object_cb cb,\n> > +\t\t\tvoid *cb_data,\n> > +\t\t\tunsigned flags)\n> > +{\n> > +\tint ret;\n> > +\n> > +\todb_prepare_alternates(odb);\n> > +\tfor (struct odb_source *source = odb->sources; source; source = source->next) {\n> > +\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local)\n> > +\t\t\tcontinue;\n> > +\n> > +\t\tif (!(flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {\n> > +\t\t\tret = odb_source_loose_for_each_object(source, oi, cb, cb_data, flags);\n> > +\t\t\tif (ret)\n> > +\t\t\t\treturn ret;\n> > +\t\t}\n> > +\n> > +\t\tret = packfile_store_for_each_object(source->packfiles, oi, cb, cb_data, flags);\n> > +\t\tif (ret)\n> > +\t\t\treturn ret;\n> > +\t}\n> > +\n> > +\treturn 0;\n> > +}\n> \n> Ok, I think I understand a bit more clearly now. As implemented here,\n> odb_for_each_object() iterates across each the objects (loose and\n> packed) in each source. Object iteration is not handled transparently\n> for each source yet though and we still explicitly iterate both loose\n> and packed objects. If I understand correctly, this current\n> implementation will become specific to the \"files\" backend/source in the\n> future.\n\nYup, that's correct :)\n\nPatrick\n"},{"id":"534020","messageId":"aWniz5_-Q6o0tJXQ@pks.im","threadId":"64809","inReplyTo":"aWlemFAu9HwKgpOe@denethor","subject":"Re: [PATCH 10/14] treewide: drop uses of `for_each_{loose,packed}_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-16T07:03:43Z","receivedAt":"2026-01-16T07:03:49Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 15, 2026 at 03:44:50PM -0600, Justin Tobler wrote:\n> On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > index 6964a5a52c..7d16fbc1b8 100644\n> > --- a/builtin/cat-file.c\n> > +++ b/builtin/cat-file.c\n> > @@ -861,8 +871,14 @@ static void batch_each_object(struct batch_options *opt,\n> >  \t\t\t\t\t\t&payload, flags);\n> >  \t\t}\n> >  \t} else {\n> > -\t\tfor_each_packed_object(the_repository, batch_one_object_packed,\n> > -\t\t\t\t       &payload, flags);\n> > +\t\tstruct object_info oi = { 0 };\n> > +\n> > +\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n> > +\t\t\tint ret = packfile_store_for_each_object(source->packfiles, &oi,\n> > +\t\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}\n> \n> Huh, I was a bit surprised to see that we are still handling object\n> iteration in a backend specific banner here. I would assume ideally we\n> would want to transparently iterate across objects wherever possible. I\n> assume the reason here has something to do with how iteration is handled\n> with bitmaps?\n\nExactly. I was pondering a bit over whether or not I should invest a bit\nmore time to also make this part here generic. But I felt like the patch\nseries was already long enough, so I decided to not pursue this for now.\n\nIt's certainly something to iterate on in the future though.\n\nPatrick\n"},{"id":"534055","messageId":"xmqqecnpa4eh.fsf@gitster.g","threadId":"64809","inReplyTo":"aWnisVFbgXIG492W@pks.im","subject":"Re: [PATCH 00/14] odb: introduce `odb_for_each_object()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-16T16:49:58Z","receivedAt":"2026-01-16T16:50:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Thu, Jan 15, 2026 at 05:50:11AM -0800, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> \n>> > The patch series is built on top of 8745eae506 (The 17th batch,\n>> > 2026-01-11) with the following two series merged into it:\n>> >\n>> >   - ps/read-object-info-improvements at b7f649ca93 (Merge\n>> >     remote-tracking branch 'junio/ps/read-object-info-improvements' into\n>> >     HEAD, 2026-01-15).\n>> >\n>> >   - ps/packfile-store-in-odb-source at 1ff0e42d33 (Merge remote-tracking\n>> >     branch 'junio/ps/packfile-store-in-odb-source' into HEAD,\n>> >     2026-01-15).\n>> \n>> These two commit objects you cite have never been at the tip of\n>> these branches in my tree; I'll go by the branch name for now ;-)\n>\n> Ugh, yeah. I referenced the merge commits in my tree, which is of course\n> dumb. Will fix the cover letter to point to what you have now.\n\nI am seeing good things in the series, without much nits to pick.\nMaybe there is no need for another round, in which case there is no\nneed for fixed cover letter, either ;-).\n"},{"id":"534065","messageId":"aWpz65mPZfy7Hfba@denethor","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-7-5418a91d5d99@pks.im","subject":"Re: [PATCH 07/14] odb: introduce `odb_for_each_object()`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-01-16T17:46:12Z","receivedAt":"2026-01-16T17:46:17Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> diff --git a/odb.h b/odb.h\n> index f97f249580..8f6d95aee5 100644\n> --- a/odb.h\n> +++ b/odb.h\n> @@ -475,6 +475,23 @@ typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n>  \t\t\t\t      struct object_info *oi,\n>  \t\t\t\t      void *cb_data);\n>  \n> +/*\n> + * Iterate through all objects contained in the object database. Note that\n> + * objects may be iterated over multiple times in case they are either stored\n> + * in different backends or in case they are stored in multiple sources.\n> + *\n> + * Returning a non-zero error code will cause iteration to abort. The error\n> + * code will be propagated.\n> + *\n> + * Returns 0 on success, a negative error code in case a failure occurred, or\n> + * an arbitrary non-zero error code returned by the callback itself.\n> + */\n> +int odb_for_each_object(struct object_database *odb,\n> +\t\t\tstruct object_info *oi,\n\nSomething I probably don't fully understand yet is the role of `struct\nobject_info` being passed in here by `odb_for_each_object()` callers.\nOutside of configuring the specific object info attributes that are\nneeded for a given callback, is there reason that callers would care\nabout the data that gets populated in it? I was under the impression\nthat this object info was really only needed for the internal\n`odb_for_eachodbject_cb` that gets invoked.\n\n> +\t\t\todb_for_each_object_cb cb,\n> +\t\t\tvoid *cb_data,\n> +\t\t\tunsigned flags);\n"},{"id":"534066","messageId":"aWp5dToSXoqAqiT6@denethor","threadId":"64809","inReplyTo":"aWniz5_-Q6o0tJXQ@pks.im","subject":"Re: [PATCH 10/14] treewide: drop uses of `for_each_{loose,packed}_object()`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-01-16T17:47:45Z","receivedAt":"2026-01-16T17:47:47Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 26/01/16 08:03AM, Patrick Steinhardt wrote:\n> On Thu, Jan 15, 2026 at 03:44:50PM -0600, Justin Tobler wrote:\n> > On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> > > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > > index 6964a5a52c..7d16fbc1b8 100644\n> > > --- a/builtin/cat-file.c\n> > > +++ b/builtin/cat-file.c\n> > > @@ -861,8 +871,14 @@ static void batch_each_object(struct batch_options *opt,\n> > >  \t\t\t\t\t\t&payload, flags);\n> > >  \t\t}\n> > >  \t} else {\n> > > -\t\tfor_each_packed_object(the_repository, batch_one_object_packed,\n> > > -\t\t\t\t       &payload, flags);\n> > > +\t\tstruct object_info oi = { 0 };\n> > > +\n> > > +\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n> > > +\t\t\tint ret = packfile_store_for_each_object(source->packfiles, &oi,\n> > > +\t\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}\n> > \n> > Huh, I was a bit surprised to see that we are still handling object\n> > iteration in a backend specific banner here. I would assume ideally we\n> > would want to transparently iterate across objects wherever possible. I\n> > assume the reason here has something to do with how iteration is handled\n> > with bitmaps?\n> \n> Exactly. I was pondering a bit over whether or not I should invest a bit\n> more time to also make this part here generic. But I felt like the patch\n> series was already long enough, so I decided to not pursue this for now.\n> \n> It's certainly something to iterate on in the future though.\n\nCertainly not worth rerolling by itself, but it might be nice to explain\nthis in the commit message and/or comment. :)\n\n-Justin\n"},{"id":"534173","messageId":"aW3Y-vqyrRmWP4yZ@pks.im","threadId":"64809","inReplyTo":"aWpz65mPZfy7Hfba@denethor","subject":"Re: [PATCH 07/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-19T07:10:50Z","receivedAt":"2026-01-19T07:10:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Jan 16, 2026 at 11:46:12AM -0600, Justin Tobler wrote:\n> On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> > diff --git a/odb.h b/odb.h\n> > index f97f249580..8f6d95aee5 100644\n> > --- a/odb.h\n> > +++ b/odb.h\n> > @@ -475,6 +475,23 @@ typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n> >  \t\t\t\t      struct object_info *oi,\n> >  \t\t\t\t      void *cb_data);\n> >  \n> > +/*\n> > + * Iterate through all objects contained in the object database. Note that\n> > + * objects may be iterated over multiple times in case they are either stored\n> > + * in different backends or in case they are stored in multiple sources.\n> > + *\n> > + * Returning a non-zero error code will cause iteration to abort. The error\n> > + * code will be propagated.\n> > + *\n> > + * Returns 0 on success, a negative error code in case a failure occurred, or\n> > + * an arbitrary non-zero error code returned by the callback itself.\n> > + */\n> > +int odb_for_each_object(struct object_database *odb,\n> > +\t\t\tstruct object_info *oi,\n> \n> Something I probably don't fully understand yet is the role of `struct\n> object_info` being passed in here by `odb_for_each_object()` callers.\n> Outside of configuring the specific object info attributes that are\n> needed for a given callback, is there reason that callers would care\n> about the data that gets populated in it? I was under the impression\n> that this object info was really only needed for the internal\n> `odb_for_eachodbject_cb` that gets invoked.\n\nSome callers do care about this info. We see this later in the series\nwhere they for example want to learn about the mtime of each of the\niterated objects, but we also have other cases where we want to for\nexample sum up the size of all objects.\n\nAnother use case for passing `struct object_info` is so that the caller\ncan tell apart which backend an object is coming from via the `whence`\nfield.\n\nApart from that there's also good reason to keep the current layout. For\nthe packfile backend for example it's significantly cheaper to iterate\nand look up object info at the same time compared to iterating and then\ncalling `odb_read_object_info()` for each individual object. We already\nhave the information available when iterating, so it's just a matter of\nalso populating the object info with it in case the caller needs it.\n\nPatrick\n"},{"id":"534174","messageId":"aW3ZATijLwAl7ZT-@pks.im","threadId":"64809","inReplyTo":"aWp5dToSXoqAqiT6@denethor","subject":"Re: [PATCH 10/14] treewide: drop uses of `for_each_{loose,packed}_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-19T07:10:57Z","receivedAt":"2026-01-19T07:11:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Jan 16, 2026 at 11:47:45AM -0600, Justin Tobler wrote:\n> On 26/01/16 08:03AM, Patrick Steinhardt wrote:\n> > On Thu, Jan 15, 2026 at 03:44:50PM -0600, Justin Tobler wrote:\n> > > On 26/01/15 12:04PM, Patrick Steinhardt wrote:\n> > > > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > > > index 6964a5a52c..7d16fbc1b8 100644\n> > > > --- a/builtin/cat-file.c\n> > > > +++ b/builtin/cat-file.c\n> > > > @@ -861,8 +871,14 @@ static void batch_each_object(struct batch_options *opt,\n> > > >  \t\t\t\t\t\t&payload, flags);\n> > > >  \t\t}\n> > > >  \t} else {\n> > > > -\t\tfor_each_packed_object(the_repository, batch_one_object_packed,\n> > > > -\t\t\t\t       &payload, flags);\n> > > > +\t\tstruct object_info oi = { 0 };\n> > > > +\n> > > > +\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n> > > > +\t\t\tint ret = packfile_store_for_each_object(source->packfiles, &oi,\n> > > > +\t\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}\n> > > \n> > > Huh, I was a bit surprised to see that we are still handling object\n> > > iteration in a backend specific banner here. I would assume ideally we\n> > > would want to transparently iterate across objects wherever possible. I\n> > > assume the reason here has something to do with how iteration is handled\n> > > with bitmaps?\n> > \n> > Exactly. I was pondering a bit over whether or not I should invest a bit\n> > more time to also make this part here generic. But I felt like the patch\n> > series was already long enough, so I decided to not pursue this for now.\n> > \n> > It's certainly something to iterate on in the future though.\n> \n> Certainly not worth rerolling by itself, but it might be nice to explain\n> this in the commit message and/or comment. :)\n\nFair, I've appended this locally. Thanks!\n\nPatrick\n"},{"id":"534215","messageId":"CAOLa=ZSt68cb+5hOwP9R8yKOXVDybSSdmmn32TyM4bq0ircygg@mail.gmail.com","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-3-5418a91d5d99@pks.im","subject":"Re: [PATCH 03/14] object-file: extract function to read object info from path","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-01-20T09:09:26Z","receivedAt":"2026-01-20T09:09:28Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Extract a new function that allows us to read object info for a specific\n> loose object via a user-supplied path. This function will be used in a\n> subsequent commit.\n>\n> Note that this also allows us to drop `stat_loose_object()`, which is\n> a simple wrapper around `odb_loose_path()` plus lstat(3p).\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  object-file.c | 39 ++++++++++++++++-----------------------\n>  1 file changed, 16 insertions(+), 23 deletions(-)\n>\n> diff --git a/object-file.c b/object-file.c\n> index 8fa461dd59..a651129426 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -165,30 +165,13 @@ int stream_object_signature(struct repository *r, const struct object_id *oid)\n>  }\n>\n>  /*\n> - * Find \"oid\" as a loose object in given source.\n> - * Returns 0 on success, negative on failure.\n> + * Find \"oid\" as a loose object in given source, open the object and return its\n> + * file descriptor. Returns the file descriptor on success, negative on failure.\n>   *\n>   * The \"path\" out-parameter will give the path of the object we found (if any).\n>   * Note that it may point to static storage and is only valid until another\n>   * call to stat_loose_object().\n>   */\n> -static int stat_loose_object(struct odb_source_loose *loose,\n> -\t\t\t     const struct object_id *oid,\n> -\t\t\t     struct stat *st, const char **path)\n> -{\n> -\tstatic struct strbuf buf = STRBUF_INIT;\n> -\n> -\t*path = odb_loose_path(loose->source, &buf, oid);\n> -\tif (!lstat(*path, st))\n> -\t\treturn 0;\n> -\n> -\treturn -1;\n> -}\n> -\n> -/*\n> - * Like stat_loose_object(), but actually open the object and return the\n> - * descriptor. See the caveats on the \"path\" parameter above.\n> - */\n>  static int open_loose_object(struct odb_source_loose *loose,\n>  \t\t\t     const struct object_id *oid, const char **path)\n>  {\n> @@ -412,7 +395,8 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)\n>  \treturn 0;\n>  }\n>\n> -int odb_source_loose_read_object_info(struct odb_source *source,\n> +static int read_object_info_from_path(struct odb_source *source,\n> +\t\t\t\t      const char *path,\n>  \t\t\t\t      const struct object_id *oid,\n>  \t\t\t\t      struct object_info *oi,\n>  \t\t\t\t      unsigned flags)\n> @@ -420,7 +404,6 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n>  \tint ret;\n>  \tint fd;\n>  \tunsigned long mapsize;\n> -\tconst char *path;\n>  \tvoid *map = NULL;\n>  \tgit_zstream stream, *stream_to_end = NULL;\n>  \tchar hdr[MAX_HEADER_LEN];\n> @@ -443,7 +426,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n>  \t\t\tgoto out;\n>  \t\t}\n>\n> -\t\tif (stat_loose_object(source->loose, oid, &st, &path) < 0) {\n> +\t\tif (lstat(path, &st) < 0) {\n>  \t\t\tret = -1;\n>  \t\t\tgoto out;\n>  \t\t}\n> @@ -455,7 +438,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n>  \t\tgoto out;\n>  \t}\n>\n> -\tfd = open_loose_object(source->loose, oid, &path);\n\nOkay, so with this change, there's only one user of\n`open_loose_object()` left. I don't see any cleanups needed there.\n\n> +\tfd = git_open(path);\n>  \tif (fd < 0) {\n>  \t\tif (errno != ENOENT)\n>  \t\t\terror_errno(_(\"unable to open loose object %s\"), oid_to_hex(oid));\n> @@ -534,6 +517,16 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n>  \treturn ret;\n>  }\n>\n> +int odb_source_loose_read_object_info(struct odb_source *source,\n> +\t\t\t\t      const struct object_id *oid,\n> +\t\t\t\t      struct object_info *oi,\n> +\t\t\t\t      unsigned flags)\n> +{\n> +\tstatic struct strbuf buf = STRBUF_INIT;\n> +\todb_loose_path(source, &buf, oid);\n> +\treturn read_object_info_from_path(source, buf.buf, oid, oi, flags);\n> +}\n> +\n\nI was a bit confused why we extracted out obd_loose_path() out, but that\nshould be explained in the next commit.\n\nLooks good.\n\n>  static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c,\n>  \t\t\t     const void *buf, unsigned long len,\n>  \t\t\t     struct object_id *oid,\n>\n> --\n> 2.52.0.660.gd05f3a8ea5.dirty\n"},{"id":"534216","messageId":"CAOLa=ZTupfCEHFHeGtA-r0g5KfghRL0X3BoH6zVTMg-GMZsodw@mail.gmail.com","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-4-5418a91d5d99@pks.im","subject":"Re: [PATCH 04/14] object-file: introduce function to iterate through objects","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-01-20T09:16:34Z","receivedAt":"2026-01-20T09:16:37Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> We have multiple divergent interfaces to iterate through objects of a\n> specific backend:\n>\n>   - `for_each_loose_object()` yields all loose objects.\n>\n>   - `for_each_packed_object()` (somewhat obviously) yields all packed\n>     objects.\n>\n> These functions have different function signatures, which makes it hard\n> to create a common abstraction layer that covers both of these.\n>\n> Introduce a new function `odb_source_loose_for_each_object()` to plug\n> this gap. This function doesn't take any data specific to loose objects,\n> but instead it accepts a `struct object_info` that will be populated the\n> exact same as if `odb_source_loose_read_object()` was called.\n>\n> The benefit of this new interface is that we can continue to pass\n> backend-specific data, as `struct object_info` contains a union for\n> these exact use cases. This will allow us to unify how we iterate\n> through objects across both loose and packed objects in a subsequent\n> commit.\n>\n> The `for_each_loose_object()` function continues to exist for now, but\n> it will be removed at the end of this patch series.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  object-file.c | 41 +++++++++++++++++++++++++++++++++++++++++\n>  object-file.h | 11 +++++++++++\n>  odb.h         | 12 ++++++++++++\n>  3 files changed, 64 insertions(+)\n>\n> diff --git a/object-file.c b/object-file.c\n> index a651129426..65e730684b 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -1801,6 +1801,47 @@ int for_each_loose_object(struct object_database *odb,\n>  \treturn 0;\n>  }\n>\n> +struct for_each_object_wrapper_data {\n> +\tstruct odb_source *source;\n> +\tstruct object_info *oi;\n> +\todb_for_each_object_cb cb;\n> +\tvoid *cb_data;\n> +};\n> +\n> +static int for_each_object_wrapper_cb(const struct object_id *oid,\n> +\t\t\t\t      const char *path,\n> +\t\t\t\t      void *cb_data)\n> +{\n> +\tstruct for_each_object_wrapper_data *data = cb_data;\n> +\tif (data->oi &&\n> +\t    read_object_info_from_path(data->source, path, oid, data->oi, 0) < 0)\n> +\t\t\treturn -1;\n> +\treturn data->cb(oid, data->oi, data->cb_data);\n> +}\n\nOkay so here, we use `read_object_info_from_path()` since we already\nhave the path, we don't need to call `odb_loose_path()`.\n\n[snip]\n"},{"id":"534217","messageId":"CAOLa=ZSgODbmRAHopGejyr1swhDzRa9rccM8TBc3CW=WkRe=pw@mail.gmail.com","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-7-5418a91d5d99@pks.im","subject":"Re: [PATCH 07/14] odb: introduce `odb_for_each_object()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-01-20T09:20:05Z","receivedAt":"2026-01-20T09:20:08Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n[snip]\n\n> diff --git a/odb.h b/odb.h\n> index f97f249580..8f6d95aee5 100644\n> --- a/odb.h\n> +++ b/odb.h\n> @@ -475,6 +475,23 @@ typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n>  \t\t\t\t      struct object_info *oi,\n>  \t\t\t\t      void *cb_data);\n>\n> +/*\n> + * Iterate through all objects contained in the object database. Note that\n> + * objects may be iterated over multiple times in case they are either stored\n> + * in different backends or in case they are stored in multiple sources.\n> + *\n> + * Returning a non-zero error code will cause iteration to abort. The error\n> + * code will be propagated.\n> + *\n\nSuper-Nit: This is for the callback function. It would be nice to be\nexplicit about that.\n\n> + * Returns 0 on success, a negative error code in case a failure occurred, or\n> + * an arbitrary non-zero error code returned by the callback itself.\n> + */\n> +int odb_for_each_object(struct object_database *odb,\n> +\t\t\tstruct object_info *oi,\n> +\t\t\todb_for_each_object_cb cb,\n> +\t\t\tvoid *cb_data,\n> +\t\t\tunsigned flags);\n> +\n>  enum {\n>  \t/*\n>  \t * By default, `odb_write_object()` does not actually write anything\n>\n> --\n> 2.52.0.660.gd05f3a8ea5.dirty\n"},{"id":"534248","messageId":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH v2 00/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:25:56Z","receivedAt":"2026-01-20T15:26:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis patch series introduces a generic `odb_for_each_object()` function\nto iterate through objects and adapts callers to use it. The intent is\nto make iteration through objects independent of the actual storage\nbackend.\n\nThe series is structured as follows:\n\n  - Commits 1 to 2 do some cleanups for the for-each-object flags.\n\n  - Commits 3 to 7 introduce the infrastructure for\n    `odb_for_each_object()`.\n\n  - Commits 8 to 13 convert a couple of callers to use the new\n    interfaces.\n\n  - Commit 14 drops now-unused functions.\n\nThe patch series is built on top of 8745eae506 (The 17th batch,\n2026-01-11) with the following two series merged into it:\n\n  - ps/read-object-info-improvements at a282a8f163 (packfile: move MIDX\n    into packfile store, 2026-01-09).\n\n  - ps/packfile-store-in-odb-source at 12d3b58b55 (packfile: drop\n    repository parameter from `packed_object_info()`, 2026-01-12) .\n\nChanges in v2:\n  - Clarify the comment of `odb_for_each_object()` to point out that\n    it's the callback that can abort iteration by returning a non-zero\n    error code.\n  - Document in the commit message that we don't yet convert all sites\n    to use `odb_for_each_object()`.\n  - Link to v1: https://lore.kernel.org/r/20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (14):\n      odb: rename `FOR_EACH_OBJECT_*` flags\n      odb: fix flags parameter to be unsigned\n      object-file: extract function to read object info from path\n      object-file: introduce function to iterate through objects\n      packfile: extract function to iterate through objects of a store\n      packfile: introduce function to iterate through objects\n      odb: introduce `odb_for_each_object()`\n      builtin/fsck: refactor to use `odb_for_each_object()`\n      treewide: enumerate promisor objects via `odb_for_each_object()`\n      treewide: drop uses of `for_each_{loose,packed}_object()`\n      odb: introduce mtime fields for object info requests\n      builtin/pack-objects: use `packfile_store_for_each_object()`\n      reachable: convert to use `odb_for_each_object()`\n      odb: drop unused `for_each_{loose,packed}_object()` functions\n\n builtin/cat-file.c     |  30 +++++++--\n builtin/fsck.c         |  57 ++++------------\n builtin/pack-objects.c |  47 +++++++-------\n commit-graph.c         |  46 +++++++++----\n object-file.c          | 120 ++++++++++++++++++++++------------\n object-file.h          |  21 +++---\n odb.c                  |  29 +++++++++\n odb.h                  |  43 ++++++++++--\n packfile.c             | 173 +++++++++++++++++++++++++++++++++----------------\n packfile.h             |  18 ++++-\n reachable.c            | 129 +++++++++++-------------------------\n repack-promisor.c      |   8 +--\n revision.c             |  10 ++-\n 13 files changed, 420 insertions(+), 311 deletions(-)\n\nRange-diff versus v1:\n\n 1:  1202ac1d9d =  1:  7658b0e3d1 odb: rename `FOR_EACH_OBJECT_*` flags\n 2:  8fd78aad98 =  2:  c082223854 odb: fix flags parameter to be unsigned\n 3:  40e049c68b =  3:  9d00d20178 object-file: extract function to read object info from path\n 4:  9eaebd1181 =  4:  213548b0ee object-file: introduce function to iterate through objects\n 5:  d88e439de2 =  5:  1521d6285e packfile: extract function to iterate through objects of a store\n 6:  85f52c0db7 =  6:  7dcb9e5cb1 packfile: introduce function to iterate through objects\n 7:  ed42cbcf6b !  7:  9ab2a31068 odb: introduce `odb_for_each_object()`\n    @@ odb.h: typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n     + * objects may be iterated over multiple times in case they are either stored\n     + * in different backends or in case they are stored in multiple sources.\n     + *\n    -+ * Returning a non-zero error code will cause iteration to abort. The error\n    -+ * code will be propagated.\n    ++ * Returning a non-zero error code from the callback function will cause\n    ++ * iteration to abort. The error code will be propagated.\n     + *\n     + * Returns 0 on success, a negative error code in case a failure occurred, or\n     + * an arbitrary non-zero error code returned by the callback itself.\n 8:  39e10e18ed =  8:  343f2007bb builtin/fsck: refactor to use `odb_for_each_object()`\n 9:  d3a87909f2 =  9:  a524a2aae8 treewide: enumerate promisor objects via `odb_for_each_object()`\n10:  06392d8a2e ! 10:  f375828c1f treewide: drop uses of `for_each_{loose,packed}_object()`\n    @@ Commit message\n     \n         Prepare for this by refactoring the sites accordingly.\n     \n    +    Note that ideally, we'd convert all callsites to use the generic\n    +    `odb_for_each_object()` function already. But for some callers this is\n    +    not possible (yet), and it would require some significant refactorings\n    +    to make this work. Converting these site will thus be deferred to a\n    +    later patch series.\n    +\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## builtin/cat-file.c ##\n11:  4a9e5687d0 = 11:  b2b2025502 odb: introduce mtime fields for object info requests\n12:  80284057a8 = 12:  8b596e7a8e builtin/pack-objects: use `packfile_store_for_each_object()`\n13:  7c38197ee5 = 13:  b8bb1cf980 reachable: convert to use `odb_for_each_object()`\n14:  886002ba49 = 14:  b53ac29d2c odb: drop unused `for_each_{loose,packed}_object()` functions\n\n---\nbase-commit: 1ff0e42d332523a11cc3d61b8d8463db5f9f14e8\nchange-id: 20260115-pks-odb-for-each-object-60b78cde09fd\n\n"},{"id":"534249","messageId":"20260120-pks-odb-for-each-object-v2-1-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 01/14] odb: rename `FOR_EACH_OBJECT_*` flags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:25:57Z","receivedAt":"2026-01-20T15:26:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename the `FOR_EACH_OBJECT_*` flags to have an `ODB_` prefix. This\nprepares us for a new upcoming `odb_for_each_object()` function and\nensures that both the function and its flags have the same prefix.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     |  2 +-\n builtin/pack-objects.c | 10 +++++-----\n commit-graph.c         |  4 ++--\n object-file.c          |  4 ++--\n object-file.h          |  2 +-\n odb.h                  | 13 +++++++------\n packfile.c             | 20 ++++++++++----------\n packfile.h             |  4 ++--\n reachable.c            |  8 ++++----\n repack-promisor.c      |  2 +-\n revision.c             |  2 +-\n 11 files changed, 36 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 2ad712e9f8..6964a5a52c 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -922,7 +922,7 @@ static int batch_objects(struct batch_options *opt)\n \t\t\tcb.seen = &seen;\n \n \t\t\tbatch_each_object(opt, batch_unordered_object,\n-\t\t\t\t\t  FOR_EACH_OBJECT_PACK_ORDER, &cb);\n+\t\t\t\t\t  ODB_FOR_EACH_OBJECT_PACK_ORDER, &cb);\n \n \t\t\toidset_clear(&seen);\n \t\t} else {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 6ee31d48c9..74317051fd 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3912,7 +3912,7 @@ static void read_packs_list_from_stdin(struct rev_info *revs)\n \t\tfor_each_object_in_pack(p,\n \t\t\t\t\tadd_object_entry_from_pack,\n \t\t\t\t\trevs,\n-\t\t\t\t\tFOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\tODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t}\n \n \tstrbuf_release(&buf);\n@@ -4344,10 +4344,10 @@ static void add_objects_in_unpacked_packs(void)\n \tif (for_each_packed_object(to_pack.repo,\n \t\t\t\t   add_object_in_unpacked_pack,\n \t\t\t\t   NULL,\n-\t\t\t\t   FOR_EACH_OBJECT_PACK_ORDER |\n-\t\t\t\t   FOR_EACH_OBJECT_LOCAL_ONLY |\n-\t\t\t\t   FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n-\t\t\t\t   FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n \t\tdie(_(\"cannot open pack index\"));\n }\n \ndiff --git a/commit-graph.c b/commit-graph.c\nindex 6b1f02e179..7f1145a082 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1927,7 +1927,7 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,\n \t\t\tgoto cleanup;\n \t\t}\n \t\tfor_each_object_in_pack(p, add_packed_commits, ctx,\n-\t\t\t\t\tFOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\tODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\tclose_pack(p);\n \t\tfree(p);\n \t}\n@@ -1965,7 +1965,7 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n \t\t\t_(\"Finding commits for commit graph among packed objects\"),\n \t\t\tctx->approx_nr_objects);\n \tfor_each_packed_object(ctx->r, add_packed_commits, ctx,\n-\t\t\t       FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \tif (ctx->progress_done < ctx->approx_nr_objects)\n \t\tdisplay_progress(ctx->progress, ctx->approx_nr_objects);\n \tstop_progress(&ctx->progress);\ndiff --git a/object-file.c b/object-file.c\nindex e7e4c3348f..64e9e239dc 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1789,7 +1789,7 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \n int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn cb, void *data,\n-\t\t\t  enum for_each_object_flags flags)\n+\t\t\t  enum odb_for_each_object_flags flags)\n {\n \tstruct odb_source *source;\n \n@@ -1800,7 +1800,7 @@ int for_each_loose_object(struct object_database *odb,\n \t\tif (r)\n \t\t\treturn r;\n \n-\t\tif (flags & FOR_EACH_OBJECT_LOCAL_ONLY)\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY)\n \t\t\tbreak;\n \t}\n \ndiff --git a/object-file.h b/object-file.h\nindex 1229d5f675..42bb50e10c 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -134,7 +134,7 @@ int for_each_loose_file_in_source(struct odb_source *source,\n  */\n int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn, void *,\n-\t\t\t  enum for_each_object_flags flags);\n+\t\t\t  enum odb_for_each_object_flags flags);\n \n \n /**\ndiff --git a/odb.h b/odb.h\nindex bab07755f4..74503addf1 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -442,24 +442,25 @@ static inline void obj_read_unlock(void)\n \tif(obj_read_use_lock)\n \t\tpthread_mutex_unlock(&obj_read_mutex);\n }\n+\n /* Flags for for_each_*_object(). */\n-enum for_each_object_flags {\n+enum odb_for_each_object_flags {\n \t/* Iterate only over local objects, not alternates. */\n-\tFOR_EACH_OBJECT_LOCAL_ONLY = (1<<0),\n+\tODB_FOR_EACH_OBJECT_LOCAL_ONLY = (1<<0),\n \n \t/* Only iterate over packs obtained from the promisor remote. */\n-\tFOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1),\n+\tODB_FOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1),\n \n \t/*\n \t * Visit objects within a pack in packfile order rather than .idx order\n \t */\n-\tFOR_EACH_OBJECT_PACK_ORDER = (1<<2),\n+\tODB_FOR_EACH_OBJECT_PACK_ORDER = (1<<2),\n \n \t/* Only iterate over packs that are not marked as kept in-core. */\n-\tFOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),\n+\tODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),\n \n \t/* Only iterate over packs that do not have .keep files. */\n-\tFOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n+\tODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n };\n \n enum {\ndiff --git a/packfile.c b/packfile.c\nindex 402c3b5dc7..b65f0b43f1 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2259,12 +2259,12 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn cb, void *data,\n-\t\t\t    enum for_each_object_flags flags)\n+\t\t\t    enum odb_for_each_object_flags flags)\n {\n \tuint32_t i;\n \tint r = 0;\n \n-\tif (flags & FOR_EACH_OBJECT_PACK_ORDER) {\n+\tif (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER) {\n \t\tif (load_pack_revindex(p->repo, p))\n \t\t\treturn -1;\n \t}\n@@ -2285,7 +2285,7 @@ int for_each_object_in_pack(struct packed_git *p,\n \t\t *   - in pack-order, it is pack position, which we must\n \t\t *     convert to an index position in order to get the oid.\n \t\t */\n-\t\tif (flags & FOR_EACH_OBJECT_PACK_ORDER)\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER)\n \t\t\tindex_pos = pack_pos_to_index(p, i);\n \t\telse\n \t\t\tindex_pos = i;\n@@ -2302,7 +2302,7 @@ int for_each_object_in_pack(struct packed_git *p,\n }\n \n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum for_each_object_flags flags)\n+\t\t\t   void *data, enum odb_for_each_object_flags flags)\n {\n \tstruct odb_source *source;\n \tint r = 0;\n@@ -2318,15 +2318,15 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\tfor (e = packfile_store_get_packs(source->packfiles); e; e = e->next) {\n \t\t\tstruct packed_git *p = e->pack;\n \n-\t\t\tif ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n \t\t\t    !p->pack_promisor)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n \t\t\t    p->pack_keep_in_core)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n \t\t\t    p->pack_keep)\n \t\t\t\tcontinue;\n \t\t\tif (open_pack_index(p)) {\n@@ -2413,8 +2413,8 @@ int is_promisor_object(struct repository *r, const struct object_id *oid)\n \t\tif (repo_has_promisor_remote(r)) {\n \t\t\tfor_each_packed_object(r, add_promisor_object,\n \t\t\t\t\t       &promisor_objects,\n-\t\t\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY |\n-\t\t\t\t\t       FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY |\n+\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\t}\n \t\tpromisor_objects_prepared = 1;\n \t}\ndiff --git a/packfile.h b/packfile.h\nindex acc5c55ad5..15551258bd 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -339,9 +339,9 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n \t\t\t\t  void *data);\n 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 for_each_object_flags flags);\n+\t\t\t    enum odb_for_each_object_flags flags);\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum for_each_object_flags flags);\n+\t\t\t   void *data, enum odb_for_each_object_flags flags);\n \n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\ndiff --git a/reachable.c b/reachable.c\nindex 4b532039d5..82676b2668 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -307,7 +307,7 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \t\t\t\t\t   int ignore_in_core_kept_packs)\n {\n \tstruct recent_data data;\n-\tenum for_each_object_flags flags;\n+\tenum odb_for_each_object_flags flags;\n \tint r;\n \n \tdata.revs = revs;\n@@ -319,13 +319,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \tdata.extra_recent_oids_loaded = 0;\n \n \tr = for_each_loose_object(the_repository->objects, add_recent_loose, &data,\n-\t\t\t\t  FOR_EACH_OBJECT_LOCAL_ONLY);\n+\t\t\t\t  ODB_FOR_EACH_OBJECT_LOCAL_ONLY);\n \tif (r)\n \t\tgoto done;\n \n-\tflags = FOR_EACH_OBJECT_LOCAL_ONLY | FOR_EACH_OBJECT_PACK_ORDER;\n+\tflags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER;\n \tif (ignore_in_core_kept_packs)\n-\t\tflags |= FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n+\t\tflags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n \n \tr = for_each_packed_object(revs->repo, add_recent_packed, &data, flags);\n \ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex ee6e0669f6..45c330b9a5 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -56,7 +56,7 @@ void repack_promisor_objects(struct repository *repo,\n \tctx.cmd = &cmd;\n \tctx.algop = repo->hash_algo;\n \tfor_each_packed_object(repo, write_oid, &ctx,\n-\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n \t\t/* No packed objects; cmd was never started */\ndiff --git a/revision.c b/revision.c\nindex b65a763770..5aadf46dac 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3938,7 +3938,7 @@ int prepare_revision_walk(struct rev_info *revs)\n \n \tif (revs->exclude_promisor_objects) {\n \t\tfor_each_packed_object(revs->repo, mark_uninteresting, revs,\n-\t\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \t}\n \n \tif (!revs->reflog_info)\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534250","messageId":"20260120-pks-odb-for-each-object-v2-2-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 02/14] odb: fix flags parameter to be unsigned","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:25:58Z","receivedAt":"2026-01-20T15:26:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `flags` parameter accepted by various `for_each_object()` functions\nis a bitfield of multiple flags. Such parameters are typically unsigned\nin the Git codebase, but we use `enum odb_for_each_object_flags` in\nsome places.\n\nAdapt these function signatures to use the correct type.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 3 ++-\n object-file.h | 3 ++-\n packfile.c    | 4 ++--\n packfile.h    | 4 ++--\n 4 files changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 64e9e239dc..8fa461dd59 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -414,7 +414,8 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)\n \n int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\t\t      const struct object_id *oid,\n-\t\t\t\t      struct object_info *oi, int flags)\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags)\n {\n \tint ret;\n \tint fd;\ndiff --git a/object-file.h b/object-file.h\nindex 42bb50e10c..2acf19fb91 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -47,7 +47,8 @@ void odb_source_loose_reprepare(struct odb_source *source);\n \n int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\t\t      const struct object_id *oid,\n-\t\t\t\t      struct object_info *oi, int flags);\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags);\n \n int odb_source_loose_read_object_stream(struct odb_read_stream **out,\n \t\t\t\t\tstruct odb_source *source,\ndiff --git a/packfile.c b/packfile.c\nindex b65f0b43f1..79fe64a25b 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2259,7 +2259,7 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn cb, void *data,\n-\t\t\t    enum odb_for_each_object_flags flags)\n+\t\t\t    unsigned flags)\n {\n \tuint32_t i;\n \tint r = 0;\n@@ -2302,7 +2302,7 @@ int for_each_object_in_pack(struct packed_git *p,\n }\n \n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum odb_for_each_object_flags flags)\n+\t\t\t   void *data, unsigned flags)\n {\n \tstruct odb_source *source;\n \tint r = 0;\ndiff --git a/packfile.h b/packfile.h\nindex 15551258bd..447c44c4a7 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -339,9 +339,9 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n \t\t\t\t  void *data);\n 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+\t\t\t    unsigned flags);\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum odb_for_each_object_flags flags);\n+\t\t\t   void *data, unsigned flags);\n \n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534251","messageId":"20260120-pks-odb-for-each-object-v2-3-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 03/14] object-file: extract function to read object info from path","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:25:59Z","receivedAt":"2026-01-20T15:26:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Extract a new function that allows us to read object info for a specific\nloose object via a user-supplied path. This function will be used in a\nsubsequent commit.\n\nNote that this also allows us to drop `stat_loose_object()`, which is\na simple wrapper around `odb_loose_path()` plus lstat(3p).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 39 ++++++++++++++++-----------------------\n 1 file changed, 16 insertions(+), 23 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 8fa461dd59..a651129426 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -165,30 +165,13 @@ int stream_object_signature(struct repository *r, const struct object_id *oid)\n }\n \n /*\n- * Find \"oid\" as a loose object in given source.\n- * Returns 0 on success, negative on failure.\n+ * Find \"oid\" as a loose object in given source, open the object and return its\n+ * file descriptor. Returns the file descriptor on success, negative on failure.\n  *\n  * The \"path\" out-parameter will give the path of the object we found (if any).\n  * Note that it may point to static storage and is only valid until another\n  * call to stat_loose_object().\n  */\n-static int stat_loose_object(struct odb_source_loose *loose,\n-\t\t\t     const struct object_id *oid,\n-\t\t\t     struct stat *st, const char **path)\n-{\n-\tstatic struct strbuf buf = STRBUF_INIT;\n-\n-\t*path = odb_loose_path(loose->source, &buf, oid);\n-\tif (!lstat(*path, st))\n-\t\treturn 0;\n-\n-\treturn -1;\n-}\n-\n-/*\n- * Like stat_loose_object(), but actually open the object and return the\n- * descriptor. See the caveats on the \"path\" parameter above.\n- */\n static int open_loose_object(struct odb_source_loose *loose,\n \t\t\t     const struct object_id *oid, const char **path)\n {\n@@ -412,7 +395,8 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)\n \treturn 0;\n }\n \n-int odb_source_loose_read_object_info(struct odb_source *source,\n+static int read_object_info_from_path(struct odb_source *source,\n+\t\t\t\t      const char *path,\n \t\t\t\t      const struct object_id *oid,\n \t\t\t\t      struct object_info *oi,\n \t\t\t\t      unsigned flags)\n@@ -420,7 +404,6 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \tint ret;\n \tint fd;\n \tunsigned long mapsize;\n-\tconst char *path;\n \tvoid *map = NULL;\n \tgit_zstream stream, *stream_to_end = NULL;\n \tchar hdr[MAX_HEADER_LEN];\n@@ -443,7 +426,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (stat_loose_object(source->loose, oid, &st, &path) < 0) {\n+\t\tif (lstat(path, &st) < 0) {\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n@@ -455,7 +438,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \t\tgoto out;\n \t}\n \n-\tfd = open_loose_object(source->loose, oid, &path);\n+\tfd = git_open(path);\n \tif (fd < 0) {\n \t\tif (errno != ENOENT)\n \t\t\terror_errno(_(\"unable to open loose object %s\"), oid_to_hex(oid));\n@@ -534,6 +517,16 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \treturn ret;\n }\n \n+int odb_source_loose_read_object_info(struct odb_source *source,\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags)\n+{\n+\tstatic struct strbuf buf = STRBUF_INIT;\n+\todb_loose_path(source, &buf, oid);\n+\treturn read_object_info_from_path(source, buf.buf, oid, oi, flags);\n+}\n+\n static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c,\n \t\t\t     const void *buf, unsigned long len,\n \t\t\t     struct object_id *oid,\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534252","messageId":"20260120-pks-odb-for-each-object-v2-4-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 04/14] object-file: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:00Z","receivedAt":"2026-01-20T15:26:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have multiple divergent interfaces to iterate through objects of a\nspecific backend:\n\n  - `for_each_loose_object()` yields all loose objects.\n\n  - `for_each_packed_object()` (somewhat obviously) yields all packed\n    objects.\n\nThese functions have different function signatures, which makes it hard\nto create a common abstraction layer that covers both of these.\n\nIntroduce a new function `odb_source_loose_for_each_object()` to plug\nthis gap. This function doesn't take any data specific to loose objects,\nbut instead it accepts a `struct object_info` that will be populated the\nexact same as if `odb_source_loose_read_object()` was called.\n\nThe benefit of this new interface is that we can continue to pass\nbackend-specific data, as `struct object_info` contains a union for\nthese exact use cases. This will allow us to unify how we iterate\nthrough objects across both loose and packed objects in a subsequent\ncommit.\n\nThe `for_each_loose_object()` function continues to exist for now, but\nit will be removed at the end of this patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 41 +++++++++++++++++++++++++++++++++++++++++\n object-file.h | 11 +++++++++++\n odb.h         | 12 ++++++++++++\n 3 files changed, 64 insertions(+)\n\ndiff --git a/object-file.c b/object-file.c\nindex a651129426..65e730684b 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1801,6 +1801,47 @@ int for_each_loose_object(struct object_database *odb,\n \treturn 0;\n }\n \n+struct for_each_object_wrapper_data {\n+\tstruct odb_source *source;\n+\tstruct object_info *oi;\n+\todb_for_each_object_cb cb;\n+\tvoid *cb_data;\n+};\n+\n+static int for_each_object_wrapper_cb(const struct object_id *oid,\n+\t\t\t\t      const char *path,\n+\t\t\t\t      void *cb_data)\n+{\n+\tstruct for_each_object_wrapper_data *data = cb_data;\n+\tif (data->oi &&\n+\t    read_object_info_from_path(data->source, path, oid, data->oi, 0) < 0)\n+\t\t\treturn -1;\n+\treturn data->cb(oid, data->oi, data->cb_data);\n+}\n+\n+int odb_source_loose_for_each_object(struct odb_source *source,\n+\t\t\t\t     struct object_info *oi,\n+\t\t\t\t     odb_for_each_object_cb cb,\n+\t\t\t\t     void *cb_data,\n+\t\t\t\t     unsigned flags)\n+{\n+\tstruct for_each_object_wrapper_data data = {\n+\t\t.source = source,\n+\t\t.oi = oi,\n+\t\t.cb = cb,\n+\t\t.cb_data = cb_data,\n+\t};\n+\n+\t/* There are no loose promisor objects, so we can return immediately. */\n+\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY))\n+\t\treturn 0;\n+\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local)\n+\t\treturn 0;\n+\n+\treturn for_each_loose_file_in_source(source, for_each_object_wrapper_cb,\n+\t\t\t\t\t     NULL, NULL, &data);\n+}\n+\n static int append_loose_object(const struct object_id *oid,\n \t\t\t       const char *path UNUSED,\n \t\t\t       void *data)\ndiff --git a/object-file.h b/object-file.h\nindex 2acf19fb91..048b778531 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -137,6 +137,17 @@ int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn, void *,\n \t\t\t  enum odb_for_each_object_flags flags);\n \n+/*\n+ * Iterate through all loose objects in the given object database source and\n+ * invoke the callback function for each of them. If given, the object info\n+ * will be populated with the object's data as if you had called\n+ * `odb_source_loose_read_object_info()` on the object.\n+ */\n+int odb_source_loose_for_each_object(struct odb_source *source,\n+\t\t\t\t     struct object_info *oi,\n+\t\t\t\t     odb_for_each_object_cb cb,\n+\t\t\t\t     void *cb_data,\n+\t\t\t\t     unsigned flags);\n \n /**\n  * format_object_header() is a thin wrapper around s xsnprintf() that\ndiff --git a/odb.h b/odb.h\nindex 74503addf1..f97f249580 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -463,6 +463,18 @@ enum odb_for_each_object_flags {\n \tODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n };\n \n+/*\n+ * A callback function that can be used to iterate through objects. If given,\n+ * the optional `oi` parameter will be populated the same as if you would call\n+ * `odb_read_object_info()`.\n+ *\n+ * Returning a non-zero error code will cause iteration to abort. The error\n+ * code will be propagated.\n+ */\n+typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      void *cb_data);\n+\n enum {\n \t/*\n \t * By default, `odb_write_object()` does not actually write anything\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534253","messageId":"20260120-pks-odb-for-each-object-v2-5-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 05/14] packfile: extract function to iterate through objects of a store","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:01Z","receivedAt":"2026-01-20T15:26:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In the next commit we're about to introduce a new function that knows to\niterate through objects of a given packfile store. Same as with the\nequivalent function for loose objects, this new function will also be\nagnostic of backends by using a `struct object_info`.\n\nPrepare for this by extracting a new shared function to iterate through\na single packfile store.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 78 ++++++++++++++++++++++++++++++++++++--------------------------\n 1 file changed, 45 insertions(+), 33 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex 79fe64a25b..d15a2ce12b 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2301,51 +2301,63 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags)\n+static int packfile_store_for_each_object_internal(struct packfile_store *store,\n+\t\t\t\t\t\t   each_packed_object_fn cb,\n+\t\t\t\t\t\t   void *data,\n+\t\t\t\t\t\t   unsigned flags,\n+\t\t\t\t\t\t   int *pack_errors)\n {\n-\tstruct odb_source *source;\n-\tint r = 0;\n-\tint pack_errors = 0;\n+\tstruct packfile_list_entry *e;\n+\tint ret = 0;\n \n-\todb_prepare_alternates(repo->objects);\n+\tstore->skip_mru_updates = true;\n \n-\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\tstruct packfile_list_entry *e;\n+\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n+\t\tstruct packed_git *p = e->pack;\n \n-\t\tsource->packfiles->skip_mru_updates = true;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((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 ((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\t*pack_errors = 1;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\tfor (e = packfile_store_get_packs(source->packfiles); e; e = e->next) {\n-\t\t\tstruct packed_git *p = e->pack;\n+\t\tret = for_each_object_in_pack(p, cb, data, flags);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n \n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t\t    !p->pack_promisor)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n-\t\t\t    p->pack_keep_in_core)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n-\t\t\t    p->pack_keep)\n-\t\t\t\tcontinue;\n-\t\t\tif (open_pack_index(p)) {\n-\t\t\t\tpack_errors = 1;\n-\t\t\t\tcontinue;\n-\t\t\t}\n+\tstore->skip_mru_updates = false;\n \n-\t\t\tr = for_each_object_in_pack(p, cb, data, flags);\n-\t\t\tif (r)\n-\t\t\t\tbreak;\n-\t\t}\n+\treturn ret;\n+}\n \n-\t\tsource->packfiles->skip_mru_updates = false;\n+int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n+\t\t\t   void *data, unsigned flags)\n+{\n+\tstruct odb_source *source;\n+\tint pack_errors = 0;\n+\tint ret = 0;\n \n-\t\tif (r)\n+\todb_prepare_alternates(repo->objects);\n+\n+\tfor (source = repo->objects->sources; source; source = source->next) {\n+\t\tret = packfile_store_for_each_object_internal(source->packfiles, cb, data,\n+\t\t\t\t\t\t\t      flags, &pack_errors);\n+\t\tif (ret)\n \t\t\tbreak;\n \t}\n \n-\treturn r ? r : pack_errors;\n+\treturn ret ? ret : pack_errors;\n }\n \n static int add_promisor_object(const struct object_id *oid,\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534254","messageId":"20260120-pks-odb-for-each-object-v2-6-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 06/14] packfile: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:02Z","receivedAt":"2026-01-20T15:26:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Introduce a new function `packfile_store_for_each_object()`. This\nfunction is the equivalent to `odb_source_loose_for_each_object()` in\nthat it:\n\n  - Works on a single packfile store and thus per object source.\n\n  - Passes a `struct object_info` to the callback function.\n\nAs such, it provides the same callback interface as we already provide\nfor loose objects now. These functions will be used in a subsequent step\nto implement `odb_for_each_object()`.\n\nThe `for_each_packed_object()` function continues to exist for now, but\nit will be removed at the end of this patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.h | 14 ++++++++++++++\n 2 files changed, 62 insertions(+)\n\ndiff --git a/packfile.c b/packfile.c\nindex d15a2ce12b..cd45c6f21c 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2360,6 +2360,54 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \treturn ret ? ret : pack_errors;\n }\n \n+struct packfile_store_for_each_object_wrapper_data {\n+\tstruct packfile_store *store;\n+\tstruct object_info *oi;\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 packfile_store_for_each_object_wrapper_data *data = cb_data;\n+\n+\tif (data->oi) {\n+\t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n+\n+\t\tif (packed_object_info(pack, offset, data->oi) < 0) {\n+\t\t\tmark_bad_packed_object(pack, oid);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\treturn data->cb(oid, data->oi, data->cb_data);\n+}\n+\n+int packfile_store_for_each_object(struct packfile_store *store,\n+\t\t\t\t   struct object_info *oi,\n+\t\t\t\t   odb_for_each_object_cb cb,\n+\t\t\t\t   void *cb_data,\n+\t\t\t\t   unsigned flags)\n+{\n+\tstruct packfile_store_for_each_object_wrapper_data data = {\n+\t\t.store = store,\n+\t\t.oi = oi,\n+\t\t.cb = cb,\n+\t\t.cb_data = cb_data,\n+\t};\n+\tint pack_errors = 0, ret;\n+\n+\tret = packfile_store_for_each_object_internal(store, packfile_store_for_each_object_wrapper,\n+\t\t\t\t\t\t      &data, flags, &pack_errors);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn pack_errors ? -1 : 0;\n+}\n+\n static int add_promisor_object(const struct object_id *oid,\n \t\t\t       struct packed_git *pack,\n \t\t\t       uint32_t pos UNUSED,\ndiff --git a/packfile.h b/packfile.h\nindex 447c44c4a7..ab0637fbe9 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -343,6 +343,20 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\t\t   void *data, unsigned 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 given, the object info will be\n+ * populated with the object's data as if you had called\n+ * `packfile_store_read_object_info()` on the object.\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+\t\t\t\t   struct object_info *oi,\n+\t\t\t\t   odb_for_each_object_cb cb,\n+\t\t\t\t   void *cb_data,\n+\t\t\t\t   unsigned flags);\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.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534255","messageId":"20260120-pks-odb-for-each-object-v2-7-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 07/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:03Z","receivedAt":"2026-01-20T15:26:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Introduce a new function `odb_for_each_object()` that knows to iterate\nthrough all objects part of a given object database. This function is\nessentially a simple wrapper around the object database sources.\n\nSubsequent commits will adapt callers to use this new function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c | 27 +++++++++++++++++++++++++++\n odb.h | 17 +++++++++++++++++\n 2 files changed, 44 insertions(+)\n\ndiff --git a/odb.c b/odb.c\nindex ac70b6a099..65f0447aa5 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -995,6 +995,33 @@ int odb_freshen_object(struct object_database *odb,\n \treturn 0;\n }\n \n+int odb_for_each_object(struct object_database *odb,\n+\t\t\tstruct object_info *oi,\n+\t\t\todb_for_each_object_cb cb,\n+\t\t\tvoid *cb_data,\n+\t\t\tunsigned flags)\n+{\n+\tint ret;\n+\n+\todb_prepare_alternates(odb);\n+\tfor (struct odb_source *source = odb->sources; source; source = source->next) {\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local)\n+\t\t\tcontinue;\n+\n+\t\tif (!(flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {\n+\t\t\tret = odb_source_loose_for_each_object(source, oi, cb, cb_data, flags);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\n+\t\tret = packfile_store_for_each_object(source->packfiles, oi, cb, cb_data, flags);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n void odb_assert_oid_type(struct object_database *odb,\n \t\t\t const struct object_id *oid, enum object_type expect)\n {\ndiff --git a/odb.h b/odb.h\nindex f97f249580..8a37fe08e0 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -475,6 +475,23 @@ typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n \t\t\t\t      struct object_info *oi,\n \t\t\t\t      void *cb_data);\n \n+/*\n+ * Iterate through all objects contained in the object database. Note that\n+ * objects may be iterated over multiple times in case they are either stored\n+ * in different backends or in case they are stored in multiple sources.\n+ *\n+ * Returning a non-zero error code from the callback function will cause\n+ * iteration to abort. The error code will be propagated.\n+ *\n+ * Returns 0 on success, a negative error code in case a failure occurred, or\n+ * an arbitrary non-zero error code returned by the callback itself.\n+ */\n+int odb_for_each_object(struct object_database *odb,\n+\t\t\tstruct object_info *oi,\n+\t\t\todb_for_each_object_cb cb,\n+\t\t\tvoid *cb_data,\n+\t\t\tunsigned flags);\n+\n enum {\n \t/*\n \t * By default, `odb_write_object()` does not actually write anything\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534256","messageId":"20260120-pks-odb-for-each-object-v2-8-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 08/14] builtin/fsck: refactor to use `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:04Z","receivedAt":"2026-01-20T15:26:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In git-fsck(1) we have two callsites where we iterate over all objects\nvia `for_each_loose_object()` and `for_each_packed_object()`. Both of\nthese are trivially convertible with `odb_for_each_object()`.\n\nRefactor these callsites accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 57 ++++++++++++---------------------------------------------\n 1 file changed, 12 insertions(+), 45 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 4979bc795e..96107695ae 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -218,15 +218,17 @@ static int mark_used(struct object *obj, enum object_type type UNUSED,\n \treturn 0;\n }\n \n-static void mark_unreachable_referents(const struct object_id *oid)\n+static int mark_unreachable_referents(const struct object_id *oid,\n+\t\t\t\t      struct object_info *io UNUSED,\n+\t\t\t\t      void *data UNUSED)\n {\n \tstruct fsck_options options = FSCK_OPTIONS_DEFAULT;\n \tstruct object *obj = lookup_object(the_repository, oid);\n \n \tif (!obj || !(obj->flags & HAS_OBJ))\n-\t\treturn; /* not part of our original set */\n+\t\treturn 0; /* not part of our original set */\n \tif (obj->flags & REACHABLE)\n-\t\treturn; /* reachable objects already traversed */\n+\t\treturn 0; /* reachable objects already traversed */\n \n \t/*\n \t * Avoid passing OBJ_NONE to fsck_walk, which will parse the object\n@@ -243,22 +245,7 @@ static void mark_unreachable_referents(const struct object_id *oid)\n \tfsck_walk(obj, NULL, &options);\n \tif (obj->type == OBJ_TREE)\n \t\tfree_tree_buffer((struct tree *)obj);\n-}\n \n-static int mark_loose_unreachable_referents(const struct object_id *oid,\n-\t\t\t\t\t    const char *path UNUSED,\n-\t\t\t\t\t    void *data UNUSED)\n-{\n-\tmark_unreachable_referents(oid);\n-\treturn 0;\n-}\n-\n-static int mark_packed_unreachable_referents(const struct object_id *oid,\n-\t\t\t\t\t     struct packed_git *pack UNUSED,\n-\t\t\t\t\t     uint32_t pos UNUSED,\n-\t\t\t\t\t     void *data UNUSED)\n-{\n-\tmark_unreachable_referents(oid);\n \treturn 0;\n }\n \n@@ -394,12 +381,8 @@ static void check_connectivity(void)\n \t\t * and ignore any that weren't present in our earlier\n \t\t * traversal.\n \t\t */\n-\t\tfor_each_loose_object(the_repository->objects,\n-\t\t\t\t      mark_loose_unreachable_referents, NULL, 0);\n-\t\tfor_each_packed_object(the_repository,\n-\t\t\t\t       mark_packed_unreachable_referents,\n-\t\t\t\t       NULL,\n-\t\t\t\t       0);\n+\t\todb_for_each_object(the_repository->objects, NULL,\n+\t\t\t\t    mark_unreachable_referents, NULL, 0);\n \t}\n \n \t/* Look up all the requirements, warn about missing objects.. */\n@@ -848,26 +831,12 @@ static void fsck_index(struct index_state *istate, const char *index_path,\n \tfsck_resolve_undo(istate, index_path);\n }\n \n-static void mark_object_for_connectivity(const struct object_id *oid)\n+static int mark_object_for_connectivity(const struct object_id *oid,\n+\t\t\t\t\tstruct object_info *oi UNUSED,\n+\t\t\t\t\tvoid *cb_data UNUSED)\n {\n \tstruct object *obj = lookup_unknown_object(the_repository, oid);\n \tobj->flags |= HAS_OBJ;\n-}\n-\n-static int mark_loose_for_connectivity(const struct object_id *oid,\n-\t\t\t\t       const char *path UNUSED,\n-\t\t\t\t       void *data UNUSED)\n-{\n-\tmark_object_for_connectivity(oid);\n-\treturn 0;\n-}\n-\n-static int mark_packed_for_connectivity(const struct object_id *oid,\n-\t\t\t\t\tstruct packed_git *pack UNUSED,\n-\t\t\t\t\tuint32_t pos UNUSED,\n-\t\t\t\t\tvoid *data UNUSED)\n-{\n-\tmark_object_for_connectivity(oid);\n \treturn 0;\n }\n \n@@ -1001,10 +970,8 @@ int cmd_fsck(int argc,\n \t\tfsck_refs(the_repository);\n \n \tif (connectivity_only) {\n-\t\tfor_each_loose_object(the_repository->objects,\n-\t\t\t\t      mark_loose_for_connectivity, NULL, 0);\n-\t\tfor_each_packed_object(the_repository,\n-\t\t\t\t       mark_packed_for_connectivity, NULL, 0);\n+\t\todb_for_each_object(the_repository->objects, NULL,\n+\t\t\t\t    mark_object_for_connectivity, NULL, 0);\n \t} else {\n \t\todb_prepare_alternates(the_repository->objects);\n \t\tfor (source = the_repository->objects->sources; source; source = source->next)\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534257","messageId":"20260120-pks-odb-for-each-object-v2-9-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 09/14] treewide: enumerate promisor objects via `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:05Z","receivedAt":"2026-01-20T15:26:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have multiple callsites where we enumerate all promisor objects in\nthe object database via `for_each_packed_object()`. This is done by\npassing the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY` flag, which causes us to\nskip over all non-promisor objects.\n\nThese callsites can be trivially converted to `odb_for_each_object()` as\nwe know to skip enumeration of loose objects in case the `PROMISOR_ONLY`\nflag was passed by the caller.\n\nRefactor the sites accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c        | 37 ++++++++++++++++++++++---------------\n repack-promisor.c |  8 ++++----\n revision.c        | 10 ++++------\n 3 files changed, 30 insertions(+), 25 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex cd45c6f21c..4f84bc19d9 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2408,28 +2408,32 @@ int packfile_store_for_each_object(struct packfile_store *store,\n \treturn pack_errors ? -1 : 0;\n }\n \n+struct add_promisor_object_data {\n+\tstruct repository *repo;\n+\tstruct oidset *set;\n+};\n+\n static int add_promisor_object(const struct object_id *oid,\n-\t\t\t       struct packed_git *pack,\n-\t\t\t       uint32_t pos UNUSED,\n-\t\t\t       void *set_)\n+\t\t\t       struct object_info *oi UNUSED,\n+\t\t\t       void *cb_data)\n {\n-\tstruct oidset *set = set_;\n+\tstruct add_promisor_object_data *data = cb_data;\n \tstruct object *obj;\n \tint we_parsed_object;\n \n-\tobj = lookup_object(pack->repo, oid);\n+\tobj = lookup_object(data->repo, oid);\n \tif (obj && obj->parsed) {\n \t\twe_parsed_object = 0;\n \t} else {\n \t\twe_parsed_object = 1;\n-\t\tobj = parse_object_with_flags(pack->repo, oid,\n+\t\tobj = parse_object_with_flags(data->repo, oid,\n \t\t\t\t\t      PARSE_OBJECT_SKIP_HASH_CHECK);\n \t}\n \n \tif (!obj)\n \t\treturn 1;\n \n-\toidset_insert(set, oid);\n+\toidset_insert(data->set, oid);\n \n \t/*\n \t * If this is a tree, commit, or tag, the objects it refers\n@@ -2447,19 +2451,19 @@ static int add_promisor_object(const struct object_id *oid,\n \t\t\t */\n \t\t\treturn 0;\n \t\twhile (tree_entry_gently(&desc, &entry))\n-\t\t\toidset_insert(set, &entry.oid);\n+\t\t\toidset_insert(data->set, &entry.oid);\n \t\tif (we_parsed_object)\n \t\t\tfree_tree_buffer(tree);\n \t} else if (obj->type == OBJ_COMMIT) {\n \t\tstruct commit *commit = (struct commit *) obj;\n \t\tstruct commit_list *parents = commit->parents;\n \n-\t\toidset_insert(set, get_commit_tree_oid(commit));\n+\t\toidset_insert(data->set, get_commit_tree_oid(commit));\n \t\tfor (; parents; parents = parents->next)\n-\t\t\toidset_insert(set, &parents->item->object.oid);\n+\t\t\toidset_insert(data->set, &parents->item->object.oid);\n \t} else if (obj->type == OBJ_TAG) {\n \t\tstruct tag *tag = (struct tag *) obj;\n-\t\toidset_insert(set, get_tagged_oid(tag));\n+\t\toidset_insert(data->set, get_tagged_oid(tag));\n \t}\n \treturn 0;\n }\n@@ -2471,10 +2475,13 @@ int is_promisor_object(struct repository *r, const struct object_id *oid)\n \n \tif (!promisor_objects_prepared) {\n \t\tif (repo_has_promisor_remote(r)) {\n-\t\t\tfor_each_packed_object(r, add_promisor_object,\n-\t\t\t\t\t       &promisor_objects,\n-\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY |\n-\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\tstruct add_promisor_object_data data = {\n+\t\t\t\t.repo = r,\n+\t\t\t\t.set = &promisor_objects,\n+\t\t\t};\n+\n+\t\t\todb_for_each_object(r->objects, NULL, add_promisor_object, &data,\n+\t\t\t\t\t    ODB_FOR_EACH_OBJECT_PROMISOR_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\t}\n \t\tpromisor_objects_prepared = 1;\n \t}\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 45c330b9a5..35c4073632 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -17,8 +17,8 @@ struct write_oid_context {\n  * necessary.\n  */\n static int write_oid(const struct object_id *oid,\n-\t\t     struct packed_git *pack UNUSED,\n-\t\t     uint32_t pos UNUSED, void *data)\n+\t\t     struct object_info *oi UNUSED,\n+\t\t     void *data)\n {\n \tstruct write_oid_context *ctx = data;\n \tstruct child_process *cmd = ctx->cmd;\n@@ -55,8 +55,8 @@ void repack_promisor_objects(struct repository *repo,\n \t */\n \tctx.cmd = &cmd;\n \tctx.algop = repo->hash_algo;\n-\tfor_each_packed_object(repo, write_oid, &ctx,\n-\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\todb_for_each_object(repo->objects, NULL, write_oid, &ctx,\n+\t\t\t    ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n \t\t/* No packed objects; cmd was never started */\ndiff --git a/revision.c b/revision.c\nindex 5aadf46dac..e34bcd8e88 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3626,8 +3626,7 @@ void reset_revision_walk(void)\n }\n \n static int mark_uninteresting(const struct object_id *oid,\n-\t\t\t      struct packed_git *pack UNUSED,\n-\t\t\t      uint32_t pos UNUSED,\n+\t\t\t      struct object_info *oi UNUSED,\n \t\t\t      void *cb)\n {\n \tstruct rev_info *revs = cb;\n@@ -3936,10 +3935,9 @@ int prepare_revision_walk(struct rev_info *revs)\n \t    (revs->limited && limiting_can_increase_treesame(revs)))\n \t\trevs->treesame.name = \"treesame\";\n \n-\tif (revs->exclude_promisor_objects) {\n-\t\tfor_each_packed_object(revs->repo, mark_uninteresting, revs,\n-\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n-\t}\n+\tif (revs->exclude_promisor_objects)\n+\t\todb_for_each_object(revs->repo->objects, NULL, mark_uninteresting,\n+\t\t\t\t    revs, ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (!revs->reflog_info)\n \t\tprepare_to_use_bloom_filter(revs);\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534258","messageId":"20260120-pks-odb-for-each-object-v2-10-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 10/14] treewide: drop uses of `for_each_{loose,packed}_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:06Z","receivedAt":"2026-01-20T15:26:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're using `for_each_loose_object()` and `for_each_packed_object()` at\na couple of callsites to enumerate all loose and packed objects,\nrespectively. These functions will be removed in a subsequent commit in\nfavor of the newly introduced `odb_source_loose_for_each_object()` and\n`packfile_store_for_each_object()` replacements.\n\nPrepare for this by refactoring the sites accordingly.\n\nNote that ideally, we'd convert all callsites to use the generic\n`odb_for_each_object()` function already. But for some callers this is\nnot possible (yet), and it would require some significant refactorings\nto make this work. Converting these site will thus be deferred to a\nlater patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c | 28 ++++++++++++++++++++++------\n commit-graph.c     | 44 +++++++++++++++++++++++++++++++-------------\n 2 files changed, 53 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 6964a5a52c..7d16fbc1b8 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -806,11 +806,14 @@ struct for_each_object_payload {\n \tvoid *payload;\n };\n \n-static int batch_one_object_loose(const struct object_id *oid,\n-\t\t\t\t  const char *path UNUSED,\n-\t\t\t\t  void *_payload)\n+static int batch_one_object_oi(const struct object_id *oid,\n+\t\t\t       struct object_info *oi,\n+\t\t\t       void *_payload)\n {\n \tstruct for_each_object_payload *payload = _payload;\n+\tif (oi && oi->whence == OI_PACKED)\n+\t\treturn payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,\n+\t\t\t\t\t payload->payload);\n \treturn payload->callback(oid, NULL, 0, payload->payload);\n }\n \n@@ -846,8 +849,15 @@ static void batch_each_object(struct batch_options *opt,\n \t\t.payload = _payload,\n \t};\n \tstruct bitmap_index *bitmap = prepare_bitmap_git(the_repository);\n+\tstruct odb_source *source;\n \n-\tfor_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);\n+\todb_prepare_alternates(the_repository->objects);\n+\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\t\tint ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi,\n+\t\t\t\t\t\t\t   &payload, flags);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n \n \tif (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,\n \t\t\t\t\t\t batch_one_object_bitmapped, &payload)) {\n@@ -861,8 +871,14 @@ static void batch_each_object(struct batch_options *opt,\n \t\t\t\t\t\t&payload, flags);\n \t\t}\n \t} else {\n-\t\tfor_each_packed_object(the_repository, batch_one_object_packed,\n-\t\t\t\t       &payload, flags);\n+\t\tstruct object_info oi = { 0 };\n+\n+\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\t\t\tint ret = packfile_store_for_each_object(source->packfiles, &oi,\n+\t\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}\n \t}\n \n \tfree_bitmap_index(bitmap);\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 7f1145a082..a3087d7883 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1479,30 +1479,38 @@ static int write_graph_chunk_bloom_data(struct hashfile *f,\n \treturn 0;\n }\n \n+static int add_packed_commits_oi(const struct object_id *oid,\n+\t\t\t\t struct object_info *oi,\n+\t\t\t\t void *data)\n+{\n+\tstruct write_commit_graph_context *ctx = (struct write_commit_graph_context*)data;\n+\n+\tif (ctx->progress)\n+\t\tdisplay_progress(ctx->progress, ++ctx->progress_done);\n+\n+\tif (*oi->typep != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\toid_array_append(&ctx->oids, oid);\n+\tset_commit_pos(ctx->r, oid);\n+\n+\treturn 0;\n+}\n+\n static int add_packed_commits(const struct object_id *oid,\n \t\t\t      struct packed_git *pack,\n \t\t\t      uint32_t pos,\n \t\t\t      void *data)\n {\n-\tstruct write_commit_graph_context *ctx = (struct write_commit_graph_context*)data;\n \tenum object_type type;\n \toff_t offset = nth_packed_object_offset(pack, pos);\n \tstruct object_info oi = OBJECT_INFO_INIT;\n \n-\tif (ctx->progress)\n-\t\tdisplay_progress(ctx->progress, ++ctx->progress_done);\n-\n \toi.typep = &type;\n \tif (packed_object_info(pack, offset, &oi) < 0)\n \t\tdie(_(\"unable to get type of object %s\"), oid_to_hex(oid));\n \n-\tif (type != OBJ_COMMIT)\n-\t\treturn 0;\n-\n-\toid_array_append(&ctx->oids, oid);\n-\tset_commit_pos(ctx->r, oid);\n-\n-\treturn 0;\n+\treturn add_packed_commits_oi(oid, &oi, data);\n }\n \n static void add_missing_parents(struct write_commit_graph_context *ctx, struct commit *commit)\n@@ -1959,13 +1967,23 @@ static int fill_oids_from_commits(struct write_commit_graph_context *ctx,\n \n static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n {\n+\tstruct odb_source *source;\n+\tenum object_type type;\n+\tstruct object_info oi = {\n+\t\t.typep = &type,\n+\t};\n+\n \tif (ctx->report_progress)\n \t\tctx->progress = start_delayed_progress(\n \t\t\tctx->r,\n \t\t\t_(\"Finding commits for commit graph among packed objects\"),\n \t\t\tctx->approx_nr_objects);\n-\tfor_each_packed_object(ctx->r, add_packed_commits, ctx,\n-\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\n+\todb_prepare_alternates(ctx->r->objects);\n+\tfor (source = ctx->r->objects->sources; source; source = source->next)\n+\t\tpackfile_store_for_each_object(source->packfiles, &oi, add_packed_commits_oi,\n+\t\t\t\t\t       ctx, ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\n \tif (ctx->progress_done < ctx->approx_nr_objects)\n \t\tdisplay_progress(ctx->progress, ctx->approx_nr_objects);\n \tstop_progress(&ctx->progress);\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534259","messageId":"20260120-pks-odb-for-each-object-v2-11-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 11/14] odb: introduce mtime fields for object info requests","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:07Z","receivedAt":"2026-01-20T15:26:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are some use cases where we need to figure out the mtime for\nobjects. Most importantly, this is the case when we want to prune\nunreachable objects. But getting at that data requires users to manually\nderive the info either via the loose object's mtime, the packfiles'\nmtime or via the \".mtimes\" file.\n\nIntroduce a new `struct object_info::mtimep` pointer that allows callers\nto request an object's mtime. This new field will be used in a\nsubsequent commit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 29 +++++++++++++++++++++++++----\n odb.c         |  2 ++\n odb.h         |  1 +\n packfile.c    | 40 +++++++++++++++++++++++++++++++++-------\n 4 files changed, 61 insertions(+), 11 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 65e730684b..c0f896673b 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -409,6 +409,7 @@ static int read_object_info_from_path(struct odb_source *source,\n \tchar hdr[MAX_HEADER_LEN];\n \tunsigned long size_scratch;\n \tenum object_type type_scratch;\n+\tstruct stat st;\n \n \t/*\n \t * If we don't care about type or size, then we don't\n@@ -421,7 +422,7 @@ static int read_object_info_from_path(struct odb_source *source,\n \tif (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) {\n \t\tstruct stat st;\n \n-\t\tif ((!oi || !oi->disk_sizep) && (flags & OBJECT_INFO_QUICK)) {\n+\t\tif ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) {\n \t\t\tret = quick_has_loose(source->loose, oid) ? 0 : -1;\n \t\t\tgoto out;\n \t\t}\n@@ -431,8 +432,12 @@ static int read_object_info_from_path(struct odb_source *source,\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (oi && oi->disk_sizep)\n-\t\t\t*oi->disk_sizep = st.st_size;\n+\t\tif (oi) {\n+\t\t\tif (oi->disk_sizep)\n+\t\t\t\t*oi->disk_sizep = st.st_size;\n+\t\t\tif (oi->mtimep)\n+\t\t\t\t*oi->mtimep = st.st_mtime;\n+\t\t}\n \n \t\tret = 0;\n \t\tgoto out;\n@@ -446,7 +451,21 @@ static int read_object_info_from_path(struct odb_source *source,\n \t\tgoto out;\n \t}\n \n-\tmap = map_fd(fd, path, &mapsize);\n+\tif (fstat(fd, &st)) {\n+\t\tclose(fd);\n+\t\tret = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tmapsize = xsize_t(st.st_size);\n+\tif (!mapsize) {\n+\t\tclose(fd);\n+\t\tret = error(_(\"object file %s is empty\"), path);\n+\t\tgoto out;\n+\t}\n+\n+\tmap = xmmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n \tif (!map) {\n \t\tret = -1;\n \t\tgoto out;\n@@ -454,6 +473,8 @@ static int read_object_info_from_path(struct odb_source *source,\n \n \tif (oi->disk_sizep)\n \t\t*oi->disk_sizep = mapsize;\n+\tif (oi->mtimep)\n+\t\t*oi->mtimep = st.st_mtime;\n \n \tstream_to_end = &stream;\n \ndiff --git a/odb.c b/odb.c\nindex 65f0447aa5..67decd3908 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -702,6 +702,8 @@ static int do_oid_object_info_extended(struct object_database *odb,\n \t\t\t\toidclr(oi->delta_base_oid, odb->repo->hash_algo);\n \t\t\tif (oi->contentp)\n \t\t\t\t*oi->contentp = xmemdupz(co->buf, co->size);\n+\t\t\tif (oi->mtimep)\n+\t\t\t\t*oi->mtimep = 0;\n \t\t\toi->whence = OI_CACHED;\n \t\t}\n \t\treturn 0;\ndiff --git a/odb.h b/odb.h\nindex 8a37fe08e0..68336d2730 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -317,6 +317,7 @@ struct object_info {\n \toff_t *disk_sizep;\n \tstruct object_id *delta_base_oid;\n \tvoid **contentp;\n+\ttime_t *mtimep;\n \n \t/* Response */\n \tenum {\ndiff --git a/packfile.c b/packfile.c\nindex 4f84bc19d9..c96ec21f86 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1578,13 +1578,14 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,\n \thashmap_add(&delta_base_cache, &ent->ent);\n }\n \n-int packed_object_info(struct packed_git *p,\n-\t\t       off_t obj_offset, struct object_info *oi)\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 {\n \tstruct pack_window *w_curs = NULL;\n \tunsigned long size;\n \toff_t curpos = obj_offset;\n \tenum object_type type = OBJ_NONE;\n+\tuint32_t pack_pos;\n \tint ret;\n \n \t/*\n@@ -1619,16 +1620,34 @@ int packed_object_info(struct packed_git *p,\n \t\t}\n \t}\n \n-\tif (oi->disk_sizep) {\n-\t\tuint32_t pos;\n-\t\tif (offset_to_pack_pos(p, obj_offset, &pos) < 0) {\n+\tif (oi->disk_sizep || (oi->mtimep && p->is_cruft)) {\n+\t\tif (offset_to_pack_pos(p, obj_offset, &pack_pos) < 0) {\n \t\t\terror(\"could not find object at offset %\"PRIuMAX\" \"\n \t\t\t      \"in pack %s\", (uintmax_t)obj_offset, p->pack_name);\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n+\t}\n+\n+\tif (oi->disk_sizep)\n+\t\t*oi->disk_sizep = pack_pos_to_offset(p, pack_pos + 1) - obj_offset;\n+\n+\tif (oi->mtimep) {\n+\t\tif (p->is_cruft) {\n+\t\t\tuint32_t index_pos;\n+\n+\t\t\tif (load_pack_mtimes(p) < 0)\n+\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n+\n+\t\t\tif (maybe_index_pos)\n+\t\t\t\tindex_pos = *maybe_index_pos;\n+\t\t\telse\n+\t\t\t\tindex_pos = pack_pos_to_index(p, pack_pos);\n \n-\t\t*oi->disk_sizep = pack_pos_to_offset(p, pos + 1) - obj_offset;\n+\t\t\t*oi->mtimep = nth_packed_mtime(p, index_pos);\n+\t\t} else {\n+\t\t\t*oi->mtimep = p->mtime;\n+\t\t}\n \t}\n \n \tif (oi->typep) {\n@@ -1681,6 +1700,12 @@ int packed_object_info(struct packed_git *p,\n \treturn ret;\n }\n \n+int packed_object_info(struct packed_git *p, off_t obj_offset,\n+\t\t       struct object_info *oi)\n+{\n+\treturn packed_object_info_with_index_pos(p, obj_offset, NULL, oi);\n+}\n+\n static void *unpack_compressed_entry(struct packed_git *p,\n \t\t\t\t    struct pack_window **w_curs,\n \t\t\t\t    off_t curpos,\n@@ -2377,7 +2402,8 @@ static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n \tif (data->oi) {\n \t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n \n-\t\tif (packed_object_info(pack, offset, data->oi) < 0) {\n+\t\tif (packed_object_info_with_index_pos(pack, offset,\n+\t\t\t\t\t\t      &index_pos, data->oi) < 0) {\n \t\t\tmark_bad_packed_object(pack, oid);\n \t\t\treturn -1;\n \t\t}\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534260","messageId":"20260120-pks-odb-for-each-object-v2-12-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 12/14] builtin/pack-objects: use `packfile_store_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:08Z","receivedAt":"2026-01-20T15:26:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When enumerating objects that are supposed to be stored in a new cruft\npack we use `for_each_packed_object()` and then derive each object's\nmtime individually. Refactor this logic to instead use the new\n`packfile_store_for_each_object()` function with an object info request\nthat asks for the respective mtimes.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/pack-objects.c | 45 +++++++++++++++++++++------------------------\n 1 file changed, 21 insertions(+), 24 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 74317051fd..223ec3b49e 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4314,25 +4314,12 @@ static void show_edge(struct commit *commit)\n }\n \n static int add_object_in_unpacked_pack(const struct object_id *oid,\n-\t\t\t\t       struct packed_git *pack,\n-\t\t\t\t       uint32_t pos,\n+\t\t\t\t       struct object_info *oi,\n \t\t\t\t       void *data UNUSED)\n {\n \tif (cruft) {\n-\t\toff_t offset;\n-\t\ttime_t mtime;\n-\n-\t\tif (pack->is_cruft) {\n-\t\t\tif (load_pack_mtimes(pack) < 0)\n-\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n-\t\t\tmtime = nth_packed_mtime(pack, pos);\n-\t\t} else {\n-\t\t\tmtime = pack->mtime;\n-\t\t}\n-\t\toffset = nth_packed_object_offset(pack, pos);\n-\n-\t\tadd_cruft_object_entry(oid, OBJ_NONE, pack, offset,\n-\t\t\t\t       NULL, mtime);\n+\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack,\n+\t\t\t\t       oi->u.packed.offset, NULL, *oi->mtimep);\n \t} else {\n \t\tadd_object_entry(oid, OBJ_NONE, \"\", 0);\n \t}\n@@ -4341,14 +4328,24 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,\n \n static void add_objects_in_unpacked_packs(void)\n {\n-\tif (for_each_packed_object(to_pack.repo,\n-\t\t\t\t   add_object_in_unpacked_pack,\n-\t\t\t\t   NULL,\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n-\t\tdie(_(\"cannot open pack index\"));\n+\tstruct odb_source *source;\n+\ttime_t mtime;\n+\tstruct object_info oi = {\n+\t\t.mtimep = &mtime,\n+\t};\n+\n+\todb_prepare_alternates(to_pack.repo->objects);\n+\tfor (source = to_pack.repo->objects->sources; source; source = source->next) {\n+\t\tif (!source->local)\n+\t\t\tcontinue;\n+\n+\t\tif (packfile_store_for_each_object(source->packfiles, &oi,\n+\t\t\t\t\t\t   add_object_in_unpacked_pack, NULL,\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n+\t\t\tdie(_(\"cannot open pack index\"));\n+\t}\n }\n \n static int add_loose_object(const struct object_id *oid, const char *path,\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534261","messageId":"20260120-pks-odb-for-each-object-v2-13-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 13/14] reachable: convert to use `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:09Z","receivedAt":"2026-01-20T15:26:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"To figure out which objects expired objects we enumerate all loose and\npacked objects individually so that we can figure out their respective\nmtimes. Refactor the code to instead use `odb_for_each_object()` with a\nrequest that ask for the object mtime instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n reachable.c | 125 +++++++++++++++++-------------------------------------------\n 1 file changed, 35 insertions(+), 90 deletions(-)\n\ndiff --git a/reachable.c b/reachable.c\nindex 82676b2668..101cfc2727 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -191,30 +191,27 @@ static int obj_is_recent(const struct object_id *oid, timestamp_t mtime,\n \treturn oidset_contains(&data->extra_recent_oids, oid);\n }\n \n-static void add_recent_object(const struct object_id *oid,\n-\t\t\t      struct packed_git *pack,\n-\t\t\t      off_t offset,\n-\t\t\t      timestamp_t mtime,\n-\t\t\t      struct recent_data *data)\n+static int want_recent_object(struct recent_data *data,\n+\t\t\t      const struct object_id *oid)\n {\n-\tstruct object *obj;\n-\tenum object_type type;\n+\tif (data->ignore_in_core_kept_packs &&\n+\t    has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE))\n+\t\treturn 0;\n+\treturn 1;\n+}\n \n-\tif (!obj_is_recent(oid, mtime, data))\n-\t\treturn;\n+static int add_recent_object(const struct object_id *oid,\n+\t\t\t     struct object_info *oi,\n+\t\t\t     void *cb_data)\n+{\n+\tstruct recent_data *data = cb_data;\n+\tstruct object *obj;\n \n-\t/*\n-\t * We do not want to call parse_object here, because\n-\t * inflating blobs and trees could be very expensive.\n-\t * However, we do need to know the correct type for\n-\t * later processing, and the revision machinery expects\n-\t * commits and tags to have been parsed.\n-\t */\n-\ttype = odb_read_object_info(the_repository->objects, oid, NULL);\n-\tif (type < 0)\n-\t\tdie(\"unable to get object info for %s\", oid_to_hex(oid));\n+\tif (!want_recent_object(data, oid) ||\n+\t    !obj_is_recent(oid, *oi->mtimep, data))\n+\t\treturn 0;\n \n-\tswitch (type) {\n+\tswitch (*oi->typep) {\n \tcase OBJ_TAG:\n \tcase OBJ_COMMIT:\n \t\tobj = parse_object_or_die(the_repository, oid, NULL);\n@@ -227,77 +224,22 @@ static void add_recent_object(const struct object_id *oid,\n \t\tbreak;\n \tdefault:\n \t\tdie(\"unknown object type for %s: %s\",\n-\t\t    oid_to_hex(oid), type_name(type));\n+\t\t    oid_to_hex(oid), type_name(*oi->typep));\n \t}\n \n \tif (!obj)\n \t\tdie(\"unable to lookup %s\", oid_to_hex(oid));\n-\n-\tadd_pending_object(data->revs, obj, \"\");\n-\tif (data->cb)\n-\t\tdata->cb(obj, pack, offset, mtime);\n-}\n-\n-static int want_recent_object(struct recent_data *data,\n-\t\t\t      const struct object_id *oid)\n-{\n-\tif (data->ignore_in_core_kept_packs &&\n-\t    has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE))\n+\tif (obj->flags & SEEN)\n \t\treturn 0;\n-\treturn 1;\n-}\n \n-static int add_recent_loose(const struct object_id *oid,\n-\t\t\t    const char *path, void *data)\n-{\n-\tstruct stat st;\n-\tstruct object *obj;\n-\n-\tif (!want_recent_object(data, oid))\n-\t\treturn 0;\n-\n-\tobj = lookup_object(the_repository, oid);\n-\n-\tif (obj && obj->flags & SEEN)\n-\t\treturn 0;\n-\n-\tif (stat(path, &st) < 0) {\n-\t\t/*\n-\t\t * It's OK if an object went away during our iteration; this\n-\t\t * could be due to a simultaneous repack. But anything else\n-\t\t * we should abort, since we might then fail to mark objects\n-\t\t * which should not be pruned.\n-\t\t */\n-\t\tif (errno == ENOENT)\n-\t\t\treturn 0;\n-\t\treturn error_errno(\"unable to stat %s\", oid_to_hex(oid));\n+\tadd_pending_object(data->revs, obj, \"\");\n+\tif (data->cb) {\n+\t\tif (oi->whence == OI_PACKED)\n+\t\t\tdata->cb(obj, oi->u.packed.pack, oi->u.packed.offset, *oi->mtimep);\n+\t\telse\n+\t\t\tdata->cb(obj, NULL, 0, *oi->mtimep);\n \t}\n \n-\tadd_recent_object(oid, NULL, 0, st.st_mtime, data);\n-\treturn 0;\n-}\n-\n-static int add_recent_packed(const struct object_id *oid,\n-\t\t\t     struct packed_git *p,\n-\t\t\t     uint32_t pos,\n-\t\t\t     void *data)\n-{\n-\tstruct object *obj;\n-\ttimestamp_t mtime = p->mtime;\n-\n-\tif (!want_recent_object(data, oid))\n-\t\treturn 0;\n-\n-\tobj = lookup_object(the_repository, oid);\n-\n-\tif (obj && obj->flags & SEEN)\n-\t\treturn 0;\n-\tif (p->is_cruft) {\n-\t\tif (load_pack_mtimes(p) < 0)\n-\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n-\t\tmtime = nth_packed_mtime(p, pos);\n-\t}\n-\tadd_recent_object(oid, p, nth_packed_object_offset(p, pos), mtime, data);\n \treturn 0;\n }\n \n@@ -307,7 +249,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \t\t\t\t\t   int ignore_in_core_kept_packs)\n {\n \tstruct recent_data data;\n-\tenum odb_for_each_object_flags flags;\n+\tunsigned flags;\n+\tenum object_type type;\n+\ttime_t mtime;\n+\tstruct object_info oi = {\n+\t\t.mtimep = &mtime,\n+\t\t.typep = &type,\n+\t};\n \tint r;\n \n \tdata.revs = revs;\n@@ -318,16 +266,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \toidset_init(&data.extra_recent_oids, 0);\n \tdata.extra_recent_oids_loaded = 0;\n \n-\tr = for_each_loose_object(the_repository->objects, add_recent_loose, &data,\n-\t\t\t\t  ODB_FOR_EACH_OBJECT_LOCAL_ONLY);\n-\tif (r)\n-\t\tgoto done;\n-\n \tflags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER;\n \tif (ignore_in_core_kept_packs)\n \t\tflags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n \n-\tr = for_each_packed_object(revs->repo, add_recent_packed, &data, flags);\n+\tr = odb_for_each_object(revs->repo->objects, &oi, add_recent_object, &data, flags);\n+\tif (r)\n+\t\tgoto done;\n \n done:\n \toidset_clear(&data.extra_recent_oids);\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534262","messageId":"20260120-pks-odb-for-each-object-v2-14-d05cbfd3d6f8@pks.im","threadId":"64809","inReplyTo":"20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im","subject":"[PATCH v2 14/14] odb: drop unused `for_each_{loose,packed}_object()` functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-20T15:26:10Z","receivedAt":"2026-01-20T15:26:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have converted all callers of `for_each_loose_object()` and\n`for_each_packed_object()` to use their new replacement functions\ninstead. We can thus remove them now.\n\nDo so and inline `packfile_store_for_each_object_internal()` now that it\nonly has a single callsite again. This makes it a bit easier to follow\nthe callback indirection that is happening there.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 20 -------------\n object-file.h | 11 -------\n packfile.c    | 92 +++++++++++++++++++----------------------------------------\n packfile.h    |  2 --\n 4 files changed, 29 insertions(+), 96 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex c0f896673b..bc5209f2fe 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1802,26 +1802,6 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \treturn r;\n }\n \n-int for_each_loose_object(struct object_database *odb,\n-\t\t\t  each_loose_object_fn cb, void *data,\n-\t\t\t  enum odb_for_each_object_flags flags)\n-{\n-\tstruct odb_source *source;\n-\n-\todb_prepare_alternates(odb);\n-\tfor (source = odb->sources; source; source = source->next) {\n-\t\tint r = for_each_loose_file_in_source(source, cb, NULL,\n-\t\t\t\t\t\t      NULL, data);\n-\t\tif (r)\n-\t\t\treturn r;\n-\n-\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY)\n-\t\t\tbreak;\n-\t}\n-\n-\treturn 0;\n-}\n-\n struct for_each_object_wrapper_data {\n \tstruct odb_source *source;\n \tstruct object_info *oi;\ndiff --git a/object-file.h b/object-file.h\nindex 048b778531..af7f57d2a1 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -126,17 +126,6 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \t\t\t\t  each_loose_subdir_fn subdir_cb,\n \t\t\t\t  void *data);\n \n-/*\n- * Iterate over all accessible loose objects without respect to\n- * reachability. By default, this includes both local and alternate objects.\n- * The order in which objects are visited is unspecified.\n- *\n- * Any flags specific to packs are ignored.\n- */\n-int for_each_loose_object(struct object_database *odb,\n-\t\t\t  each_loose_object_fn, void *,\n-\t\t\t  enum odb_for_each_object_flags flags);\n-\n /*\n  * Iterate through all loose objects in the given object database source and\n  * invoke the callback function for each of them. If given, the object info\ndiff --git a/packfile.c b/packfile.c\nindex c96ec21f86..493d81fdca 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2326,65 +2326,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-static int packfile_store_for_each_object_internal(struct packfile_store *store,\n-\t\t\t\t\t\t   each_packed_object_fn cb,\n-\t\t\t\t\t\t   void *data,\n-\t\t\t\t\t\t   unsigned flags,\n-\t\t\t\t\t\t   int *pack_errors)\n-{\n-\tstruct packfile_list_entry *e;\n-\tint ret = 0;\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 ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\tcontinue;\n-\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t    !p->pack_promisor)\n-\t\t\tcontinue;\n-\t\tif ((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 ((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\t*pack_errors = 1;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tret = for_each_object_in_pack(p, cb, data, flags);\n-\t\tif (ret)\n-\t\t\tbreak;\n-\t}\n-\n-\tstore->skip_mru_updates = false;\n-\n-\treturn ret;\n-}\n-\n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags)\n-{\n-\tstruct odb_source *source;\n-\tint pack_errors = 0;\n-\tint ret = 0;\n-\n-\todb_prepare_alternates(repo->objects);\n-\n-\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\tret = packfile_store_for_each_object_internal(source->packfiles, cb, data,\n-\t\t\t\t\t\t\t      flags, &pack_errors);\n-\t\tif (ret)\n-\t\t\tbreak;\n-\t}\n-\n-\treturn ret ? ret : pack_errors;\n-}\n-\n struct packfile_store_for_each_object_wrapper_data {\n \tstruct packfile_store *store;\n \tstruct object_info *oi;\n@@ -2424,12 +2365,37 @@ int packfile_store_for_each_object(struct packfile_store *store,\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-\tret = packfile_store_for_each_object_internal(store, packfile_store_for_each_object_wrapper,\n-\t\t\t\t\t\t      &data, flags, &pack_errors);\n-\tif (ret)\n-\t\treturn ret;\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 ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((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 ((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, flags);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n+\n+\tstore->skip_mru_updates = false;\n \n \treturn pack_errors ? -1 : 0;\n }\ndiff --git a/packfile.h b/packfile.h\nindex ab0637fbe9..8e0d2b7661 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -340,8 +340,6 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn, void *data,\n \t\t\t    unsigned flags);\n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags);\n \n /*\n  * Iterate through all packed objects in the given packfile store and invoke\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534315","messageId":"aXCCoI76k2zjioWb@pks.im","threadId":"64809","inReplyTo":"CAOLa=ZSgODbmRAHopGejyr1swhDzRa9rccM8TBc3CW=WkRe=pw@mail.gmail.com","subject":"Re: [PATCH 07/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T07:39:12Z","receivedAt":"2026-01-21T07:39:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Jan 20, 2026 at 09:20:05AM +0000, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/odb.h b/odb.h\n> > index f97f249580..8f6d95aee5 100644\n> > --- a/odb.h\n> > +++ b/odb.h\n> > @@ -475,6 +475,23 @@ typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n> >  \t\t\t\t      struct object_info *oi,\n> >  \t\t\t\t      void *cb_data);\n> >\n> > +/*\n> > + * Iterate through all objects contained in the object database. Note that\n> > + * objects may be iterated over multiple times in case they are either stored\n> > + * in different backends or in case they are stored in multiple sources.\n> > + *\n> > + * Returning a non-zero error code will cause iteration to abort. The error\n> > + * code will be propagated.\n> > + *\n> \n> Super-Nit: This is for the callback function. It would be nice to be\n> explicit about that.\n\nMakes sense indeed, will change.\n\nPatrick\n"},{"id":"534332","messageId":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH v3 00/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:16Z","receivedAt":"2026-01-21T12:50:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis patch series introduces a generic `odb_for_each_object()` function\nto iterate through objects and adapts callers to use it. The intent is\nto make iteration through objects independent of the actual storage\nbackend.\n\nThe series is structured as follows:\n\n  - Commits 1 to 2 do some cleanups for the for-each-object flags.\n\n  - Commits 3 to 7 introduce the infrastructure for\n    `odb_for_each_object()`.\n\n  - Commits 8 to 13 convert a couple of callers to use the new\n    interfaces.\n\n  - Commit 14 drops now-unused functions.\n\nThe patch series is built on top of 8745eae506 (The 17th batch,\n2026-01-11) with the following two series merged into it:\n\n  - ps/read-object-info-improvements at a282a8f163 (packfile: move MIDX\n    into packfile store, 2026-01-09).\n\n  - ps/packfile-store-in-odb-source at 12d3b58b55 (packfile: drop\n    repository parameter from `packed_object_info()`, 2026-01-12) .\n\nChanges in v3:\n  - Fix error code propagation in last commit.\n  - Link to v2: https://lore.kernel.org/r/20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im\n\nChanges in v2:\n  - Clarify the comment of `odb_for_each_object()` to point out that\n    it's the callback that can abort iteration by returning a non-zero\n    error code.\n  - Document in the commit message that we don't yet convert all sites\n    to use `odb_for_each_object()`.\n  - Link to v1: https://lore.kernel.org/r/20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (14):\n      odb: rename `FOR_EACH_OBJECT_*` flags\n      odb: fix flags parameter to be unsigned\n      object-file: extract function to read object info from path\n      object-file: introduce function to iterate through objects\n      packfile: extract function to iterate through objects of a store\n      packfile: introduce function to iterate through objects\n      odb: introduce `odb_for_each_object()`\n      builtin/fsck: refactor to use `odb_for_each_object()`\n      treewide: enumerate promisor objects via `odb_for_each_object()`\n      treewide: drop uses of `for_each_{loose,packed}_object()`\n      odb: introduce mtime fields for object info requests\n      builtin/pack-objects: use `packfile_store_for_each_object()`\n      reachable: convert to use `odb_for_each_object()`\n      odb: drop unused `for_each_{loose,packed}_object()` functions\n\n builtin/cat-file.c     |  30 +++++++--\n builtin/fsck.c         |  57 ++++------------\n builtin/pack-objects.c |  47 ++++++-------\n commit-graph.c         |  46 +++++++++----\n object-file.c          | 120 +++++++++++++++++++++------------\n object-file.h          |  21 +++---\n odb.c                  |  29 ++++++++\n odb.h                  |  43 ++++++++++--\n packfile.c             | 180 +++++++++++++++++++++++++++++++++----------------\n packfile.h             |  18 ++++-\n reachable.c            | 129 ++++++++++-------------------------\n repack-promisor.c      |   8 +--\n revision.c             |  10 ++-\n 13 files changed, 426 insertions(+), 312 deletions(-)\n\nRange-diff versus v2:\n\n 1:  3cd6a9b898 =  1:  f931af359e odb: rename `FOR_EACH_OBJECT_*` flags\n 2:  2b9a766928 =  2:  4454d3b8e6 odb: fix flags parameter to be unsigned\n 3:  e5a8257291 =  3:  0953291ffc object-file: extract function to read object info from path\n 4:  309fb50d2a =  4:  b0a8ff2d9d object-file: introduce function to iterate through objects\n 5:  8332af532d =  5:  def018bbca packfile: extract function to iterate through objects of a store\n 6:  17675561dc =  6:  caccd45aa0 packfile: introduce function to iterate through objects\n 7:  aa79e2f2ea =  7:  4e429e52b2 odb: introduce `odb_for_each_object()`\n 8:  33737e286b =  8:  8f16adec2c builtin/fsck: refactor to use `odb_for_each_object()`\n 9:  606b944a67 =  9:  a1c95ffc4f treewide: enumerate promisor objects via `odb_for_each_object()`\n10:  bf31434259 = 10:  c0ecc5517e treewide: drop uses of `for_each_{loose,packed}_object()`\n11:  359ac505ae = 11:  1687ac9f3c odb: introduce mtime fields for object info requests\n12:  eb7c6f5571 = 12:  1d4b35e3a5 builtin/pack-objects: use `packfile_store_for_each_object()`\n13:  80227f4d71 = 13:  f360ff980a reachable: convert to use `odb_for_each_object()`\n14:  b614e33feb ! 14:  bbad8b1a2b odb: drop unused `for_each_{loose,packed}_object()` functions\n    @@ packfile.c: int packfile_store_for_each_object(struct packfile_store *store,\n     +\t\tret = for_each_object_in_pack(p, packfile_store_for_each_object_wrapper,\n     +\t\t\t\t\t      &data, flags);\n     +\t\tif (ret)\n    -+\t\t\tbreak;\n    ++\t\t\tgoto out;\n     +\t}\n     +\n    -+\tstore->skip_mru_updates = false;\n    ++\tret = 0;\n      \n    - \treturn pack_errors ? -1 : 0;\n    +-\treturn pack_errors ? -1 : 0;\n    ++out:\n    ++\tstore->skip_mru_updates = false;\n    ++\n    ++\tif (!ret && pack_errors)\n    ++\t\tret = -1;\n    ++\treturn ret;\n      }\n    + \n    + struct add_promisor_object_data {\n     \n      ## packfile.h ##\n     @@ packfile.h: typedef int each_packed_object_fn(const struct object_id *oid,\n\n---\nbase-commit: 1ff0e42d332523a11cc3d61b8d8463db5f9f14e8\nchange-id: 20260115-pks-odb-for-each-object-60b78cde09fd\n\n"},{"id":"534333","messageId":"20260121-pks-odb-for-each-object-v3-1-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 01/14] odb: rename `FOR_EACH_OBJECT_*` flags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:17Z","receivedAt":"2026-01-21T12:50:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename the `FOR_EACH_OBJECT_*` flags to have an `ODB_` prefix. This\nprepares us for a new upcoming `odb_for_each_object()` function and\nensures that both the function and its flags have the same prefix.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     |  2 +-\n builtin/pack-objects.c | 10 +++++-----\n commit-graph.c         |  4 ++--\n object-file.c          |  4 ++--\n object-file.h          |  2 +-\n odb.h                  | 13 +++++++------\n packfile.c             | 20 ++++++++++----------\n packfile.h             |  4 ++--\n reachable.c            |  8 ++++----\n repack-promisor.c      |  2 +-\n revision.c             |  2 +-\n 11 files changed, 36 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 2ad712e9f8..6964a5a52c 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -922,7 +922,7 @@ static int batch_objects(struct batch_options *opt)\n \t\t\tcb.seen = &seen;\n \n \t\t\tbatch_each_object(opt, batch_unordered_object,\n-\t\t\t\t\t  FOR_EACH_OBJECT_PACK_ORDER, &cb);\n+\t\t\t\t\t  ODB_FOR_EACH_OBJECT_PACK_ORDER, &cb);\n \n \t\t\toidset_clear(&seen);\n \t\t} else {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 6ee31d48c9..74317051fd 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3912,7 +3912,7 @@ static void read_packs_list_from_stdin(struct rev_info *revs)\n \t\tfor_each_object_in_pack(p,\n \t\t\t\t\tadd_object_entry_from_pack,\n \t\t\t\t\trevs,\n-\t\t\t\t\tFOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\tODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t}\n \n \tstrbuf_release(&buf);\n@@ -4344,10 +4344,10 @@ static void add_objects_in_unpacked_packs(void)\n \tif (for_each_packed_object(to_pack.repo,\n \t\t\t\t   add_object_in_unpacked_pack,\n \t\t\t\t   NULL,\n-\t\t\t\t   FOR_EACH_OBJECT_PACK_ORDER |\n-\t\t\t\t   FOR_EACH_OBJECT_LOCAL_ONLY |\n-\t\t\t\t   FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n-\t\t\t\t   FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n \t\tdie(_(\"cannot open pack index\"));\n }\n \ndiff --git a/commit-graph.c b/commit-graph.c\nindex 6b1f02e179..7f1145a082 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1927,7 +1927,7 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,\n \t\t\tgoto cleanup;\n \t\t}\n \t\tfor_each_object_in_pack(p, add_packed_commits, ctx,\n-\t\t\t\t\tFOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\tODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\tclose_pack(p);\n \t\tfree(p);\n \t}\n@@ -1965,7 +1965,7 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n \t\t\t_(\"Finding commits for commit graph among packed objects\"),\n \t\t\tctx->approx_nr_objects);\n \tfor_each_packed_object(ctx->r, add_packed_commits, ctx,\n-\t\t\t       FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \tif (ctx->progress_done < ctx->approx_nr_objects)\n \t\tdisplay_progress(ctx->progress, ctx->approx_nr_objects);\n \tstop_progress(&ctx->progress);\ndiff --git a/object-file.c b/object-file.c\nindex e7e4c3348f..64e9e239dc 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1789,7 +1789,7 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \n int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn cb, void *data,\n-\t\t\t  enum for_each_object_flags flags)\n+\t\t\t  enum odb_for_each_object_flags flags)\n {\n \tstruct odb_source *source;\n \n@@ -1800,7 +1800,7 @@ int for_each_loose_object(struct object_database *odb,\n \t\tif (r)\n \t\t\treturn r;\n \n-\t\tif (flags & FOR_EACH_OBJECT_LOCAL_ONLY)\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY)\n \t\t\tbreak;\n \t}\n \ndiff --git a/object-file.h b/object-file.h\nindex 1229d5f675..42bb50e10c 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -134,7 +134,7 @@ int for_each_loose_file_in_source(struct odb_source *source,\n  */\n int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn, void *,\n-\t\t\t  enum for_each_object_flags flags);\n+\t\t\t  enum odb_for_each_object_flags flags);\n \n \n /**\ndiff --git a/odb.h b/odb.h\nindex bab07755f4..74503addf1 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -442,24 +442,25 @@ static inline void obj_read_unlock(void)\n \tif(obj_read_use_lock)\n \t\tpthread_mutex_unlock(&obj_read_mutex);\n }\n+\n /* Flags for for_each_*_object(). */\n-enum for_each_object_flags {\n+enum odb_for_each_object_flags {\n \t/* Iterate only over local objects, not alternates. */\n-\tFOR_EACH_OBJECT_LOCAL_ONLY = (1<<0),\n+\tODB_FOR_EACH_OBJECT_LOCAL_ONLY = (1<<0),\n \n \t/* Only iterate over packs obtained from the promisor remote. */\n-\tFOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1),\n+\tODB_FOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1),\n \n \t/*\n \t * Visit objects within a pack in packfile order rather than .idx order\n \t */\n-\tFOR_EACH_OBJECT_PACK_ORDER = (1<<2),\n+\tODB_FOR_EACH_OBJECT_PACK_ORDER = (1<<2),\n \n \t/* Only iterate over packs that are not marked as kept in-core. */\n-\tFOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),\n+\tODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),\n \n \t/* Only iterate over packs that do not have .keep files. */\n-\tFOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n+\tODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n };\n \n enum {\ndiff --git a/packfile.c b/packfile.c\nindex 402c3b5dc7..b65f0b43f1 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2259,12 +2259,12 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn cb, void *data,\n-\t\t\t    enum for_each_object_flags flags)\n+\t\t\t    enum odb_for_each_object_flags flags)\n {\n \tuint32_t i;\n \tint r = 0;\n \n-\tif (flags & FOR_EACH_OBJECT_PACK_ORDER) {\n+\tif (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER) {\n \t\tif (load_pack_revindex(p->repo, p))\n \t\t\treturn -1;\n \t}\n@@ -2285,7 +2285,7 @@ int for_each_object_in_pack(struct packed_git *p,\n \t\t *   - in pack-order, it is pack position, which we must\n \t\t *     convert to an index position in order to get the oid.\n \t\t */\n-\t\tif (flags & FOR_EACH_OBJECT_PACK_ORDER)\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER)\n \t\t\tindex_pos = pack_pos_to_index(p, i);\n \t\telse\n \t\t\tindex_pos = i;\n@@ -2302,7 +2302,7 @@ int for_each_object_in_pack(struct packed_git *p,\n }\n \n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum for_each_object_flags flags)\n+\t\t\t   void *data, enum odb_for_each_object_flags flags)\n {\n \tstruct odb_source *source;\n \tint r = 0;\n@@ -2318,15 +2318,15 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\tfor (e = packfile_store_get_packs(source->packfiles); e; e = e->next) {\n \t\t\tstruct packed_git *p = e->pack;\n \n-\t\t\tif ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n \t\t\t    !p->pack_promisor)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n \t\t\t    p->pack_keep_in_core)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n \t\t\t    p->pack_keep)\n \t\t\t\tcontinue;\n \t\t\tif (open_pack_index(p)) {\n@@ -2413,8 +2413,8 @@ int is_promisor_object(struct repository *r, const struct object_id *oid)\n \t\tif (repo_has_promisor_remote(r)) {\n \t\t\tfor_each_packed_object(r, add_promisor_object,\n \t\t\t\t\t       &promisor_objects,\n-\t\t\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY |\n-\t\t\t\t\t       FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY |\n+\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\t}\n \t\tpromisor_objects_prepared = 1;\n \t}\ndiff --git a/packfile.h b/packfile.h\nindex acc5c55ad5..15551258bd 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -339,9 +339,9 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n \t\t\t\t  void *data);\n 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 for_each_object_flags flags);\n+\t\t\t    enum odb_for_each_object_flags flags);\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum for_each_object_flags flags);\n+\t\t\t   void *data, enum odb_for_each_object_flags flags);\n \n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\ndiff --git a/reachable.c b/reachable.c\nindex 4b532039d5..82676b2668 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -307,7 +307,7 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \t\t\t\t\t   int ignore_in_core_kept_packs)\n {\n \tstruct recent_data data;\n-\tenum for_each_object_flags flags;\n+\tenum odb_for_each_object_flags flags;\n \tint r;\n \n \tdata.revs = revs;\n@@ -319,13 +319,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \tdata.extra_recent_oids_loaded = 0;\n \n \tr = for_each_loose_object(the_repository->objects, add_recent_loose, &data,\n-\t\t\t\t  FOR_EACH_OBJECT_LOCAL_ONLY);\n+\t\t\t\t  ODB_FOR_EACH_OBJECT_LOCAL_ONLY);\n \tif (r)\n \t\tgoto done;\n \n-\tflags = FOR_EACH_OBJECT_LOCAL_ONLY | FOR_EACH_OBJECT_PACK_ORDER;\n+\tflags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER;\n \tif (ignore_in_core_kept_packs)\n-\t\tflags |= FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n+\t\tflags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n \n \tr = for_each_packed_object(revs->repo, add_recent_packed, &data, flags);\n \ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex ee6e0669f6..45c330b9a5 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -56,7 +56,7 @@ void repack_promisor_objects(struct repository *repo,\n \tctx.cmd = &cmd;\n \tctx.algop = repo->hash_algo;\n \tfor_each_packed_object(repo, write_oid, &ctx,\n-\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n \t\t/* No packed objects; cmd was never started */\ndiff --git a/revision.c b/revision.c\nindex b65a763770..5aadf46dac 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3938,7 +3938,7 @@ int prepare_revision_walk(struct rev_info *revs)\n \n \tif (revs->exclude_promisor_objects) {\n \t\tfor_each_packed_object(revs->repo, mark_uninteresting, revs,\n-\t\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \t}\n \n \tif (!revs->reflog_info)\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534334","messageId":"20260121-pks-odb-for-each-object-v3-2-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 02/14] odb: fix flags parameter to be unsigned","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:18Z","receivedAt":"2026-01-21T12:50:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `flags` parameter accepted by various `for_each_object()` functions\nis a bitfield of multiple flags. Such parameters are typically unsigned\nin the Git codebase, but we use `enum odb_for_each_object_flags` in\nsome places.\n\nAdapt these function signatures to use the correct type.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 3 ++-\n object-file.h | 3 ++-\n packfile.c    | 4 ++--\n packfile.h    | 4 ++--\n 4 files changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 64e9e239dc..8fa461dd59 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -414,7 +414,8 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)\n \n int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\t\t      const struct object_id *oid,\n-\t\t\t\t      struct object_info *oi, int flags)\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags)\n {\n \tint ret;\n \tint fd;\ndiff --git a/object-file.h b/object-file.h\nindex 42bb50e10c..2acf19fb91 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -47,7 +47,8 @@ void odb_source_loose_reprepare(struct odb_source *source);\n \n int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\t\t      const struct object_id *oid,\n-\t\t\t\t      struct object_info *oi, int flags);\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags);\n \n int odb_source_loose_read_object_stream(struct odb_read_stream **out,\n \t\t\t\t\tstruct odb_source *source,\ndiff --git a/packfile.c b/packfile.c\nindex b65f0b43f1..79fe64a25b 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2259,7 +2259,7 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn cb, void *data,\n-\t\t\t    enum odb_for_each_object_flags flags)\n+\t\t\t    unsigned flags)\n {\n \tuint32_t i;\n \tint r = 0;\n@@ -2302,7 +2302,7 @@ int for_each_object_in_pack(struct packed_git *p,\n }\n \n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum odb_for_each_object_flags flags)\n+\t\t\t   void *data, unsigned flags)\n {\n \tstruct odb_source *source;\n \tint r = 0;\ndiff --git a/packfile.h b/packfile.h\nindex 15551258bd..447c44c4a7 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -339,9 +339,9 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n \t\t\t\t  void *data);\n 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+\t\t\t    unsigned flags);\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum odb_for_each_object_flags flags);\n+\t\t\t   void *data, unsigned flags);\n \n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534335","messageId":"20260121-pks-odb-for-each-object-v3-3-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 03/14] object-file: extract function to read object info from path","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:19Z","receivedAt":"2026-01-21T12:50:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Extract a new function that allows us to read object info for a specific\nloose object via a user-supplied path. This function will be used in a\nsubsequent commit.\n\nNote that this also allows us to drop `stat_loose_object()`, which is\na simple wrapper around `odb_loose_path()` plus lstat(3p).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 39 ++++++++++++++++-----------------------\n 1 file changed, 16 insertions(+), 23 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 8fa461dd59..a651129426 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -165,30 +165,13 @@ int stream_object_signature(struct repository *r, const struct object_id *oid)\n }\n \n /*\n- * Find \"oid\" as a loose object in given source.\n- * Returns 0 on success, negative on failure.\n+ * Find \"oid\" as a loose object in given source, open the object and return its\n+ * file descriptor. Returns the file descriptor on success, negative on failure.\n  *\n  * The \"path\" out-parameter will give the path of the object we found (if any).\n  * Note that it may point to static storage and is only valid until another\n  * call to stat_loose_object().\n  */\n-static int stat_loose_object(struct odb_source_loose *loose,\n-\t\t\t     const struct object_id *oid,\n-\t\t\t     struct stat *st, const char **path)\n-{\n-\tstatic struct strbuf buf = STRBUF_INIT;\n-\n-\t*path = odb_loose_path(loose->source, &buf, oid);\n-\tif (!lstat(*path, st))\n-\t\treturn 0;\n-\n-\treturn -1;\n-}\n-\n-/*\n- * Like stat_loose_object(), but actually open the object and return the\n- * descriptor. See the caveats on the \"path\" parameter above.\n- */\n static int open_loose_object(struct odb_source_loose *loose,\n \t\t\t     const struct object_id *oid, const char **path)\n {\n@@ -412,7 +395,8 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)\n \treturn 0;\n }\n \n-int odb_source_loose_read_object_info(struct odb_source *source,\n+static int read_object_info_from_path(struct odb_source *source,\n+\t\t\t\t      const char *path,\n \t\t\t\t      const struct object_id *oid,\n \t\t\t\t      struct object_info *oi,\n \t\t\t\t      unsigned flags)\n@@ -420,7 +404,6 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \tint ret;\n \tint fd;\n \tunsigned long mapsize;\n-\tconst char *path;\n \tvoid *map = NULL;\n \tgit_zstream stream, *stream_to_end = NULL;\n \tchar hdr[MAX_HEADER_LEN];\n@@ -443,7 +426,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (stat_loose_object(source->loose, oid, &st, &path) < 0) {\n+\t\tif (lstat(path, &st) < 0) {\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n@@ -455,7 +438,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \t\tgoto out;\n \t}\n \n-\tfd = open_loose_object(source->loose, oid, &path);\n+\tfd = git_open(path);\n \tif (fd < 0) {\n \t\tif (errno != ENOENT)\n \t\t\terror_errno(_(\"unable to open loose object %s\"), oid_to_hex(oid));\n@@ -534,6 +517,16 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \treturn ret;\n }\n \n+int odb_source_loose_read_object_info(struct odb_source *source,\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags)\n+{\n+\tstatic struct strbuf buf = STRBUF_INIT;\n+\todb_loose_path(source, &buf, oid);\n+\treturn read_object_info_from_path(source, buf.buf, oid, oi, flags);\n+}\n+\n static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c,\n \t\t\t     const void *buf, unsigned long len,\n \t\t\t     struct object_id *oid,\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534336","messageId":"20260121-pks-odb-for-each-object-v3-4-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 04/14] object-file: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:20Z","receivedAt":"2026-01-21T12:50:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have multiple divergent interfaces to iterate through objects of a\nspecific backend:\n\n  - `for_each_loose_object()` yields all loose objects.\n\n  - `for_each_packed_object()` (somewhat obviously) yields all packed\n    objects.\n\nThese functions have different function signatures, which makes it hard\nto create a common abstraction layer that covers both of these.\n\nIntroduce a new function `odb_source_loose_for_each_object()` to plug\nthis gap. This function doesn't take any data specific to loose objects,\nbut instead it accepts a `struct object_info` that will be populated the\nexact same as if `odb_source_loose_read_object()` was called.\n\nThe benefit of this new interface is that we can continue to pass\nbackend-specific data, as `struct object_info` contains a union for\nthese exact use cases. This will allow us to unify how we iterate\nthrough objects across both loose and packed objects in a subsequent\ncommit.\n\nThe `for_each_loose_object()` function continues to exist for now, but\nit will be removed at the end of this patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 41 +++++++++++++++++++++++++++++++++++++++++\n object-file.h | 11 +++++++++++\n odb.h         | 12 ++++++++++++\n 3 files changed, 64 insertions(+)\n\ndiff --git a/object-file.c b/object-file.c\nindex a651129426..65e730684b 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1801,6 +1801,47 @@ int for_each_loose_object(struct object_database *odb,\n \treturn 0;\n }\n \n+struct for_each_object_wrapper_data {\n+\tstruct odb_source *source;\n+\tstruct object_info *oi;\n+\todb_for_each_object_cb cb;\n+\tvoid *cb_data;\n+};\n+\n+static int for_each_object_wrapper_cb(const struct object_id *oid,\n+\t\t\t\t      const char *path,\n+\t\t\t\t      void *cb_data)\n+{\n+\tstruct for_each_object_wrapper_data *data = cb_data;\n+\tif (data->oi &&\n+\t    read_object_info_from_path(data->source, path, oid, data->oi, 0) < 0)\n+\t\t\treturn -1;\n+\treturn data->cb(oid, data->oi, data->cb_data);\n+}\n+\n+int odb_source_loose_for_each_object(struct odb_source *source,\n+\t\t\t\t     struct object_info *oi,\n+\t\t\t\t     odb_for_each_object_cb cb,\n+\t\t\t\t     void *cb_data,\n+\t\t\t\t     unsigned flags)\n+{\n+\tstruct for_each_object_wrapper_data data = {\n+\t\t.source = source,\n+\t\t.oi = oi,\n+\t\t.cb = cb,\n+\t\t.cb_data = cb_data,\n+\t};\n+\n+\t/* There are no loose promisor objects, so we can return immediately. */\n+\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY))\n+\t\treturn 0;\n+\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local)\n+\t\treturn 0;\n+\n+\treturn for_each_loose_file_in_source(source, for_each_object_wrapper_cb,\n+\t\t\t\t\t     NULL, NULL, &data);\n+}\n+\n static int append_loose_object(const struct object_id *oid,\n \t\t\t       const char *path UNUSED,\n \t\t\t       void *data)\ndiff --git a/object-file.h b/object-file.h\nindex 2acf19fb91..048b778531 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -137,6 +137,17 @@ int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn, void *,\n \t\t\t  enum odb_for_each_object_flags flags);\n \n+/*\n+ * Iterate through all loose objects in the given object database source and\n+ * invoke the callback function for each of them. If given, the object info\n+ * will be populated with the object's data as if you had called\n+ * `odb_source_loose_read_object_info()` on the object.\n+ */\n+int odb_source_loose_for_each_object(struct odb_source *source,\n+\t\t\t\t     struct object_info *oi,\n+\t\t\t\t     odb_for_each_object_cb cb,\n+\t\t\t\t     void *cb_data,\n+\t\t\t\t     unsigned flags);\n \n /**\n  * format_object_header() is a thin wrapper around s xsnprintf() that\ndiff --git a/odb.h b/odb.h\nindex 74503addf1..f97f249580 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -463,6 +463,18 @@ enum odb_for_each_object_flags {\n \tODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n };\n \n+/*\n+ * A callback function that can be used to iterate through objects. If given,\n+ * the optional `oi` parameter will be populated the same as if you would call\n+ * `odb_read_object_info()`.\n+ *\n+ * Returning a non-zero error code will cause iteration to abort. The error\n+ * code will be propagated.\n+ */\n+typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      void *cb_data);\n+\n enum {\n \t/*\n \t * By default, `odb_write_object()` does not actually write anything\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534337","messageId":"20260121-pks-odb-for-each-object-v3-5-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 05/14] packfile: extract function to iterate through objects of a store","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:21Z","receivedAt":"2026-01-21T12:50:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In the next commit we're about to introduce a new function that knows to\niterate through objects of a given packfile store. Same as with the\nequivalent function for loose objects, this new function will also be\nagnostic of backends by using a `struct object_info`.\n\nPrepare for this by extracting a new shared function to iterate through\na single packfile store.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 78 ++++++++++++++++++++++++++++++++++++--------------------------\n 1 file changed, 45 insertions(+), 33 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex 79fe64a25b..d15a2ce12b 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2301,51 +2301,63 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags)\n+static int packfile_store_for_each_object_internal(struct packfile_store *store,\n+\t\t\t\t\t\t   each_packed_object_fn cb,\n+\t\t\t\t\t\t   void *data,\n+\t\t\t\t\t\t   unsigned flags,\n+\t\t\t\t\t\t   int *pack_errors)\n {\n-\tstruct odb_source *source;\n-\tint r = 0;\n-\tint pack_errors = 0;\n+\tstruct packfile_list_entry *e;\n+\tint ret = 0;\n \n-\todb_prepare_alternates(repo->objects);\n+\tstore->skip_mru_updates = true;\n \n-\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\tstruct packfile_list_entry *e;\n+\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n+\t\tstruct packed_git *p = e->pack;\n \n-\t\tsource->packfiles->skip_mru_updates = true;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((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 ((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\t*pack_errors = 1;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\tfor (e = packfile_store_get_packs(source->packfiles); e; e = e->next) {\n-\t\t\tstruct packed_git *p = e->pack;\n+\t\tret = for_each_object_in_pack(p, cb, data, flags);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n \n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t\t    !p->pack_promisor)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n-\t\t\t    p->pack_keep_in_core)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n-\t\t\t    p->pack_keep)\n-\t\t\t\tcontinue;\n-\t\t\tif (open_pack_index(p)) {\n-\t\t\t\tpack_errors = 1;\n-\t\t\t\tcontinue;\n-\t\t\t}\n+\tstore->skip_mru_updates = false;\n \n-\t\t\tr = for_each_object_in_pack(p, cb, data, flags);\n-\t\t\tif (r)\n-\t\t\t\tbreak;\n-\t\t}\n+\treturn ret;\n+}\n \n-\t\tsource->packfiles->skip_mru_updates = false;\n+int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n+\t\t\t   void *data, unsigned flags)\n+{\n+\tstruct odb_source *source;\n+\tint pack_errors = 0;\n+\tint ret = 0;\n \n-\t\tif (r)\n+\todb_prepare_alternates(repo->objects);\n+\n+\tfor (source = repo->objects->sources; source; source = source->next) {\n+\t\tret = packfile_store_for_each_object_internal(source->packfiles, cb, data,\n+\t\t\t\t\t\t\t      flags, &pack_errors);\n+\t\tif (ret)\n \t\t\tbreak;\n \t}\n \n-\treturn r ? r : pack_errors;\n+\treturn ret ? ret : pack_errors;\n }\n \n static int add_promisor_object(const struct object_id *oid,\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534338","messageId":"20260121-pks-odb-for-each-object-v3-6-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 06/14] packfile: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:22Z","receivedAt":"2026-01-21T12:50:49Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Introduce a new function `packfile_store_for_each_object()`. This\nfunction is the equivalent to `odb_source_loose_for_each_object()` in\nthat it:\n\n  - Works on a single packfile store and thus per object source.\n\n  - Passes a `struct object_info` to the callback function.\n\nAs such, it provides the same callback interface as we already provide\nfor loose objects now. These functions will be used in a subsequent step\nto implement `odb_for_each_object()`.\n\nThe `for_each_packed_object()` function continues to exist for now, but\nit will be removed at the end of this patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.h | 14 ++++++++++++++\n 2 files changed, 62 insertions(+)\n\ndiff --git a/packfile.c b/packfile.c\nindex d15a2ce12b..cd45c6f21c 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2360,6 +2360,54 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \treturn ret ? ret : pack_errors;\n }\n \n+struct packfile_store_for_each_object_wrapper_data {\n+\tstruct packfile_store *store;\n+\tstruct object_info *oi;\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 packfile_store_for_each_object_wrapper_data *data = cb_data;\n+\n+\tif (data->oi) {\n+\t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n+\n+\t\tif (packed_object_info(pack, offset, data->oi) < 0) {\n+\t\t\tmark_bad_packed_object(pack, oid);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\treturn data->cb(oid, data->oi, data->cb_data);\n+}\n+\n+int packfile_store_for_each_object(struct packfile_store *store,\n+\t\t\t\t   struct object_info *oi,\n+\t\t\t\t   odb_for_each_object_cb cb,\n+\t\t\t\t   void *cb_data,\n+\t\t\t\t   unsigned flags)\n+{\n+\tstruct packfile_store_for_each_object_wrapper_data data = {\n+\t\t.store = store,\n+\t\t.oi = oi,\n+\t\t.cb = cb,\n+\t\t.cb_data = cb_data,\n+\t};\n+\tint pack_errors = 0, ret;\n+\n+\tret = packfile_store_for_each_object_internal(store, packfile_store_for_each_object_wrapper,\n+\t\t\t\t\t\t      &data, flags, &pack_errors);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn pack_errors ? -1 : 0;\n+}\n+\n static int add_promisor_object(const struct object_id *oid,\n \t\t\t       struct packed_git *pack,\n \t\t\t       uint32_t pos UNUSED,\ndiff --git a/packfile.h b/packfile.h\nindex 447c44c4a7..ab0637fbe9 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -343,6 +343,20 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\t\t   void *data, unsigned 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 given, the object info will be\n+ * populated with the object's data as if you had called\n+ * `packfile_store_read_object_info()` on the object.\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+\t\t\t\t   struct object_info *oi,\n+\t\t\t\t   odb_for_each_object_cb cb,\n+\t\t\t\t   void *cb_data,\n+\t\t\t\t   unsigned flags);\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.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534339","messageId":"20260121-pks-odb-for-each-object-v3-7-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 07/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:23Z","receivedAt":"2026-01-21T12:50:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Introduce a new function `odb_for_each_object()` that knows to iterate\nthrough all objects part of a given object database. This function is\nessentially a simple wrapper around the object database sources.\n\nSubsequent commits will adapt callers to use this new function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c | 27 +++++++++++++++++++++++++++\n odb.h | 17 +++++++++++++++++\n 2 files changed, 44 insertions(+)\n\ndiff --git a/odb.c b/odb.c\nindex ac70b6a099..65f0447aa5 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -995,6 +995,33 @@ int odb_freshen_object(struct object_database *odb,\n \treturn 0;\n }\n \n+int odb_for_each_object(struct object_database *odb,\n+\t\t\tstruct object_info *oi,\n+\t\t\todb_for_each_object_cb cb,\n+\t\t\tvoid *cb_data,\n+\t\t\tunsigned flags)\n+{\n+\tint ret;\n+\n+\todb_prepare_alternates(odb);\n+\tfor (struct odb_source *source = odb->sources; source; source = source->next) {\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local)\n+\t\t\tcontinue;\n+\n+\t\tif (!(flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {\n+\t\t\tret = odb_source_loose_for_each_object(source, oi, cb, cb_data, flags);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\n+\t\tret = packfile_store_for_each_object(source->packfiles, oi, cb, cb_data, flags);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n void odb_assert_oid_type(struct object_database *odb,\n \t\t\t const struct object_id *oid, enum object_type expect)\n {\ndiff --git a/odb.h b/odb.h\nindex f97f249580..8a37fe08e0 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -475,6 +475,23 @@ typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n \t\t\t\t      struct object_info *oi,\n \t\t\t\t      void *cb_data);\n \n+/*\n+ * Iterate through all objects contained in the object database. Note that\n+ * objects may be iterated over multiple times in case they are either stored\n+ * in different backends or in case they are stored in multiple sources.\n+ *\n+ * Returning a non-zero error code from the callback function will cause\n+ * iteration to abort. The error code will be propagated.\n+ *\n+ * Returns 0 on success, a negative error code in case a failure occurred, or\n+ * an arbitrary non-zero error code returned by the callback itself.\n+ */\n+int odb_for_each_object(struct object_database *odb,\n+\t\t\tstruct object_info *oi,\n+\t\t\todb_for_each_object_cb cb,\n+\t\t\tvoid *cb_data,\n+\t\t\tunsigned flags);\n+\n enum {\n \t/*\n \t * By default, `odb_write_object()` does not actually write anything\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534340","messageId":"20260121-pks-odb-for-each-object-v3-8-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 08/14] builtin/fsck: refactor to use `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:24Z","receivedAt":"2026-01-21T12:50:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In git-fsck(1) we have two callsites where we iterate over all objects\nvia `for_each_loose_object()` and `for_each_packed_object()`. Both of\nthese are trivially convertible with `odb_for_each_object()`.\n\nRefactor these callsites accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 57 ++++++++++++---------------------------------------------\n 1 file changed, 12 insertions(+), 45 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 4979bc795e..96107695ae 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -218,15 +218,17 @@ static int mark_used(struct object *obj, enum object_type type UNUSED,\n \treturn 0;\n }\n \n-static void mark_unreachable_referents(const struct object_id *oid)\n+static int mark_unreachable_referents(const struct object_id *oid,\n+\t\t\t\t      struct object_info *io UNUSED,\n+\t\t\t\t      void *data UNUSED)\n {\n \tstruct fsck_options options = FSCK_OPTIONS_DEFAULT;\n \tstruct object *obj = lookup_object(the_repository, oid);\n \n \tif (!obj || !(obj->flags & HAS_OBJ))\n-\t\treturn; /* not part of our original set */\n+\t\treturn 0; /* not part of our original set */\n \tif (obj->flags & REACHABLE)\n-\t\treturn; /* reachable objects already traversed */\n+\t\treturn 0; /* reachable objects already traversed */\n \n \t/*\n \t * Avoid passing OBJ_NONE to fsck_walk, which will parse the object\n@@ -243,22 +245,7 @@ static void mark_unreachable_referents(const struct object_id *oid)\n \tfsck_walk(obj, NULL, &options);\n \tif (obj->type == OBJ_TREE)\n \t\tfree_tree_buffer((struct tree *)obj);\n-}\n \n-static int mark_loose_unreachable_referents(const struct object_id *oid,\n-\t\t\t\t\t    const char *path UNUSED,\n-\t\t\t\t\t    void *data UNUSED)\n-{\n-\tmark_unreachable_referents(oid);\n-\treturn 0;\n-}\n-\n-static int mark_packed_unreachable_referents(const struct object_id *oid,\n-\t\t\t\t\t     struct packed_git *pack UNUSED,\n-\t\t\t\t\t     uint32_t pos UNUSED,\n-\t\t\t\t\t     void *data UNUSED)\n-{\n-\tmark_unreachable_referents(oid);\n \treturn 0;\n }\n \n@@ -394,12 +381,8 @@ static void check_connectivity(void)\n \t\t * and ignore any that weren't present in our earlier\n \t\t * traversal.\n \t\t */\n-\t\tfor_each_loose_object(the_repository->objects,\n-\t\t\t\t      mark_loose_unreachable_referents, NULL, 0);\n-\t\tfor_each_packed_object(the_repository,\n-\t\t\t\t       mark_packed_unreachable_referents,\n-\t\t\t\t       NULL,\n-\t\t\t\t       0);\n+\t\todb_for_each_object(the_repository->objects, NULL,\n+\t\t\t\t    mark_unreachable_referents, NULL, 0);\n \t}\n \n \t/* Look up all the requirements, warn about missing objects.. */\n@@ -848,26 +831,12 @@ static void fsck_index(struct index_state *istate, const char *index_path,\n \tfsck_resolve_undo(istate, index_path);\n }\n \n-static void mark_object_for_connectivity(const struct object_id *oid)\n+static int mark_object_for_connectivity(const struct object_id *oid,\n+\t\t\t\t\tstruct object_info *oi UNUSED,\n+\t\t\t\t\tvoid *cb_data UNUSED)\n {\n \tstruct object *obj = lookup_unknown_object(the_repository, oid);\n \tobj->flags |= HAS_OBJ;\n-}\n-\n-static int mark_loose_for_connectivity(const struct object_id *oid,\n-\t\t\t\t       const char *path UNUSED,\n-\t\t\t\t       void *data UNUSED)\n-{\n-\tmark_object_for_connectivity(oid);\n-\treturn 0;\n-}\n-\n-static int mark_packed_for_connectivity(const struct object_id *oid,\n-\t\t\t\t\tstruct packed_git *pack UNUSED,\n-\t\t\t\t\tuint32_t pos UNUSED,\n-\t\t\t\t\tvoid *data UNUSED)\n-{\n-\tmark_object_for_connectivity(oid);\n \treturn 0;\n }\n \n@@ -1001,10 +970,8 @@ int cmd_fsck(int argc,\n \t\tfsck_refs(the_repository);\n \n \tif (connectivity_only) {\n-\t\tfor_each_loose_object(the_repository->objects,\n-\t\t\t\t      mark_loose_for_connectivity, NULL, 0);\n-\t\tfor_each_packed_object(the_repository,\n-\t\t\t\t       mark_packed_for_connectivity, NULL, 0);\n+\t\todb_for_each_object(the_repository->objects, NULL,\n+\t\t\t\t    mark_object_for_connectivity, NULL, 0);\n \t} else {\n \t\todb_prepare_alternates(the_repository->objects);\n \t\tfor (source = the_repository->objects->sources; source; source = source->next)\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534341","messageId":"20260121-pks-odb-for-each-object-v3-9-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 09/14] treewide: enumerate promisor objects via `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:25Z","receivedAt":"2026-01-21T12:50:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have multiple callsites where we enumerate all promisor objects in\nthe object database via `for_each_packed_object()`. This is done by\npassing the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY` flag, which causes us to\nskip over all non-promisor objects.\n\nThese callsites can be trivially converted to `odb_for_each_object()` as\nwe know to skip enumeration of loose objects in case the `PROMISOR_ONLY`\nflag was passed by the caller.\n\nRefactor the sites accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c        | 37 ++++++++++++++++++++++---------------\n repack-promisor.c |  8 ++++----\n revision.c        | 10 ++++------\n 3 files changed, 30 insertions(+), 25 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex cd45c6f21c..4f84bc19d9 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2408,28 +2408,32 @@ int packfile_store_for_each_object(struct packfile_store *store,\n \treturn pack_errors ? -1 : 0;\n }\n \n+struct add_promisor_object_data {\n+\tstruct repository *repo;\n+\tstruct oidset *set;\n+};\n+\n static int add_promisor_object(const struct object_id *oid,\n-\t\t\t       struct packed_git *pack,\n-\t\t\t       uint32_t pos UNUSED,\n-\t\t\t       void *set_)\n+\t\t\t       struct object_info *oi UNUSED,\n+\t\t\t       void *cb_data)\n {\n-\tstruct oidset *set = set_;\n+\tstruct add_promisor_object_data *data = cb_data;\n \tstruct object *obj;\n \tint we_parsed_object;\n \n-\tobj = lookup_object(pack->repo, oid);\n+\tobj = lookup_object(data->repo, oid);\n \tif (obj && obj->parsed) {\n \t\twe_parsed_object = 0;\n \t} else {\n \t\twe_parsed_object = 1;\n-\t\tobj = parse_object_with_flags(pack->repo, oid,\n+\t\tobj = parse_object_with_flags(data->repo, oid,\n \t\t\t\t\t      PARSE_OBJECT_SKIP_HASH_CHECK);\n \t}\n \n \tif (!obj)\n \t\treturn 1;\n \n-\toidset_insert(set, oid);\n+\toidset_insert(data->set, oid);\n \n \t/*\n \t * If this is a tree, commit, or tag, the objects it refers\n@@ -2447,19 +2451,19 @@ static int add_promisor_object(const struct object_id *oid,\n \t\t\t */\n \t\t\treturn 0;\n \t\twhile (tree_entry_gently(&desc, &entry))\n-\t\t\toidset_insert(set, &entry.oid);\n+\t\t\toidset_insert(data->set, &entry.oid);\n \t\tif (we_parsed_object)\n \t\t\tfree_tree_buffer(tree);\n \t} else if (obj->type == OBJ_COMMIT) {\n \t\tstruct commit *commit = (struct commit *) obj;\n \t\tstruct commit_list *parents = commit->parents;\n \n-\t\toidset_insert(set, get_commit_tree_oid(commit));\n+\t\toidset_insert(data->set, get_commit_tree_oid(commit));\n \t\tfor (; parents; parents = parents->next)\n-\t\t\toidset_insert(set, &parents->item->object.oid);\n+\t\t\toidset_insert(data->set, &parents->item->object.oid);\n \t} else if (obj->type == OBJ_TAG) {\n \t\tstruct tag *tag = (struct tag *) obj;\n-\t\toidset_insert(set, get_tagged_oid(tag));\n+\t\toidset_insert(data->set, get_tagged_oid(tag));\n \t}\n \treturn 0;\n }\n@@ -2471,10 +2475,13 @@ int is_promisor_object(struct repository *r, const struct object_id *oid)\n \n \tif (!promisor_objects_prepared) {\n \t\tif (repo_has_promisor_remote(r)) {\n-\t\t\tfor_each_packed_object(r, add_promisor_object,\n-\t\t\t\t\t       &promisor_objects,\n-\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY |\n-\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\tstruct add_promisor_object_data data = {\n+\t\t\t\t.repo = r,\n+\t\t\t\t.set = &promisor_objects,\n+\t\t\t};\n+\n+\t\t\todb_for_each_object(r->objects, NULL, add_promisor_object, &data,\n+\t\t\t\t\t    ODB_FOR_EACH_OBJECT_PROMISOR_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\t}\n \t\tpromisor_objects_prepared = 1;\n \t}\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 45c330b9a5..35c4073632 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -17,8 +17,8 @@ struct write_oid_context {\n  * necessary.\n  */\n static int write_oid(const struct object_id *oid,\n-\t\t     struct packed_git *pack UNUSED,\n-\t\t     uint32_t pos UNUSED, void *data)\n+\t\t     struct object_info *oi UNUSED,\n+\t\t     void *data)\n {\n \tstruct write_oid_context *ctx = data;\n \tstruct child_process *cmd = ctx->cmd;\n@@ -55,8 +55,8 @@ void repack_promisor_objects(struct repository *repo,\n \t */\n \tctx.cmd = &cmd;\n \tctx.algop = repo->hash_algo;\n-\tfor_each_packed_object(repo, write_oid, &ctx,\n-\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\todb_for_each_object(repo->objects, NULL, write_oid, &ctx,\n+\t\t\t    ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n \t\t/* No packed objects; cmd was never started */\ndiff --git a/revision.c b/revision.c\nindex 5aadf46dac..e34bcd8e88 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3626,8 +3626,7 @@ void reset_revision_walk(void)\n }\n \n static int mark_uninteresting(const struct object_id *oid,\n-\t\t\t      struct packed_git *pack UNUSED,\n-\t\t\t      uint32_t pos UNUSED,\n+\t\t\t      struct object_info *oi UNUSED,\n \t\t\t      void *cb)\n {\n \tstruct rev_info *revs = cb;\n@@ -3936,10 +3935,9 @@ int prepare_revision_walk(struct rev_info *revs)\n \t    (revs->limited && limiting_can_increase_treesame(revs)))\n \t\trevs->treesame.name = \"treesame\";\n \n-\tif (revs->exclude_promisor_objects) {\n-\t\tfor_each_packed_object(revs->repo, mark_uninteresting, revs,\n-\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n-\t}\n+\tif (revs->exclude_promisor_objects)\n+\t\todb_for_each_object(revs->repo->objects, NULL, mark_uninteresting,\n+\t\t\t\t    revs, ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (!revs->reflog_info)\n \t\tprepare_to_use_bloom_filter(revs);\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534342","messageId":"20260121-pks-odb-for-each-object-v3-10-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 10/14] treewide: drop uses of `for_each_{loose,packed}_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:26Z","receivedAt":"2026-01-21T12:51:00Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're using `for_each_loose_object()` and `for_each_packed_object()` at\na couple of callsites to enumerate all loose and packed objects,\nrespectively. These functions will be removed in a subsequent commit in\nfavor of the newly introduced `odb_source_loose_for_each_object()` and\n`packfile_store_for_each_object()` replacements.\n\nPrepare for this by refactoring the sites accordingly.\n\nNote that ideally, we'd convert all callsites to use the generic\n`odb_for_each_object()` function already. But for some callers this is\nnot possible (yet), and it would require some significant refactorings\nto make this work. Converting these site will thus be deferred to a\nlater patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c | 28 ++++++++++++++++++++++------\n commit-graph.c     | 44 +++++++++++++++++++++++++++++++-------------\n 2 files changed, 53 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 6964a5a52c..7d16fbc1b8 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -806,11 +806,14 @@ struct for_each_object_payload {\n \tvoid *payload;\n };\n \n-static int batch_one_object_loose(const struct object_id *oid,\n-\t\t\t\t  const char *path UNUSED,\n-\t\t\t\t  void *_payload)\n+static int batch_one_object_oi(const struct object_id *oid,\n+\t\t\t       struct object_info *oi,\n+\t\t\t       void *_payload)\n {\n \tstruct for_each_object_payload *payload = _payload;\n+\tif (oi && oi->whence == OI_PACKED)\n+\t\treturn payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,\n+\t\t\t\t\t payload->payload);\n \treturn payload->callback(oid, NULL, 0, payload->payload);\n }\n \n@@ -846,8 +849,15 @@ static void batch_each_object(struct batch_options *opt,\n \t\t.payload = _payload,\n \t};\n \tstruct bitmap_index *bitmap = prepare_bitmap_git(the_repository);\n+\tstruct odb_source *source;\n \n-\tfor_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);\n+\todb_prepare_alternates(the_repository->objects);\n+\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\t\tint ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi,\n+\t\t\t\t\t\t\t   &payload, flags);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n \n \tif (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,\n \t\t\t\t\t\t batch_one_object_bitmapped, &payload)) {\n@@ -861,8 +871,14 @@ static void batch_each_object(struct batch_options *opt,\n \t\t\t\t\t\t&payload, flags);\n \t\t}\n \t} else {\n-\t\tfor_each_packed_object(the_repository, batch_one_object_packed,\n-\t\t\t\t       &payload, flags);\n+\t\tstruct object_info oi = { 0 };\n+\n+\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\t\t\tint ret = packfile_store_for_each_object(source->packfiles, &oi,\n+\t\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}\n \t}\n \n \tfree_bitmap_index(bitmap);\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 7f1145a082..a3087d7883 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1479,30 +1479,38 @@ static int write_graph_chunk_bloom_data(struct hashfile *f,\n \treturn 0;\n }\n \n+static int add_packed_commits_oi(const struct object_id *oid,\n+\t\t\t\t struct object_info *oi,\n+\t\t\t\t void *data)\n+{\n+\tstruct write_commit_graph_context *ctx = (struct write_commit_graph_context*)data;\n+\n+\tif (ctx->progress)\n+\t\tdisplay_progress(ctx->progress, ++ctx->progress_done);\n+\n+\tif (*oi->typep != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\toid_array_append(&ctx->oids, oid);\n+\tset_commit_pos(ctx->r, oid);\n+\n+\treturn 0;\n+}\n+\n static int add_packed_commits(const struct object_id *oid,\n \t\t\t      struct packed_git *pack,\n \t\t\t      uint32_t pos,\n \t\t\t      void *data)\n {\n-\tstruct write_commit_graph_context *ctx = (struct write_commit_graph_context*)data;\n \tenum object_type type;\n \toff_t offset = nth_packed_object_offset(pack, pos);\n \tstruct object_info oi = OBJECT_INFO_INIT;\n \n-\tif (ctx->progress)\n-\t\tdisplay_progress(ctx->progress, ++ctx->progress_done);\n-\n \toi.typep = &type;\n \tif (packed_object_info(pack, offset, &oi) < 0)\n \t\tdie(_(\"unable to get type of object %s\"), oid_to_hex(oid));\n \n-\tif (type != OBJ_COMMIT)\n-\t\treturn 0;\n-\n-\toid_array_append(&ctx->oids, oid);\n-\tset_commit_pos(ctx->r, oid);\n-\n-\treturn 0;\n+\treturn add_packed_commits_oi(oid, &oi, data);\n }\n \n static void add_missing_parents(struct write_commit_graph_context *ctx, struct commit *commit)\n@@ -1959,13 +1967,23 @@ static int fill_oids_from_commits(struct write_commit_graph_context *ctx,\n \n static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n {\n+\tstruct odb_source *source;\n+\tenum object_type type;\n+\tstruct object_info oi = {\n+\t\t.typep = &type,\n+\t};\n+\n \tif (ctx->report_progress)\n \t\tctx->progress = start_delayed_progress(\n \t\t\tctx->r,\n \t\t\t_(\"Finding commits for commit graph among packed objects\"),\n \t\t\tctx->approx_nr_objects);\n-\tfor_each_packed_object(ctx->r, add_packed_commits, ctx,\n-\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\n+\todb_prepare_alternates(ctx->r->objects);\n+\tfor (source = ctx->r->objects->sources; source; source = source->next)\n+\t\tpackfile_store_for_each_object(source->packfiles, &oi, add_packed_commits_oi,\n+\t\t\t\t\t       ctx, ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\n \tif (ctx->progress_done < ctx->approx_nr_objects)\n \t\tdisplay_progress(ctx->progress, ctx->approx_nr_objects);\n \tstop_progress(&ctx->progress);\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534343","messageId":"20260121-pks-odb-for-each-object-v3-11-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 11/14] odb: introduce mtime fields for object info requests","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:27Z","receivedAt":"2026-01-21T12:51:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are some use cases where we need to figure out the mtime for\nobjects. Most importantly, this is the case when we want to prune\nunreachable objects. But getting at that data requires users to manually\nderive the info either via the loose object's mtime, the packfiles'\nmtime or via the \".mtimes\" file.\n\nIntroduce a new `struct object_info::mtimep` pointer that allows callers\nto request an object's mtime. This new field will be used in a\nsubsequent commit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 29 +++++++++++++++++++++++++----\n odb.c         |  2 ++\n odb.h         |  1 +\n packfile.c    | 40 +++++++++++++++++++++++++++++++++-------\n 4 files changed, 61 insertions(+), 11 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 65e730684b..c0f896673b 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -409,6 +409,7 @@ static int read_object_info_from_path(struct odb_source *source,\n \tchar hdr[MAX_HEADER_LEN];\n \tunsigned long size_scratch;\n \tenum object_type type_scratch;\n+\tstruct stat st;\n \n \t/*\n \t * If we don't care about type or size, then we don't\n@@ -421,7 +422,7 @@ static int read_object_info_from_path(struct odb_source *source,\n \tif (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) {\n \t\tstruct stat st;\n \n-\t\tif ((!oi || !oi->disk_sizep) && (flags & OBJECT_INFO_QUICK)) {\n+\t\tif ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) {\n \t\t\tret = quick_has_loose(source->loose, oid) ? 0 : -1;\n \t\t\tgoto out;\n \t\t}\n@@ -431,8 +432,12 @@ static int read_object_info_from_path(struct odb_source *source,\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (oi && oi->disk_sizep)\n-\t\t\t*oi->disk_sizep = st.st_size;\n+\t\tif (oi) {\n+\t\t\tif (oi->disk_sizep)\n+\t\t\t\t*oi->disk_sizep = st.st_size;\n+\t\t\tif (oi->mtimep)\n+\t\t\t\t*oi->mtimep = st.st_mtime;\n+\t\t}\n \n \t\tret = 0;\n \t\tgoto out;\n@@ -446,7 +451,21 @@ static int read_object_info_from_path(struct odb_source *source,\n \t\tgoto out;\n \t}\n \n-\tmap = map_fd(fd, path, &mapsize);\n+\tif (fstat(fd, &st)) {\n+\t\tclose(fd);\n+\t\tret = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tmapsize = xsize_t(st.st_size);\n+\tif (!mapsize) {\n+\t\tclose(fd);\n+\t\tret = error(_(\"object file %s is empty\"), path);\n+\t\tgoto out;\n+\t}\n+\n+\tmap = xmmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n \tif (!map) {\n \t\tret = -1;\n \t\tgoto out;\n@@ -454,6 +473,8 @@ static int read_object_info_from_path(struct odb_source *source,\n \n \tif (oi->disk_sizep)\n \t\t*oi->disk_sizep = mapsize;\n+\tif (oi->mtimep)\n+\t\t*oi->mtimep = st.st_mtime;\n \n \tstream_to_end = &stream;\n \ndiff --git a/odb.c b/odb.c\nindex 65f0447aa5..67decd3908 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -702,6 +702,8 @@ static int do_oid_object_info_extended(struct object_database *odb,\n \t\t\t\toidclr(oi->delta_base_oid, odb->repo->hash_algo);\n \t\t\tif (oi->contentp)\n \t\t\t\t*oi->contentp = xmemdupz(co->buf, co->size);\n+\t\t\tif (oi->mtimep)\n+\t\t\t\t*oi->mtimep = 0;\n \t\t\toi->whence = OI_CACHED;\n \t\t}\n \t\treturn 0;\ndiff --git a/odb.h b/odb.h\nindex 8a37fe08e0..68336d2730 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -317,6 +317,7 @@ struct object_info {\n \toff_t *disk_sizep;\n \tstruct object_id *delta_base_oid;\n \tvoid **contentp;\n+\ttime_t *mtimep;\n \n \t/* Response */\n \tenum {\ndiff --git a/packfile.c b/packfile.c\nindex 4f84bc19d9..c96ec21f86 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1578,13 +1578,14 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,\n \thashmap_add(&delta_base_cache, &ent->ent);\n }\n \n-int packed_object_info(struct packed_git *p,\n-\t\t       off_t obj_offset, struct object_info *oi)\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 {\n \tstruct pack_window *w_curs = NULL;\n \tunsigned long size;\n \toff_t curpos = obj_offset;\n \tenum object_type type = OBJ_NONE;\n+\tuint32_t pack_pos;\n \tint ret;\n \n \t/*\n@@ -1619,16 +1620,34 @@ int packed_object_info(struct packed_git *p,\n \t\t}\n \t}\n \n-\tif (oi->disk_sizep) {\n-\t\tuint32_t pos;\n-\t\tif (offset_to_pack_pos(p, obj_offset, &pos) < 0) {\n+\tif (oi->disk_sizep || (oi->mtimep && p->is_cruft)) {\n+\t\tif (offset_to_pack_pos(p, obj_offset, &pack_pos) < 0) {\n \t\t\terror(\"could not find object at offset %\"PRIuMAX\" \"\n \t\t\t      \"in pack %s\", (uintmax_t)obj_offset, p->pack_name);\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n+\t}\n+\n+\tif (oi->disk_sizep)\n+\t\t*oi->disk_sizep = pack_pos_to_offset(p, pack_pos + 1) - obj_offset;\n+\n+\tif (oi->mtimep) {\n+\t\tif (p->is_cruft) {\n+\t\t\tuint32_t index_pos;\n+\n+\t\t\tif (load_pack_mtimes(p) < 0)\n+\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n+\n+\t\t\tif (maybe_index_pos)\n+\t\t\t\tindex_pos = *maybe_index_pos;\n+\t\t\telse\n+\t\t\t\tindex_pos = pack_pos_to_index(p, pack_pos);\n \n-\t\t*oi->disk_sizep = pack_pos_to_offset(p, pos + 1) - obj_offset;\n+\t\t\t*oi->mtimep = nth_packed_mtime(p, index_pos);\n+\t\t} else {\n+\t\t\t*oi->mtimep = p->mtime;\n+\t\t}\n \t}\n \n \tif (oi->typep) {\n@@ -1681,6 +1700,12 @@ int packed_object_info(struct packed_git *p,\n \treturn ret;\n }\n \n+int packed_object_info(struct packed_git *p, off_t obj_offset,\n+\t\t       struct object_info *oi)\n+{\n+\treturn packed_object_info_with_index_pos(p, obj_offset, NULL, oi);\n+}\n+\n static void *unpack_compressed_entry(struct packed_git *p,\n \t\t\t\t    struct pack_window **w_curs,\n \t\t\t\t    off_t curpos,\n@@ -2377,7 +2402,8 @@ static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n \tif (data->oi) {\n \t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n \n-\t\tif (packed_object_info(pack, offset, data->oi) < 0) {\n+\t\tif (packed_object_info_with_index_pos(pack, offset,\n+\t\t\t\t\t\t      &index_pos, data->oi) < 0) {\n \t\t\tmark_bad_packed_object(pack, oid);\n \t\t\treturn -1;\n \t\t}\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534344","messageId":"20260121-pks-odb-for-each-object-v3-12-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 12/14] builtin/pack-objects: use `packfile_store_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:28Z","receivedAt":"2026-01-21T12:51:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When enumerating objects that are supposed to be stored in a new cruft\npack we use `for_each_packed_object()` and then derive each object's\nmtime individually. Refactor this logic to instead use the new\n`packfile_store_for_each_object()` function with an object info request\nthat asks for the respective mtimes.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/pack-objects.c | 45 +++++++++++++++++++++------------------------\n 1 file changed, 21 insertions(+), 24 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 74317051fd..223ec3b49e 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4314,25 +4314,12 @@ static void show_edge(struct commit *commit)\n }\n \n static int add_object_in_unpacked_pack(const struct object_id *oid,\n-\t\t\t\t       struct packed_git *pack,\n-\t\t\t\t       uint32_t pos,\n+\t\t\t\t       struct object_info *oi,\n \t\t\t\t       void *data UNUSED)\n {\n \tif (cruft) {\n-\t\toff_t offset;\n-\t\ttime_t mtime;\n-\n-\t\tif (pack->is_cruft) {\n-\t\t\tif (load_pack_mtimes(pack) < 0)\n-\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n-\t\t\tmtime = nth_packed_mtime(pack, pos);\n-\t\t} else {\n-\t\t\tmtime = pack->mtime;\n-\t\t}\n-\t\toffset = nth_packed_object_offset(pack, pos);\n-\n-\t\tadd_cruft_object_entry(oid, OBJ_NONE, pack, offset,\n-\t\t\t\t       NULL, mtime);\n+\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack,\n+\t\t\t\t       oi->u.packed.offset, NULL, *oi->mtimep);\n \t} else {\n \t\tadd_object_entry(oid, OBJ_NONE, \"\", 0);\n \t}\n@@ -4341,14 +4328,24 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,\n \n static void add_objects_in_unpacked_packs(void)\n {\n-\tif (for_each_packed_object(to_pack.repo,\n-\t\t\t\t   add_object_in_unpacked_pack,\n-\t\t\t\t   NULL,\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n-\t\tdie(_(\"cannot open pack index\"));\n+\tstruct odb_source *source;\n+\ttime_t mtime;\n+\tstruct object_info oi = {\n+\t\t.mtimep = &mtime,\n+\t};\n+\n+\todb_prepare_alternates(to_pack.repo->objects);\n+\tfor (source = to_pack.repo->objects->sources; source; source = source->next) {\n+\t\tif (!source->local)\n+\t\t\tcontinue;\n+\n+\t\tif (packfile_store_for_each_object(source->packfiles, &oi,\n+\t\t\t\t\t\t   add_object_in_unpacked_pack, NULL,\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n+\t\t\tdie(_(\"cannot open pack index\"));\n+\t}\n }\n \n static int add_loose_object(const struct object_id *oid, const char *path,\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534345","messageId":"20260121-pks-odb-for-each-object-v3-13-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 13/14] reachable: convert to use `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:29Z","receivedAt":"2026-01-21T12:51:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"To figure out which objects expired objects we enumerate all loose and\npacked objects individually so that we can figure out their respective\nmtimes. Refactor the code to instead use `odb_for_each_object()` with a\nrequest that ask for the object mtime instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n reachable.c | 125 +++++++++++++++++-------------------------------------------\n 1 file changed, 35 insertions(+), 90 deletions(-)\n\ndiff --git a/reachable.c b/reachable.c\nindex 82676b2668..101cfc2727 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -191,30 +191,27 @@ static int obj_is_recent(const struct object_id *oid, timestamp_t mtime,\n \treturn oidset_contains(&data->extra_recent_oids, oid);\n }\n \n-static void add_recent_object(const struct object_id *oid,\n-\t\t\t      struct packed_git *pack,\n-\t\t\t      off_t offset,\n-\t\t\t      timestamp_t mtime,\n-\t\t\t      struct recent_data *data)\n+static int want_recent_object(struct recent_data *data,\n+\t\t\t      const struct object_id *oid)\n {\n-\tstruct object *obj;\n-\tenum object_type type;\n+\tif (data->ignore_in_core_kept_packs &&\n+\t    has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE))\n+\t\treturn 0;\n+\treturn 1;\n+}\n \n-\tif (!obj_is_recent(oid, mtime, data))\n-\t\treturn;\n+static int add_recent_object(const struct object_id *oid,\n+\t\t\t     struct object_info *oi,\n+\t\t\t     void *cb_data)\n+{\n+\tstruct recent_data *data = cb_data;\n+\tstruct object *obj;\n \n-\t/*\n-\t * We do not want to call parse_object here, because\n-\t * inflating blobs and trees could be very expensive.\n-\t * However, we do need to know the correct type for\n-\t * later processing, and the revision machinery expects\n-\t * commits and tags to have been parsed.\n-\t */\n-\ttype = odb_read_object_info(the_repository->objects, oid, NULL);\n-\tif (type < 0)\n-\t\tdie(\"unable to get object info for %s\", oid_to_hex(oid));\n+\tif (!want_recent_object(data, oid) ||\n+\t    !obj_is_recent(oid, *oi->mtimep, data))\n+\t\treturn 0;\n \n-\tswitch (type) {\n+\tswitch (*oi->typep) {\n \tcase OBJ_TAG:\n \tcase OBJ_COMMIT:\n \t\tobj = parse_object_or_die(the_repository, oid, NULL);\n@@ -227,77 +224,22 @@ static void add_recent_object(const struct object_id *oid,\n \t\tbreak;\n \tdefault:\n \t\tdie(\"unknown object type for %s: %s\",\n-\t\t    oid_to_hex(oid), type_name(type));\n+\t\t    oid_to_hex(oid), type_name(*oi->typep));\n \t}\n \n \tif (!obj)\n \t\tdie(\"unable to lookup %s\", oid_to_hex(oid));\n-\n-\tadd_pending_object(data->revs, obj, \"\");\n-\tif (data->cb)\n-\t\tdata->cb(obj, pack, offset, mtime);\n-}\n-\n-static int want_recent_object(struct recent_data *data,\n-\t\t\t      const struct object_id *oid)\n-{\n-\tif (data->ignore_in_core_kept_packs &&\n-\t    has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE))\n+\tif (obj->flags & SEEN)\n \t\treturn 0;\n-\treturn 1;\n-}\n \n-static int add_recent_loose(const struct object_id *oid,\n-\t\t\t    const char *path, void *data)\n-{\n-\tstruct stat st;\n-\tstruct object *obj;\n-\n-\tif (!want_recent_object(data, oid))\n-\t\treturn 0;\n-\n-\tobj = lookup_object(the_repository, oid);\n-\n-\tif (obj && obj->flags & SEEN)\n-\t\treturn 0;\n-\n-\tif (stat(path, &st) < 0) {\n-\t\t/*\n-\t\t * It's OK if an object went away during our iteration; this\n-\t\t * could be due to a simultaneous repack. But anything else\n-\t\t * we should abort, since we might then fail to mark objects\n-\t\t * which should not be pruned.\n-\t\t */\n-\t\tif (errno == ENOENT)\n-\t\t\treturn 0;\n-\t\treturn error_errno(\"unable to stat %s\", oid_to_hex(oid));\n+\tadd_pending_object(data->revs, obj, \"\");\n+\tif (data->cb) {\n+\t\tif (oi->whence == OI_PACKED)\n+\t\t\tdata->cb(obj, oi->u.packed.pack, oi->u.packed.offset, *oi->mtimep);\n+\t\telse\n+\t\t\tdata->cb(obj, NULL, 0, *oi->mtimep);\n \t}\n \n-\tadd_recent_object(oid, NULL, 0, st.st_mtime, data);\n-\treturn 0;\n-}\n-\n-static int add_recent_packed(const struct object_id *oid,\n-\t\t\t     struct packed_git *p,\n-\t\t\t     uint32_t pos,\n-\t\t\t     void *data)\n-{\n-\tstruct object *obj;\n-\ttimestamp_t mtime = p->mtime;\n-\n-\tif (!want_recent_object(data, oid))\n-\t\treturn 0;\n-\n-\tobj = lookup_object(the_repository, oid);\n-\n-\tif (obj && obj->flags & SEEN)\n-\t\treturn 0;\n-\tif (p->is_cruft) {\n-\t\tif (load_pack_mtimes(p) < 0)\n-\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n-\t\tmtime = nth_packed_mtime(p, pos);\n-\t}\n-\tadd_recent_object(oid, p, nth_packed_object_offset(p, pos), mtime, data);\n \treturn 0;\n }\n \n@@ -307,7 +249,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \t\t\t\t\t   int ignore_in_core_kept_packs)\n {\n \tstruct recent_data data;\n-\tenum odb_for_each_object_flags flags;\n+\tunsigned flags;\n+\tenum object_type type;\n+\ttime_t mtime;\n+\tstruct object_info oi = {\n+\t\t.mtimep = &mtime,\n+\t\t.typep = &type,\n+\t};\n \tint r;\n \n \tdata.revs = revs;\n@@ -318,16 +266,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \toidset_init(&data.extra_recent_oids, 0);\n \tdata.extra_recent_oids_loaded = 0;\n \n-\tr = for_each_loose_object(the_repository->objects, add_recent_loose, &data,\n-\t\t\t\t  ODB_FOR_EACH_OBJECT_LOCAL_ONLY);\n-\tif (r)\n-\t\tgoto done;\n-\n \tflags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER;\n \tif (ignore_in_core_kept_packs)\n \t\tflags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n \n-\tr = for_each_packed_object(revs->repo, add_recent_packed, &data, flags);\n+\tr = odb_for_each_object(revs->repo->objects, &oi, add_recent_object, &data, flags);\n+\tif (r)\n+\t\tgoto done;\n \n done:\n \toidset_clear(&data.extra_recent_oids);\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534346","messageId":"20260121-pks-odb-for-each-object-v3-14-12c4dfd24227@pks.im","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"[PATCH v3 14/14] odb: drop unused `for_each_{loose,packed}_object()` functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-21T12:50:30Z","receivedAt":"2026-01-21T12:51:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have converted all callers of `for_each_loose_object()` and\n`for_each_packed_object()` to use their new replacement functions\ninstead. We can thus remove them now.\n\nDo so and inline `packfile_store_for_each_object_internal()` now that it\nonly has a single callsite again. This makes it a bit easier to follow\nthe callback indirection that is happening there.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 20 ------------\n object-file.h | 11 -------\n packfile.c    | 99 +++++++++++++++++++++--------------------------------------\n packfile.h    |  2 --\n 4 files changed, 35 insertions(+), 97 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex c0f896673b..bc5209f2fe 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1802,26 +1802,6 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \treturn r;\n }\n \n-int for_each_loose_object(struct object_database *odb,\n-\t\t\t  each_loose_object_fn cb, void *data,\n-\t\t\t  enum odb_for_each_object_flags flags)\n-{\n-\tstruct odb_source *source;\n-\n-\todb_prepare_alternates(odb);\n-\tfor (source = odb->sources; source; source = source->next) {\n-\t\tint r = for_each_loose_file_in_source(source, cb, NULL,\n-\t\t\t\t\t\t      NULL, data);\n-\t\tif (r)\n-\t\t\treturn r;\n-\n-\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY)\n-\t\t\tbreak;\n-\t}\n-\n-\treturn 0;\n-}\n-\n struct for_each_object_wrapper_data {\n \tstruct odb_source *source;\n \tstruct object_info *oi;\ndiff --git a/object-file.h b/object-file.h\nindex 048b778531..af7f57d2a1 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -126,17 +126,6 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \t\t\t\t  each_loose_subdir_fn subdir_cb,\n \t\t\t\t  void *data);\n \n-/*\n- * Iterate over all accessible loose objects without respect to\n- * reachability. By default, this includes both local and alternate objects.\n- * The order in which objects are visited is unspecified.\n- *\n- * Any flags specific to packs are ignored.\n- */\n-int for_each_loose_object(struct object_database *odb,\n-\t\t\t  each_loose_object_fn, void *,\n-\t\t\t  enum odb_for_each_object_flags flags);\n-\n /*\n  * Iterate through all loose objects in the given object database source and\n  * invoke the callback function for each of them. If given, the object info\ndiff --git a/packfile.c b/packfile.c\nindex c96ec21f86..6f56b5e2dc 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2326,65 +2326,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-static int packfile_store_for_each_object_internal(struct packfile_store *store,\n-\t\t\t\t\t\t   each_packed_object_fn cb,\n-\t\t\t\t\t\t   void *data,\n-\t\t\t\t\t\t   unsigned flags,\n-\t\t\t\t\t\t   int *pack_errors)\n-{\n-\tstruct packfile_list_entry *e;\n-\tint ret = 0;\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 ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\tcontinue;\n-\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t    !p->pack_promisor)\n-\t\t\tcontinue;\n-\t\tif ((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 ((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\t*pack_errors = 1;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tret = for_each_object_in_pack(p, cb, data, flags);\n-\t\tif (ret)\n-\t\t\tbreak;\n-\t}\n-\n-\tstore->skip_mru_updates = false;\n-\n-\treturn ret;\n-}\n-\n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags)\n-{\n-\tstruct odb_source *source;\n-\tint pack_errors = 0;\n-\tint ret = 0;\n-\n-\todb_prepare_alternates(repo->objects);\n-\n-\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\tret = packfile_store_for_each_object_internal(source->packfiles, cb, data,\n-\t\t\t\t\t\t\t      flags, &pack_errors);\n-\t\tif (ret)\n-\t\t\tbreak;\n-\t}\n-\n-\treturn ret ? ret : pack_errors;\n-}\n-\n struct packfile_store_for_each_object_wrapper_data {\n \tstruct packfile_store *store;\n \tstruct object_info *oi;\n@@ -2424,14 +2365,44 @@ int packfile_store_for_each_object(struct packfile_store *store,\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-\tret = packfile_store_for_each_object_internal(store, packfile_store_for_each_object_wrapper,\n-\t\t\t\t\t\t      &data, flags, &pack_errors);\n-\tif (ret)\n-\t\treturn ret;\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 ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((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 ((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, flags);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tret = 0;\n \n-\treturn pack_errors ? -1 : 0;\n+out:\n+\tstore->skip_mru_updates = false;\n+\n+\tif (!ret && pack_errors)\n+\t\tret = -1;\n+\treturn ret;\n }\n \n struct add_promisor_object_data {\ndiff --git a/packfile.h b/packfile.h\nindex ab0637fbe9..8e0d2b7661 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -340,8 +340,6 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn, void *data,\n \t\t\t    unsigned flags);\n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags);\n \n /*\n  * Iterate through all packed objects in the given packfile store and invoke\n\n-- \n2.53.0.rc0.250.g0ac79233d6.dirty\n\n"},{"id":"534390","messageId":"20260121211128.GB723458@coredump.intra.peff.net","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-2-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 02/14] odb: fix flags parameter to be unsigned","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-01-21T21:11:28Z","receivedAt":"2026-01-21T21:11:29Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:18PM +0100, Patrick Steinhardt wrote:\n\n> The `flags` parameter accepted by various `for_each_object()` functions\n> is a bitfield of multiple flags. Such parameters are typically unsigned\n> in the Git codebase, but we use `enum odb_for_each_object_flags` in\n> some places.\n\nI agree that using \"unsigned\" instead of \"int\" for flags is a good\npractice in general. But isn't using \"unsigned\" instead of an enum\nstrictly worse?\n\nThe enum is more descriptive to human readers (since the type defines\nwhich flags we expect to see). And it lets the compiler use the correct\ntype in the few cases where it might matter. E.g., if you imagine an\nenum that defines 40 bits, then the compiler will know that it needs to\nuse a type larger than 32 bits to store it. Whereas passing a raw\n\"unsigned\" will truncate some values.\n\nI don't expect this latter reason to be common, but if we are going to\nhave a general principle for how to pass flags, it feels like passing\nthe enum (assuming the flags are defined in one) is always better. And\nIMHO just the first reason (human readers) makes it worth doing that way\nanyway.\n\nYou can find this pattern in lots of places (try grepping for \"enum\n[a-z_]* flag\"). The ones that aren't are typically using flags that are\nnot using enums at all (just #defines).\n\n> diff --git a/object-file.c b/object-file.c\n> index 64e9e239dc..8fa461dd59 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -414,7 +414,8 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)\n>  \n>  int odb_source_loose_read_object_info(struct odb_source *source,\n>  \t\t\t\t      const struct object_id *oid,\n> -\t\t\t\t      struct object_info *oi, int flags)\n> +\t\t\t\t      struct object_info *oi,\n> +\t\t\t\t      unsigned flags)\n\nSo I'd argue this should be switching to the enum...\n\n> diff --git a/packfile.h b/packfile.h\n> index 15551258bd..447c44c4a7 100644\n> --- a/packfile.h\n> +++ b/packfile.h\n> @@ -339,9 +339,9 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n>  \t\t\t\t  void *data);\n>  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> +\t\t\t    unsigned flags);\n>  int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n> -\t\t\t   void *data, enum odb_for_each_object_flags flags);\n> +\t\t\t   void *data, unsigned flags);\n\n..and these should be left untouched.\n\n-Peff\n"},{"id":"534418","messageId":"aXFosXv328ZPjlcw@nand.local","threadId":"64809","inReplyTo":"20260121211128.GB723458@coredump.intra.peff.net","subject":"Re: [PATCH v3 02/14] odb: fix flags parameter to be unsigned","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-22T00:00:49Z","receivedAt":"2026-01-22T00:00:53Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 04:11:28PM -0500, Jeff King wrote:\n> On Wed, Jan 21, 2026 at 01:50:18PM +0100, Patrick Steinhardt wrote:\n>\n> > The `flags` parameter accepted by various `for_each_object()` functions\n> > is a bitfield of multiple flags. Such parameters are typically unsigned\n> > in the Git codebase, but we use `enum odb_for_each_object_flags` in\n> > some places.\n>\n> I agree that using \"unsigned\" instead of \"int\" for flags is a good\n> practice in general. But isn't using \"unsigned\" instead of an enum\n> strictly worse?\n\nI agree with you that we should be using an enum in these cases over\nunsigned for the reasons you suggest. I've stumbled over this in the\npast, so perhaps this is worth adding to the CodingGuidelines?\n\nThanks,\nTaylor\n"},{"id":"534419","messageId":"aXFpcms/adskOx3X@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-3-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 03/14] object-file: extract function to read object info from path","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-22T00:04:02Z","receivedAt":"2026-01-22T00:04:07Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:19PM +0100, Patrick Steinhardt wrote:\n> Extract a new function that allows us to read object info for a specific\n> loose object via a user-supplied path. This function will be used in a\n> subsequent commit.\n\nI think that I'm a tad unsure of this interface. I understand that for\nthe existing object storage mechanism that having a path makes sense:\nloose objects are stored in files which are referenced by their path.\n\nBut this feels like a leaky abstraction to me. If we are dealing with an\nobject store implementation that uses entries in a database, or\narbitrary blob storage, do they have an equivalent concept of \"path\"?\n\nPerhaps this is clear later on in the series, but I think at this point\nI am a little unclear of the direction.\n\nThanks,\nTaylor\n"},{"id":"534420","messageId":"aXFsFAV/1J9DLQRY@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-4-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 04/14] object-file: introduce function to iterate through objects","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-22T00:15:16Z","receivedAt":"2026-01-22T00:15:20Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:20PM +0100, Patrick Steinhardt wrote:\n> Introduce a new function `odb_source_loose_for_each_object()` to plug\n> this gap. This function doesn't take any data specific to loose objects,\n> but instead it accepts a `struct object_info` that will be populated the\n> exact same as if `odb_source_loose_read_object()` was called.\n\nThis may be a bit of a tangent, but I wonder if we are over-applying the\nfunction prefixing convention.\n\nIn general I am really happy with this convention, and it yields\norganized headers where functions are clearly grouped by what structure\nthey operate on. But I have noticed a handful of times where we replaced\na very concise function name with a longer prefixed version.\n\nI think I don't have a clear sense of what the benefit of prefixing is\nin this particular instance. Supposing for a moment that we don't have\nan existing for_each_loose_object() function (which I think is the\nend-state of this series). What does the name\n\"odb_source_loose_for_each_object()\" convey that\n\"for_each_loose_object()\" does not?\n\nI think if there were multiple ways to iterate over loose objects, it\nmakes a lot of sense to prefix them such that they are grouped to avoid\nmixing interfaces or using one API when you meant to call another. But\nmy understanding is that the intent here is to consolidate all of the\ndifferent ways to iterate over objects which live in different\nodb_source implementations opaque to the caller. As a result, what other\nway exists to iterate over loose objects?\n\nAnother aspect of this is how approachable the function is to newcomers.\nOn the one hand, I can see an argument that prefixing makes it clear\nwhich functions belong together, and so if a newcomer is familiar with\nthe concept of ODB sources, then they should reasonably expect that a\nfunction to iterate over loose objects would begin with \"odb_source_\".\n\nBut on the other hand, while a newcomer may be familiar with the basics\nof Git's object model enough to understand the distinction between loose\nand packed objects, they may not be familiar with the concept of an ODB\nsource. In that case, the prefix makes it somewhat more difficult to\nfind the right function to use.\n\nI think there is a reasonable argument towards prefixing in the case\nthat we want to link against this function from outside of Git. But\nAFAIK that is not likely to happen in the near future. So in the interim\nI think we are left with function names which are a little more verbose\nthan the ones they are replacing without a clear benefit.\n\nTo be clear, I am generally in favor of this convention and have been\napplying it myself especially when splitting out the repack builtin\nimplementation into their own compilation units. But I wonder if we\ncould relax the convention in cases like these without sacrificing\nclarity/organization.\n\nThanks,\nTaylor\n"},{"id":"534422","messageId":"aXF+fMQKry71Gh0w@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 00/14] odb: introduce `odb_for_each_object()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-22T01:33:48Z","receivedAt":"2026-01-22T01:33:51Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:16PM +0100, Patrick Steinhardt wrote:\n> The patch series is built on top of 8745eae506 (The 17th batch,\n> 2026-01-11) with the following two series merged into it:\n>\n>   - ps/read-object-info-improvements at a282a8f163 (packfile: move MIDX\n>     into packfile store, 2026-01-09).\n>\n>   - ps/packfile-store-in-odb-source at 12d3b58b55 (packfile: drop\n>     repository parameter from `packed_object_info()`, 2026-01-12) .\n\nI was having a little bit of trouble constructing a base to apply these\npatches. a282a8f163 merges cleanly into 8745eae506, but 12d3b58b55 does\nnot merge cleanly into that, nor do they apply as a single octopus\nmerge.\n\nLooking at the base-commit identified below from your fork[1], there is\nsome conflict resolution required to merge in the latter series. I'm\nincluding the --remerge-diff results below in case others are interested\nin applying this locally.\n\n--- 8< ---\ndiff --git a/packfile.c b/packfile.c\nremerge CONFLICT (content): Merge conflict in packfile.c\nindex 4cc9d8c07e6..402c3b5dc73 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2164,16 +2164,8 @@ int packfile_store_read_object_info(struct packfile_store *store,\n \tif (!oi)\n \t\treturn 0;\n\n-<<<<<<< b7f649ca936 (Merge remote-tracking branch 'junio/ps/read-object-info-improvements' into HEAD)\n \tret = packed_object_info(e.p, e.offset, oi);\n \tif (ret < 0) {\n-||||||| merged common ancestors\n-\trtype = packed_object_info(store->odb->repo, e.p, e.offset, oi);\n-\tif (rtype < 0) {\n-=======\n-\trtype = packed_object_info(store->source->odb->repo, e.p, e.offset, oi);\n-\tif (rtype < 0) {\n->>>>>>> a282a8f163f (packfile: move MIDX into packfile store)\n \t\tmark_bad_packed_object(e.p, oid);\n \t\treturn -1;\n \t}\n@@ -2574,17 +2566,9 @@ int packfile_store_read_object_stream(struct odb_read_stream **out,\n \toi.sizep = &size;\n\n \tif (packfile_store_read_object_info(store, oid, &oi, 0) ||\n-<<<<<<< b7f649ca936 (Merge remote-tracking branch 'junio/ps/read-object-info-improvements' into HEAD)\n \t    oi.u.packed.type == PACKED_OBJECT_TYPE_REF_DELTA ||\n \t    oi.u.packed.type == PACKED_OBJECT_TYPE_OFS_DELTA ||\n-\t    repo_settings_get_big_file_threshold(store->odb->repo) >= size)\n-||||||| merged common ancestors\n-\t    oi.u.packed.is_delta ||\n-\t    repo_settings_get_big_file_threshold(store->odb->repo) >= size)\n-=======\n-\t    oi.u.packed.is_delta ||\n \t    repo_settings_get_big_file_threshold(store->source->odb->repo) >= size)\n->>>>>>> a282a8f163f (packfile: move MIDX into packfile store)\n \t\treturn -1;\n\n \tin_pack_type = unpack_object_header(oi.u.packed.pack,\n--- >8 ---\n\nThanks,\nTaylor\n\n[1]: https://gitlab.com/pks-gitlab/git.git/\n"},{"id":"534423","messageId":"aXF/XfEcHA7lvyDE@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-5-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 05/14] packfile: extract function to iterate through objects of a store","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-22T01:37:33Z","receivedAt":"2026-01-22T01:37:35Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:21PM +0100, Patrick Steinhardt wrote:\n> ---\n>  packfile.c | 78 ++++++++++++++++++++++++++++++++++++--------------------------\n>  1 file changed, 45 insertions(+), 33 deletions(-)\n\nReading with --color-moved and ignoring space changes makes it clear\nthat this patch is extracting the logic to iterate through the packfile\nstore of a single source into its own function, and then calling that\nfunction from within for_each_packed_object().\n\nSeems reasonable.\n\nThanks,\nTaylor\n"},{"id":"534426","messageId":"aXHI0vNArKiDCL-I@pks.im","threadId":"64809","inReplyTo":"20260121211128.GB723458@coredump.intra.peff.net","subject":"Re: [PATCH v3 02/14] odb: fix flags parameter to be unsigned","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-22T06:50:58Z","receivedAt":"2026-01-22T06:51:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Jan 21, 2026 at 04:11:28PM -0500, Jeff King wrote:\n> On Wed, Jan 21, 2026 at 01:50:18PM +0100, Patrick Steinhardt wrote:\n> \n> > The `flags` parameter accepted by various `for_each_object()` functions\n> > is a bitfield of multiple flags. Such parameters are typically unsigned\n> > in the Git codebase, but we use `enum odb_for_each_object_flags` in\n> > some places.\n> \n> I agree that using \"unsigned\" instead of \"int\" for flags is a good\n> practice in general. But isn't using \"unsigned\" instead of an enum\n> strictly worse?\n> \n> The enum is more descriptive to human readers (since the type defines\n> which flags we expect to see). And it lets the compiler use the correct\n> type in the few cases where it might matter. E.g., if you imagine an\n> enum that defines 40 bits, then the compiler will know that it needs to\n> use a type larger than 32 bits to store it. Whereas passing a raw\n> \"unsigned\" will truncate some values.\n> \n> I don't expect this latter reason to be common, but if we are going to\n> have a general principle for how to pass flags, it feels like passing\n> the enum (assuming the flags are defined in one) is always better. And\n> IMHO just the first reason (human readers) makes it worth doing that way\n> anyway.\n\nI'd agree if we used the enum as a plain value directly. But in case\nwe're using it as a bitset I think it muddies the waters a bit, and I\nhad the understanding that we typically want to use `unsigned` for\nflag bitsets like this.\n\nI think the reason I'm a bit torn is that I'm not a huge fan of having\nenum values that don't fall into the range of valid enums. It's valid C\nof course, but it just smells weird to me.\n\n> You can find this pattern in lots of places (try grepping for \"enum\n> [a-z_]* flag\"). The ones that aren't are typically using flags that are\n> not using enums at all (just #defines).\n\nTrue, but `unsigned flags` is way more common:\n\n    $ git grep 'unsigned flags' | wc -l\n    219\n\n    $ git grep 'enum [a-z_]* flag' | wc -l\n    56\n\nIn any case, I don't feel too strongly about all of this. I'm happy to\nadapt if there is general consensus that we want to use enums instead,\nbut if so I'd like us to document this in our coding guidelines.\n\nThanks!\n\nPatrick\n"},{"id":"534427","messageId":"aXHI_Q_88q1aAXlW@pks.im","threadId":"64809","inReplyTo":"aXFpcms/adskOx3X@nand.local","subject":"Re: [PATCH v3 03/14] object-file: extract function to read object info from path","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-22T06:51:41Z","receivedAt":"2026-01-22T06:51:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Jan 21, 2026 at 07:04:02PM -0500, Taylor Blau wrote:\n> On Wed, Jan 21, 2026 at 01:50:19PM +0100, Patrick Steinhardt wrote:\n> > Extract a new function that allows us to read object info for a specific\n> > loose object via a user-supplied path. This function will be used in a\n> > subsequent commit.\n> \n> I think that I'm a tad unsure of this interface. I understand that for\n> the existing object storage mechanism that having a path makes sense:\n> loose objects are stored in files which are referenced by their path.\n> \n> But this feels like a leaky abstraction to me. If we are dealing with an\n> object store implementation that uses entries in a database, or\n> arbitrary blob storage, do they have an equivalent concept of \"path\"?\n\nIt is leaky indeed, but that should be fine given that it's local to the\nloose object backend anyway. So no other object storage format uses or\neven sees it.\n\nPatrick\n"},{"id":"534428","messageId":"aXHJEBY1FnbGRtzK@pks.im","threadId":"64809","inReplyTo":"aXFsFAV/1J9DLQRY@nand.local","subject":"Re: [PATCH v3 04/14] object-file: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-22T06:52:00Z","receivedAt":"2026-01-22T06:52:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Jan 21, 2026 at 07:15:16PM -0500, Taylor Blau wrote:\n> On Wed, Jan 21, 2026 at 01:50:20PM +0100, Patrick Steinhardt wrote:\n> > Introduce a new function `odb_source_loose_for_each_object()` to plug\n> > this gap. This function doesn't take any data specific to loose objects,\n> > but instead it accepts a `struct object_info` that will be populated the\n> > exact same as if `odb_source_loose_read_object()` was called.\n> \n> This may be a bit of a tangent, but I wonder if we are over-applying the\n> function prefixing convention.\n> \n> In general I am really happy with this convention, and it yields\n> organized headers where functions are clearly grouped by what structure\n> they operate on. But I have noticed a handful of times where we replaced\n> a very concise function name with a longer prefixed version.\n> \n> I think I don't have a clear sense of what the benefit of prefixing is\n> in this particular instance. Supposing for a moment that we don't have\n> an existing for_each_loose_object() function (which I think is the\n> end-state of this series). What does the name\n> \"odb_source_loose_for_each_object()\" convey that\n> \"for_each_loose_object()\" does not?\n\nAs you say further down, it makes it easy to see that it's a function\nthat belongs to `struct odb_source_loose`. It immediately gives the\nreader a sense what the main structure is it belongs to, thus gives\nscope and makes LSPs work better because of the common prefix.\n\n> I think if there were multiple ways to iterate over loose objects, it\n> makes a lot of sense to prefix them such that they are grouped to avoid\n> mixing interfaces or using one API when you meant to call another. But\n> my understanding is that the intent here is to consolidate all of the\n> different ways to iterate over objects which live in different\n> odb_source implementations opaque to the caller. As a result, what other\n> way exists to iterate over loose objects?\n\nThere will be more to come: iterating over objects with a prefix, for\nexample. In general, this series is taking a layered approach:\n\n  - `odb_for_each_object()` is the high-level function that users should\n    use if possible. It is part of the ODB layer and abstracts away\n    details about the ODB sources.\n\n  - `odb_source_for_each_object()` will be introduced in the next patch\n    series. It allows the user to take an ODB source and iterate over\n    its contained objects, regardless of what the backend is.\n\n  - `odb_source_loose_for_each_object()` is the low-level implementation\n    for one specific backend. We also have equivalent functions for the\n    other backends, like for example for packed objects.\n\nThe longer the function name, the more specific the logic becomes. Sure,\neventually it becomes a mouthful, but ideally users wouldn't have to\never interact with the low-level details at all.\n\n> Another aspect of this is how approachable the function is to newcomers.\n> On the one hand, I can see an argument that prefixing makes it clear\n> which functions belong together, and so if a newcomer is familiar with\n> the concept of ODB sources, then they should reasonably expect that a\n> function to iterate over loose objects would begin with \"odb_source_\".\n> \n> But on the other hand, while a newcomer may be familiar with the basics\n> of Git's object model enough to understand the distinction between loose\n> and packed objects, they may not be familiar with the concept of an ODB\n> source. In that case, the prefix makes it somewhat more difficult to\n> find the right function to use.\n\nSo I would claim that this is even intentional. If a reader is not aware\nwhat an ODB source is, then chances are high that using the function\nthat iterates through one specific source is the wrong thing to do. They\nshould rather use `odb_for_each_object()` in that case, which is the\nhigher-level interface that doesn't require the reader to know about ODB\nsources in the first place.\n\nI kind of see this as \"guiding\" the reader and giving them some hints\nwhat the preferred interface is.\n\nPatrick\n"},{"id":"534466","messageId":"xmqqcy31pscg.fsf@gitster.g","threadId":"64809","inReplyTo":"aXFosXv328ZPjlcw@nand.local","subject":"Re: [PATCH v3 02/14] odb: fix flags parameter to be unsigned","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-22T15:41:51Z","receivedAt":"2026-01-22T15:41:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n> I agree with you that we should be using an enum in these cases over\n> unsigned for the reasons you suggest. I've stumbled over this in the\n> past, so perhaps this is worth adding to the CodingGuidelines?\n\nI am OK with declaring our preference of \"enum\" over \"#define\"d\nconstants.  The only two minor hesitation I have against the use of\n\"enum\", especially for bitset but not for enumeration, are that\n\n (1) enum gives a false sense of type safety to casual coders. If I\n     have two enum types and pass one to as a parameter to a\n     function that expects the other one, would the compiler help me\n     catch that as a potential mistake?  -Wenum-conversion is not\n     enabled even with -Wall so I am assuming that the compiler\n     folks fells that it is not reliable enough.\n\n (2) it is not easy to force an enum type to be unsigned, unless you\n     are at C23 or above.  If shifting enums are warned by the\n     compilers by default, I wouldn't worry about it, but use of\n     unsigned is more explicit in this regard.\n\n\n\nUse of enum does help debuggers, as gcc figures out that three and\ntres are both (ONEBIT | TWOBIT) when asked to print it in the\nfollowing snippet.\n\n    enum bits {\n            ONEBIT = (1 << 0),\n            TWOBIT = (1 << 1),\n    };\n\n    int main(int ac, char **av)\n    {\n            enum bits one = ONEBIT;\n            enum bits two = TWOBIT;\n            enum bits three = one | two;\n            enum bits tres = 3;\n            ...\n\n"},{"id":"534470","messageId":"xmqq8qdppolg.fsf@gitster.g","threadId":"64809","inReplyTo":"aXF+fMQKry71Gh0w@nand.local","subject":"Re: [PATCH v3 00/14] odb: introduce `odb_for_each_object()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-22T17:02:51Z","receivedAt":"2026-01-22T17:02:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n> On Wed, Jan 21, 2026 at 01:50:16PM +0100, Patrick Steinhardt wrote:\n>> The patch series is built on top of 8745eae506 (The 17th batch,\n>> 2026-01-11) with the following two series merged into it:\n>>\n>>   - ps/read-object-info-improvements at a282a8f163 (packfile: move MIDX\n>>     into packfile store, 2026-01-09).\n>>\n>>   - ps/packfile-store-in-odb-source at 12d3b58b55 (packfile: drop\n>>     repository parameter from `packed_object_info()`, 2026-01-12) .\n>\n> I was having a little bit of trouble constructing a base to apply these\n> patches. a282a8f163 merges cleanly into 8745eae506, but 12d3b58b55 does\n> not merge cleanly into that, nor do they apply as a single octopus\n> merge.\n>\n> Looking at the base-commit identified below from your fork[1], there is\n> some conflict resolution required to merge in the latter series. I'm\n> including the --remerge-diff results below in case others are interested\n> in applying this locally.\n\nThanks for independently validating the conflict resolution I did.\nA quick glance of your remerge-diff matches what I had been using\nfor the past week:\n\n$ git log --oneline --first-parent master..ps/odb-for-each-object\n...\nec16dde5c8 Merge branch 'ps/packfile-store-in-odb-source' into ps/odb-for-each-object\nc8e1706e8d Merge branch 'ps/read-object-info-improvements' into ps/odb-for-each-object\n\n$ git log -2 --oneline --remerge-diff -p ec16dde5c8\nec16dde5c8 Merge branch 'ps/packfile-store-in-odb-source' into ps/odb-for-each-object\ndiff --git a/packfile.c b/packfile.c\nremerge CONFLICT (content): Merge conflict in packfile.c\nindex d951de73d1..402c3b5dc7 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2164,16 +2164,8 @@ int packfile_store_read_object_info(struct packfile_store *store,\n \tif (!oi)\n \t\treturn 0;\n \n-<<<<<<< c8e1706e8d (Merge branch 'ps/read-object-info-improvements' into ps/odb-for-each-object)\n \tret = packed_object_info(e.p, e.offset, oi);\n \tif (ret < 0) {\n-||||||| merged common ancestors\n-\trtype = packed_object_info(store->odb->repo, e.p, e.offset, oi);\n-\tif (rtype < 0) {\n-=======\n-\trtype = packed_object_info(store->source->odb->repo, e.p, e.offset, oi);\n-\tif (rtype < 0) {\n->>>>>>> a282a8f163 (packfile: move MIDX into packfile store)\n \t\tmark_bad_packed_object(e.p, oid);\n \t\treturn -1;\n \t}\n@@ -2574,17 +2566,9 @@ int packfile_store_read_object_stream(struct odb_read_stream **out,\n \toi.sizep = &size;\n \n \tif (packfile_store_read_object_info(store, oid, &oi, 0) ||\n-<<<<<<< c8e1706e8d (Merge branch 'ps/read-object-info-improvements' into ps/odb-for-each-object)\n \t    oi.u.packed.type == PACKED_OBJECT_TYPE_REF_DELTA ||\n \t    oi.u.packed.type == PACKED_OBJECT_TYPE_OFS_DELTA ||\n-\t    repo_settings_get_big_file_threshold(store->odb->repo) >= size)\n-||||||| merged common ancestors\n-\t    oi.u.packed.is_delta ||\n-\t    repo_settings_get_big_file_threshold(store->odb->repo) >= size)\n-=======\n-\t    oi.u.packed.is_delta ||\n \t    repo_settings_get_big_file_threshold(store->source->odb->repo) >= size)\n->>>>>>> a282a8f163 (packfile: move MIDX into packfile store)\n \t\treturn -1;\n \n \tin_pack_type = unpack_object_header(oi.u.packed.pack,\nc8e1706e8d Merge branch 'ps/read-object-info-improvements' into ps/odb-for-each-object\n\n\n\n\n\n>\n> --- 8< ---\n> diff --git a/packfile.c b/packfile.c\n> remerge CONFLICT (content): Merge conflict in packfile.c\n> index 4cc9d8c07e6..402c3b5dc73 100644\n> --- a/packfile.c\n> +++ b/packfile.c\n> @@ -2164,16 +2164,8 @@ int packfile_store_read_object_info(struct packfile_store *store,\n>  \tif (!oi)\n>  \t\treturn 0;\n>\n> -<<<<<<< b7f649ca936 (Merge remote-tracking branch 'junio/ps/read-object-info-improvements' into HEAD)\n>  \tret = packed_object_info(e.p, e.offset, oi);\n>  \tif (ret < 0) {\n> -||||||| merged common ancestors\n> -\trtype = packed_object_info(store->odb->repo, e.p, e.offset, oi);\n> -\tif (rtype < 0) {\n> -=======\n> -\trtype = packed_object_info(store->source->odb->repo, e.p, e.offset, oi);\n> -\tif (rtype < 0) {\n> ->>>>>>> a282a8f163f (packfile: move MIDX into packfile store)\n>  \t\tmark_bad_packed_object(e.p, oid);\n>  \t\treturn -1;\n>  \t}\n> @@ -2574,17 +2566,9 @@ int packfile_store_read_object_stream(struct odb_read_stream **out,\n>  \toi.sizep = &size;\n>\n>  \tif (packfile_store_read_object_info(store, oid, &oi, 0) ||\n> -<<<<<<< b7f649ca936 (Merge remote-tracking branch 'junio/ps/read-object-info-improvements' into HEAD)\n>  \t    oi.u.packed.type == PACKED_OBJECT_TYPE_REF_DELTA ||\n>  \t    oi.u.packed.type == PACKED_OBJECT_TYPE_OFS_DELTA ||\n> -\t    repo_settings_get_big_file_threshold(store->odb->repo) >= size)\n> -||||||| merged common ancestors\n> -\t    oi.u.packed.is_delta ||\n> -\t    repo_settings_get_big_file_threshold(store->odb->repo) >= size)\n> -=======\n> -\t    oi.u.packed.is_delta ||\n>  \t    repo_settings_get_big_file_threshold(store->source->odb->repo) >= size)\n> ->>>>>>> a282a8f163f (packfile: move MIDX into packfile store)\n>  \t\treturn -1;\n>\n>  \tin_pack_type = unpack_object_header(oi.u.packed.pack,\n> --- >8 ---\n>\n> Thanks,\n> Taylor\n>\n> [1]: https://gitlab.com/pks-gitlab/git.git/\n"},{"id":"534488","messageId":"20260122192337.GC2098026@coredump.intra.peff.net","threadId":"64809","inReplyTo":"xmqqcy31pscg.fsf@gitster.g","subject":"Re: [PATCH v3 02/14] odb: fix flags parameter to be unsigned","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-01-22T19:23:37Z","receivedAt":"2026-01-22T19:23:39Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Jan 22, 2026 at 07:41:51AM -0800, Junio C Hamano wrote:\n\n> Taylor Blau <me@ttaylorr.com> writes:\n> \n> > I agree with you that we should be using an enum in these cases over\n> > unsigned for the reasons you suggest. I've stumbled over this in the\n> > past, so perhaps this is worth adding to the CodingGuidelines?\n> \n> I am OK with declaring our preference of \"enum\" over \"#define\"d\n> constants.  The only two minor hesitation I have against the use of\n> \"enum\", especially for bitset but not for enumeration, are that\n\nI don't think there's any disagreement over using enums in general. It's\njust a question of what type to declare in function interfaces.\n\n>  (1) enum gives a false sense of type safety to casual coders. If I\n>      have two enum types and pass one to as a parameter to a\n>      function that expects the other one, would the compiler help me\n>      catch that as a potential mistake?  -Wenum-conversion is not\n>      enabled even with -Wall so I am assuming that the compiler\n>      folks fells that it is not reliable enough.\n\nIt is enabled with -Wextra, which we turn on with DEVELOPER=1. I think\ngcc will catch the most obvious mismatches like:\n\n  enum one { FOO };\n  enum two { BAR };\n  void func(enum one value);\n  void doit(void) { func(BAR); }\n\nwhich yields:\n\n  $ gcc -c -Wall -Wextra foo.c\n  foo.c: In function ‘doit’:\n  foo.c:4:24: warning: implicit conversion from ‘enum two’ to ‘enum one’ [-Wenum-conversion]\n      4 | void doit(void) { func(BAR); }\n        |                        ^~~\n\nWhat it doesn't help with is passing arbitrary integers, which includes\n#define'd constants. Swapping out \"enum two\" for:\n\n  #define BAR 1\n\nwill not produce a warning. That's the issue that I ran into with the\ncolor code in:\n\n  https://lore.kernel.org/git/20250916202748.GM612873@coredump.intra.peff.net/\n\nUnfortunately bit operations on enum values seem to lose the \"type\" for\nthe purposes of this warning, and just become regular integers. So if we\nmodify our example to:\n\n  num one { FOO_A = 1 << 0, FOO_B = 1 << 1 };\n  enum two { BAR_A = 1 << 0, BAR_B = 1 << 1 };\n  void func(enum one value);\n  void doit(void) { func(BAR_A | BAR_B); }\n\nit no longer complains.\n\nI still think we are better off declaring the flag parameters with the\nenum type, though. It will catch some problematic cases. And even if\nthere were no compiler support at all, I think the hint to humans about\nthe expected type is worth it.\n\n>  (2) it is not easy to force an enum type to be unsigned, unless you\n>      are at C23 or above.  If shifting enums are warned by the\n>      compilers by default, I wouldn't worry about it, but use of\n>      unsigned is more explicit in this regard.\n\nDo we need to force unsignedness for bit-flags? The compiler will use a\ntype that is sufficiently large for the enum values defined, and I would\nnot expect anybody to shift them. Only to construct them with bitwise-OR\nand check them with bitwise-AND.\n\n-Peff\n"},{"id":"534514","messageId":"aXK2awZo/d9bUjPY@nand.local","threadId":"64809","inReplyTo":"aXHI0vNArKiDCL-I@pks.im","subject":"Re: [PATCH v3 02/14] odb: fix flags parameter to be unsigned","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-22T23:44:43Z","receivedAt":"2026-01-22T23:44:49Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Jan 22, 2026 at 07:50:58AM +0100, Patrick Steinhardt wrote:\n> > You can find this pattern in lots of places (try grepping for \"enum\n> > [a-z_]* flag\"). The ones that aren't are typically using flags that are\n> > not using enums at all (just #defines).\n>\n> True, but `unsigned flags` is way more common:\n>\n>     $ git grep 'unsigned flags' | wc -l\n>     219\n>\n>     $ git grep 'enum [a-z_]* flag' | wc -l\n>     56\n\nSure, though I think the convention can/should evolve where it makes\nsense. I tend to agree with Peff earlier in this thread that enum flags\nare preferable to unsigned ones for the reasons he laid out. I don't\nthink we should go and proactively convert the 219 instances of\n\"unsigned flags\", but for new code I think we should prefer enum flags.\n\nThanks,\nTaylor\n"},{"id":"534515","messageId":"aXK3FV8MoEBeAcu9@nand.local","threadId":"64809","inReplyTo":"aXHI_Q_88q1aAXlW@pks.im","subject":"Re: [PATCH v3 03/14] object-file: extract function to read object info from path","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-22T23:47:33Z","receivedAt":"2026-01-22T23:47:38Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Jan 22, 2026 at 07:51:41AM +0100, Patrick Steinhardt wrote:\n> On Wed, Jan 21, 2026 at 07:04:02PM -0500, Taylor Blau wrote:\n> > On Wed, Jan 21, 2026 at 01:50:19PM +0100, Patrick Steinhardt wrote:\n> > > Extract a new function that allows us to read object info for a specific\n> > > loose object via a user-supplied path. This function will be used in a\n> > > subsequent commit.\n> >\n> > I think that I'm a tad unsure of this interface. I understand that for\n> > the existing object storage mechanism that having a path makes sense:\n> > loose objects are stored in files which are referenced by their path.\n> >\n> > But this feels like a leaky abstraction to me. If we are dealing with an\n> > object store implementation that uses entries in a database, or\n> > arbitrary blob storage, do they have an equivalent concept of \"path\"?\n>\n> It is leaky indeed, but that should be fine given that it's local to the\n> loose object backend anyway. So no other object storage format uses or\n> even sees it.\n\nIf it's local to the loose object backend then I agree it's OK here.\nI think I was unclear that was the case since I saw \"path\" being used in\nconjunction with the generic \"odb_source\" type.\n\nThanks,\nTaylor\n"},{"id":"534516","messageId":"aXK6S82kY8gwoEfQ@nand.local","threadId":"64809","inReplyTo":"aXHJEBY1FnbGRtzK@pks.im","subject":"Re: [PATCH v3 04/14] object-file: introduce function to iterate through objects","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T00:01:15Z","receivedAt":"2026-01-23T00:01:20Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Jan 22, 2026 at 07:52:00AM +0100, Patrick Steinhardt wrote:\n> > I think if there were multiple ways to iterate over loose objects, it\n> > makes a lot of sense to prefix them such that they are grouped to avoid\n> > mixing interfaces or using one API when you meant to call another. But\n> > my understanding is that the intent here is to consolidate all of the\n> > different ways to iterate over objects which live in different\n> > odb_source implementations opaque to the caller. As a result, what other\n> > way exists to iterate over loose objects?\n>\n> There will be more to come: iterating over objects with a prefix, for\n> example. In general, this series is taking a layered approach:\n>\n>   - `odb_for_each_object()` is the high-level function that users should\n>     use if possible. It is part of the ODB layer and abstracts away\n>     details about the ODB sources.\n>\n>   - `odb_source_for_each_object()` will be introduced in the next patch\n>     series. It allows the user to take an ODB source and iterate over\n>     its contained objects, regardless of what the backend is.\n>\n>   - `odb_source_loose_for_each_object()` is the low-level implementation\n>     for one specific backend. We also have equivalent functions for the\n>     other backends, like for example for packed objects.\n>\n> The longer the function name, the more specific the logic becomes. Sure,\n> eventually it becomes a mouthful, but ideally users wouldn't have to\n> ever interact with the low-level details at all.\n\nThanks for the extra information, this is definitely what I was missing.\nIf there are many ways to iterate over objects, then the naming scheme\nabove makes sense.\n\nThe point that I was trying to get across was that I think that the\nconvention of naming a function that does \"foo\" to a struct \"S\" as\n\"S_foo()\" is great, but that we shouldn't apply that convention when\nthere is only one way to do \"foo\" in general.\n\nFor this particular case, I think I would have pushed back if you said\nthat `odb_for_each_object()` was the only function that we'd end up with\n(i.e., there is no non-ODB way to do this, so for_each_object() is just\nas descriptive IMO). But that's not the case, so I think the naming\nscheme you have here makes sense.\n\nThanks,\nTaylor\n"},{"id":"534517","messageId":"aXK7cSJW2syew89a@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-6-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 06/14] packfile: introduce function to iterate through objects","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T00:06:09Z","receivedAt":"2026-01-23T00:06:15Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:22PM +0100, Patrick Steinhardt wrote:\n> Introduce a new function `packfile_store_for_each_object()`. This\n> function is the equivalent to `odb_source_loose_for_each_object()` in\n\ns/to/of/ ?\n\n> that it:\n>\n>   - Works on a single packfile store and thus per object source.\n\ns/thus per/thus/ ?\n\n> diff --git a/packfile.c b/packfile.c\n> index d15a2ce12b..cd45c6f21c 100644\n> --- a/packfile.c\n> +++ b/packfile.c\n> @@ -2360,6 +2360,54 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n>  \treturn ret ? ret : pack_errors;\n>  }\n>\n> +struct packfile_store_for_each_object_wrapper_data {\n> +\tstruct packfile_store *store;\n> +\tstruct object_info *oi;\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 packfile_store_for_each_object_wrapper_data *data = cb_data;\n> +\n> +\tif (data->oi) {\n\nInteresting. Is it the case that if the caller provides a non-NULL\npointer to an object_info struct, that we will reuse the request portion\nfor all iterated objects, updating the response portion as we go along?\n\nIf so, I am a little uneasy about the potential for us to mix portions\nof the response from an earlier object with a later one. Skimming\npacked_object_info(), I don't think that we are in any immediate danger\nsince it overwrites all fields in the response section. But that feels\nsomewhat fragile to me, say, if packed_object_info() were to at some\npoint conditionally assign a field.\n\nI wonder if we should split the request/response sections of object_info\ninto their own object_info_req and object_info_resp structs. If we did\nthat, then we could invert the pattern for providing the response,\nfilling it out ourselves and then passing a pointer to it back to the\ncaller via the callback function.\n\nTBH, I wonder whether we should push this onto the caller entirely. If\nthey need to make an object_info request for each object, is there any\ncost to having them do that explicitly themselves?\n\nThanks,\nTaylor\n"},{"id":"534518","messageId":"aXK9MkCTt6LrSi+E@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-7-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 07/14] odb: introduce `odb_for_each_object()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T00:13:38Z","receivedAt":"2026-01-23T00:13:42Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:23PM +0100, Patrick Steinhardt wrote:\n> Introduce a new function `odb_for_each_object()` that knows to iterate\n> through all objects part of a given object database. This function is\n> essentially a simple wrapper around the object database sources.\n>\n> Subsequent commits will adapt callers to use this new function.\n\nMakes sense.\n\n> +int odb_for_each_object(struct object_database *odb,\n> +\t\t\tstruct object_info *oi,\n> +\t\t\todb_for_each_object_cb cb,\n> +\t\t\tvoid *cb_data,\n> +\t\t\tunsigned flags)\n> +{\n> +\tint ret;\n> +\n> +\todb_prepare_alternates(odb);\n> +\tfor (struct odb_source *source = odb->sources; source; source = source->next) {\n> +\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local)\n> +\t\t\tcontinue;\n> +\n> +\t\tif (!(flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {\n> +\t\t\tret = odb_source_loose_for_each_object(source, oi, cb, cb_data, flags);\n> +\t\t\tif (ret)\n> +\t\t\t\treturn ret;\n> +\t\t}\n\nHaving the bits corresponding to these two flags be set means that we\ncan avoid looking into the source entirely, as we know ahead of time\nthat none of its objects would match the caller's criteria.\n\n> +\t\tret = packfile_store_for_each_object(source->packfiles, oi, cb, cb_data, flags);\n\n...but when we *do* need to iterate through an individual source, we\npass the flags down to that source which handles the rest of them. Good.\n\nThanks,\nTaylor\n"},{"id":"534519","messageId":"aXLBoFnqbnCcylCD@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-8-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 08/14] builtin/fsck: refactor to use `odb_for_each_object()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T00:32:32Z","receivedAt":"2026-01-23T00:32:37Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:24PM +0100, Patrick Steinhardt wrote:\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/fsck.c | 57 ++++++++++++---------------------------------------------\n>  1 file changed, 12 insertions(+), 45 deletions(-)\n\nThis patch was a really pleasant read. It's really great to see both\npairs of functions collapse into a single one that acts the same over\nloose/packed objects as opposed to two functions which do the same thing\nbut need to have different signatures to be able to plug into the\niterators for loose vs. packed objects.\n\n> diff --git a/builtin/fsck.c b/builtin/fsck.c\n> index 4979bc795e..96107695ae 100644\n> --- a/builtin/fsck.c\n> +++ b/builtin/fsck.c\n> @@ -218,15 +218,17 @@ static int mark_used(struct object *obj, enum object_type type UNUSED,\n>  \treturn 0;\n>  }\n>\n> -static void mark_unreachable_referents(const struct object_id *oid)\n> +static int mark_unreachable_referents(const struct object_id *oid,\n> +\t\t\t\t      struct object_info *io UNUSED,\n\ns/io/oi/ ?\n\n> +\t\t\t\t      void *data UNUSED)\n>  {\n\nThe transformation here makes sense (and I think aloud through the\nsimilar mark_object_for_connectivity() transformation below). One\nthought that I had while reading, though, was how this function behaves\nwhen it is passed the same object more than once, since you mention that\nas a possibility in the commit which introduces odb_for_each_object().\n\nI think that this is OK, since we already likely send the same object to\nthis function multiple times if, e.g., we freshened an object from the\ncruft pack, in which case we'd see it both when iterating packed\nobjects as well as when iterating loose ones.\n\nAs far as I can tell, that's OK, but it might be nice to provide a brief\nanalysis of that in the commit message, just to be sure and to help\nfuture readers.\n\n> @@ -848,26 +831,12 @@ static void fsck_index(struct index_state *istate, const char *index_path,\n>  \tfsck_resolve_undo(istate, index_path);\n>  }\n>\n> -static void mark_object_for_connectivity(const struct object_id *oid)\n> +static int mark_object_for_connectivity(const struct object_id *oid,\n> +\t\t\t\t\tstruct object_info *oi UNUSED,\n> +\t\t\t\t\tvoid *cb_data UNUSED)\n>  {\n>  \tstruct object *obj = lookup_unknown_object(the_repository, oid);\n>  \tobj->flags |= HAS_OBJ;\n> -}\n> -\n> -static int mark_loose_for_connectivity(const struct object_id *oid,\n> -\t\t\t\t       const char *path UNUSED,\n> -\t\t\t\t       void *data UNUSED)\n> -{\n> -\tmark_object_for_connectivity(oid);\n> -\treturn 0;\n> -}\n> -\n> -static int mark_packed_for_connectivity(const struct object_id *oid,\n> -\t\t\t\t\tstruct packed_git *pack UNUSED,\n> -\t\t\t\t\tuint32_t pos UNUSED,\n> -\t\t\t\t\tvoid *data UNUSED)\n> -{\n> -\tmark_object_for_connectivity(oid);\n>  \treturn 0;\n>  }\n\nThis is really nice, and everything here makes sense. Both of the old\ncallback functions merely call mark_object_for_connectivity() but are\ndifferent in order to accommodate the different function signatures\nrequired. The new function uses the common interface and does the exact\nsame thing. Looking great.\n\n> @@ -1001,10 +970,8 @@ int cmd_fsck(int argc,\n>  \t\tfsck_refs(the_repository);\n>\n>  \tif (connectivity_only) {\n> -\t\tfor_each_loose_object(the_repository->objects,\n> -\t\t\t\t      mark_loose_for_connectivity, NULL, 0);\n> -\t\tfor_each_packed_object(the_repository,\n> -\t\t\t\t       mark_packed_for_connectivity, NULL, 0);\n> +\t\todb_for_each_object(the_repository->objects, NULL,\n> +\t\t\t\t    mark_object_for_connectivity, NULL, 0);\n\nMakes sense.\n\nThanks,\nTaylor\n"},{"id":"534520","messageId":"aXLB5JxuCeQchOzl@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-9-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 09/14] treewide: enumerate promisor objects via `odb_for_each_object()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T00:33:40Z","receivedAt":"2026-01-23T00:33:44Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:25PM +0100, Patrick Steinhardt wrote:\n> ---\n>  packfile.c        | 37 ++++++++++++++++++++++---------------\n>  repack-promisor.c |  8 ++++----\n>  revision.c        | 10 ++++------\n>  3 files changed, 30 insertions(+), 25 deletions(-)\n\nAll looks very sensible. Thanks for structuring the series in the way\nthat you did, it's very easy to follow these conversions and see that\nthey were done correctly.\n\nThanks,\nTaylor\n"},{"id":"534521","messageId":"aXLEzAnNRTf5A6bt@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-10-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 10/14] treewide: drop uses of `for_each_{loose,packed}_object()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T00:46:04Z","receivedAt":"2026-01-23T00:46:09Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:26PM +0100, Patrick Steinhardt wrote:\n> We're using `for_each_loose_object()` and `for_each_packed_object()` at\n> a couple of callsites to enumerate all loose and packed objects,\n> respectively. These functions will be removed in a subsequent commit in\n> favor of the newly introduced `odb_source_loose_for_each_object()` and\n> `packfile_store_for_each_object()` replacements.\n>\n> Prepare for this by refactoring the sites accordingly.\n>\n> Note that ideally, we'd convert all callsites to use the generic\n> `odb_for_each_object()` function already. But for some callers this is\n> not possible (yet), and it would require some significant refactorings\n> to make this work. Converting these site will thus be deferred to a\n> later patch series.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/cat-file.c | 28 ++++++++++++++++++++++------\n>  commit-graph.c     | 44 +++++++++++++++++++++++++++++++-------------\n>  2 files changed, 53 insertions(+), 19 deletions(-)\n>\n> diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> index 6964a5a52c..7d16fbc1b8 100644\n> --- a/builtin/cat-file.c\n> +++ b/builtin/cat-file.c\n> @@ -806,11 +806,14 @@ struct for_each_object_payload {\n>  \tvoid *payload;\n>  };\n>\n> -static int batch_one_object_loose(const struct object_id *oid,\n> -\t\t\t\t  const char *path UNUSED,\n> -\t\t\t\t  void *_payload)\n> +static int batch_one_object_oi(const struct object_id *oid,\n> +\t\t\t       struct object_info *oi,\n> +\t\t\t       void *_payload)\n>  {\n>  \tstruct for_each_object_payload *payload = _payload;\n> +\tif (oi && oi->whence == OI_PACKED)\n> +\t\treturn payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,\n\nAh, here's a good argument for having the API provide the caller with\nthe object_info response it requested. Obviously the packfile_store\nknows which packfile it's looking at, so asking the caller to\nre-discover the same information is wasteful.\n\nThat said, I'm still a little leery of the way we're passing that\ninformation around for the same reasons as I shared earlier in the\nthread, but I definitely can see the motivation.\n\n> @@ -846,8 +849,15 @@ static void batch_each_object(struct batch_options *opt,\n>  \t\t.payload = _payload,\n>  \t};\n>  \tstruct bitmap_index *bitmap = prepare_bitmap_git(the_repository);\n> +\tstruct odb_source *source;\n>\n> -\tfor_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);\n> +\todb_prepare_alternates(the_repository->objects);\n> +\tfor (source = the_repository->objects->sources; source; source = source->next) {\n> +\t\tint ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi,\n> +\t\t\t\t\t\t\t   &payload, flags);\n> +\t\tif (ret)\n> +\t\t\tbreak;\n> +\t}\n\nOK, I'm guessing that this is one such case where we can't yet use\nodb_for_each_object() function directly because of the refactoring which\nyou alluded to in the commit message. That seems reasonable, though I\nwonder if it's worth adding a /* TODO */ comment here to that effect.\n\nJust out of curiosity, what does that refactoring entail? I'm curious\nbecause I wonder whether the caller is just written in such a way that\nit makes it hard to immediately plug into the new API, or whether there\nare more fundamental issues at play that make the refactoring less than\nstraightforward. If the latter, those could potentially help inform the\ndirection here.\n\n(To be clear, I figure that this is likely work that you have already\ndone, I'm just curious to see if the details would yield any benefit to\nthe immediate patch series under discussion.)\n\n> diff --git a/commit-graph.c b/commit-graph.c\n> index 7f1145a082..a3087d7883 100644\n> --- a/commit-graph.c\n> +++ b/commit-graph.c\n\nThe conversion here all looks great to me.\n\nThanks,\nTaylor\n"},{"id":"534522","messageId":"aXLJoDdoEyKXKtBf@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-11-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 11/14] odb: introduce mtime fields for object info requests","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T01:06:40Z","receivedAt":"2026-01-23T01:06:43Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:27PM +0100, Patrick Steinhardt wrote:\n> There are some use cases where we need to figure out the mtime for\n> objects. Most importantly, this is the case when we want to prune\n> unreachable objects. But getting at that data requires users to manually\n> derive the info either via the loose object's mtime, the packfiles'\n> mtime or via the \".mtimes\" file.\n>\n> Introduce a new `struct object_info::mtimep` pointer that allows callers\n> to request an object's mtime. This new field will be used in a\n> subsequent commit.\n\nThe goal seems reasonable to me, but I am a little unsure about whether\nor not this is the right place to expose this information. I have some\nmore thoughts below...\n\n> diff --git a/object-file.c b/object-file.c\n> index 65e730684b..c0f896673b 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -409,6 +409,7 @@ static int read_object_info_from_path(struct odb_source *source,\n>  \tchar hdr[MAX_HEADER_LEN];\n>  \tunsigned long size_scratch;\n>  \tenum object_type type_scratch;\n> +\tstruct stat st;\n\nI was a little confused why we were declaring a stat struct here...\n\n>  \t/*\n>  \t * If we don't care about type or size, then we don't\n> @@ -421,7 +422,7 @@ static int read_object_info_from_path(struct odb_source *source,\n>  \tif (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) {\n>  \t\tstruct stat st;\n>\n> -\t\tif ((!oi || !oi->disk_sizep) && (flags & OBJECT_INFO_QUICK)) {\n> +\t\tif ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) {\n>  \t\t\tret = quick_has_loose(source->loose, oid) ? 0 : -1;\n>  \t\t\tgoto out;\n>  \t\t}\n> @@ -431,8 +432,12 @@ static int read_object_info_from_path(struct odb_source *source,\n>  \t\t\tgoto out;\n>  \t\t}\n>\n> -\t\tif (oi && oi->disk_sizep)\n> -\t\t\t*oi->disk_sizep = st.st_size;\n> +\t\tif (oi) {\n> +\t\t\tif (oi->disk_sizep)\n> +\t\t\t\t*oi->disk_sizep = st.st_size;\n\n...and then assigning it here without actually calling lstat() between\nthe two. But the diff context elides the fact that there is another stat\ndeclaration within this block that we *do* lstat() into before reading\nit.\n\nThat tripped me up a little while reviewing, but not a huge deal. I do\nwonder whether or not there is a clearer way to structure all of these\nconditionals. I *think* that what you wrote here is right, but the way\nthat it has grown organically over time (to be clear, not the fault of\nyour series) makes it a little difficult to follow.\n\n> +\t\t\tif (oi->mtimep)\n> +\t\t\t\t*oi->mtimep = st.st_mtime;\n> +\t\t}\n>\n>  \t\tret = 0;\n>  \t\tgoto out;\n> @@ -446,7 +451,21 @@ static int read_object_info_from_path(struct odb_source *source,\n>  \t\tgoto out;\n>  \t}\n>\n> -\tmap = map_fd(fd, path, &mapsize);\n> +\tif (fstat(fd, &st)) {\n> +\t\tclose(fd);\n> +\t\tret = -1;\n> +\t\tgoto out;\n> +\t}\n\nMakes sense. We were previously letting map_fd() take care of stat()-ing\nthe file to know how large the mmap should be, but now we might need\nthat information for the mtime as well. So doing what map_fd() is doing\nunderneath here directly makes sense.\n\n> diff --git a/odb.c b/odb.c\n> index 65f0447aa5..67decd3908 100644\n> --- a/odb.c\n> +++ b/odb.c\n> @@ -702,6 +702,8 @@ static int do_oid_object_info_extended(struct object_database *odb,\n>  \t\t\t\toidclr(oi->delta_base_oid, odb->repo->hash_algo);\n>  \t\t\tif (oi->contentp)\n>  \t\t\t\t*oi->contentp = xmemdupz(co->buf, co->size);\n> +\t\t\tif (oi->mtimep)\n> +\t\t\t\t*oi->mtimep = 0;\n\nAssuming that you do not change the object_info request/response\nsemantics, I wonder if it might make sense to zero out the entirety of\nthe response section as a belt-and-suspenders mechanism in case future\ncontributors forget to assign zero to the new fields themselves.\n\n> @@ -1619,16 +1620,34 @@ int packed_object_info(struct packed_git *p,\n>  \t\t}\n>  \t}\n>\n> -\tif (oi->disk_sizep) {\n> -\t\tuint32_t pos;\n> -\t\tif (offset_to_pack_pos(p, obj_offset, &pos) < 0) {\n> +\tif (oi->disk_sizep || (oi->mtimep && p->is_cruft)) {\n> +\t\tif (offset_to_pack_pos(p, obj_offset, &pack_pos) < 0) {\n>  \t\t\terror(\"could not find object at offset %\"PRIuMAX\" \"\n>  \t\t\t      \"in pack %s\", (uintmax_t)obj_offset, p->pack_name);\n>  \t\t\tret = -1;\n>  \t\t\tgoto out;\n>  \t\t}\n> +\t}\n> +\n> +\tif (oi->disk_sizep)\n> +\t\t*oi->disk_sizep = pack_pos_to_offset(p, pack_pos + 1) - obj_offset;\n> +\n> +\tif (oi->mtimep) {\n> +\t\tif (p->is_cruft) {\n> +\t\t\tuint32_t index_pos;\n> +\n> +\t\t\tif (load_pack_mtimes(p) < 0)\n> +\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n\nDo you think it would be worth doing instead:\n\n    die(_(\"could not load .mtimes for cruft pack '%s'\"), pack_basename(p));\n\n? Most repositories should only ever have one cruft pack in practice\n(even so, there should still be some value in identifying it by its\nchecksum in case someone is repacking underneath us). But some\nrepositories will have >1 cruft pack, so knowing which one is busted may\nbe useful in that case.\n\n> +\n> +\t\t\tif (maybe_index_pos)\n> +\t\t\t\tindex_pos = *maybe_index_pos;\n> +\t\t\telse\n> +\t\t\t\tindex_pos = pack_pos_to_index(p, pack_pos);\n>\n> -\t\t*oi->disk_sizep = pack_pos_to_offset(p, pos + 1) - obj_offset;\n> +\t\t\t*oi->mtimep = nth_packed_mtime(p, index_pos);\n> +\t\t} else {\n> +\t\t\t*oi->mtimep = p->mtime;\n> +\t\t}\n\nI am a little stuck here on whether or not this is the right layer to\ndetermine an object's mtime. On the one hand, it makes sense to me that\ncallers would want to know the mtime of an object, either by the mtime\nof the loose object on disk, or the mtime of the contain pack otherwise.\n\nBut I'm not sure whether the GC-specific definition of \"mtime\" is what\nthe caller would always want. For GC uses, yes, having mtime be aware of\ncruft packs makes total sense to me. But for non-GC uses, would there\never be a scenario where the caller would want to know the mtime of an\nobject's containing pack, regardless of whether or not that pack is\ncruft?\n\nI suppose they could get around that today by doing something like:\n\n    if (oi->whence == OI_PACKED) {\n        struct packed_git *p = oi->u.packed.p;\n        if (p->is_cruft) {\n            /* reinterpret the meaning of mtime... */\n            *oi->mtimep = p->mtime;\n        }\n    }\n\n, but that feels a little clunky. I dunno, maybe this hypothetical\ndoesn't really exist and I'm overthinking this. But I have this nagging\nfeeling that we are exposing this information at too low of a level as\nto make the object store aware of cruft pack/GC-specific mechanics.\n\nThanks,\nTaylor\n"},{"id":"534523","messageId":"aXLNM+AOpdQtmisC@nand.local","threadId":"64809","inReplyTo":"20260121-pks-odb-for-each-object-v3-12-12c4dfd24227@pks.im","subject":"Re: [PATCH v3 12/14] builtin/pack-objects: use `packfile_store_for_each_object()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T01:21:55Z","receivedAt":"2026-01-23T01:22:00Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jan 21, 2026 at 01:50:28PM +0100, Patrick Steinhardt wrote:\n>  static int add_object_in_unpacked_pack(const struct object_id *oid,\n> -\t\t\t\t       struct packed_git *pack,\n> -\t\t\t\t       uint32_t pos,\n> +\t\t\t\t       struct object_info *oi,\n>  \t\t\t\t       void *data UNUSED)\n>  {\n>  \tif (cruft) {\n> -\t\toff_t offset;\n> -\t\ttime_t mtime;\n> -\n> -\t\tif (pack->is_cruft) {\n> -\t\t\tif (load_pack_mtimes(pack) < 0)\n> -\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n> -\t\t\tmtime = nth_packed_mtime(pack, pos);\n> -\t\t} else {\n> -\t\t\tmtime = pack->mtime;\n> -\t\t}\n> -\t\toffset = nth_packed_object_offset(pack, pos);\n> -\n> -\t\tadd_cruft_object_entry(oid, OBJ_NONE, pack, offset,\n> -\t\t\t\t       NULL, mtime);\n\nOK, here's where we see the existing logic for determining the mtime of\nan object in the GC sense. I see there's a subsequent patch that also\nmakes use of the object_info->mtimep field, and my guess is (not having\ncompletely read that patch yet) that having the same notion of mtime\nbetween the two callsites is desirable.\n\nI still wonder whether imposing that notion of mtime at the object_info\nlayer is the right choice. I wonder if it would make more sense to allow\nthe caller to have a \"statp\" pointer filled out (or alternatively stick\na \"struct stat\" in both the packed union type as well as the loose one,\nthough the latter doesn't yet exist).\n\nThen the caller could do something like:\n\nstatic time_t object_info_gc_mtime(const struct object_info *oi)\n{\n    if (!oi->statp)\n        BUG(\"oops!\");\n\n    switch (oi->whence) {\n    case OI_CACHED:\n        return 0;\n    case OI_LOOSE:\n        return oi->statp->st_mtime;\n    case OI_PACKED:\n        struct packed_git *p = oi->u.packed.pack;\n        if (p->is_cruft) {\n            uint32_t pack_pos;\n\n            if (load_pack_mtimes(p) < 0)\n                die(_(\"could not load cruft pack .mtimes for '%s'\"),\n                    pack_basename(p));\n            if (offset_to_pack_pos(p, oi->u.packed.offset, &pack_pos) < 0)\n                die(_(\"could not find offset for object '%s' in cruft pack '%s'\"),\n                    oid_to_hex(&oi->oid),\n                    pack_basename(p));\n\n            return nth_packed_mtime(p, pack_pos_to_index(p, pack_pos));\n        } else {\n            return p->mtime; /* or oi->statp->st_mtime */\n        }\n    default:\n        BUG(\"unknown oi->whence: %d\", oi->whence);\n    }\n}\n\nI like the above because it encapsulates the GC-specific interpretation\nof an object's mtime outside of the object_info layer, while adding\ninformation (namely statp) that is generic enough to be potentially\nuseful to other callers who may not be interested in the GC-specific\ninterpretation.\n\n> +\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack,\n> +\t\t\t\t       oi->u.packed.offset, NULL, *oi->mtimep);\n>  \t} else {\n>  \t\tadd_object_entry(oid, OBJ_NONE, \"\", 0);\n>  \t}\n> @@ -4341,14 +4328,24 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,\n>\n>  static void add_objects_in_unpacked_packs(void)\n>  {\n> -\tif (for_each_packed_object(to_pack.repo,\n> -\t\t\t\t   add_object_in_unpacked_pack,\n> -\t\t\t\t   NULL,\n> -\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n> -\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n> -\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n> -\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n> -\t\tdie(_(\"cannot open pack index\"));\n> +\tstruct odb_source *source;\n> +\ttime_t mtime;\n> +\tstruct object_info oi = {\n> +\t\t.mtimep = &mtime,\n> +\t};\n> +\n> +\todb_prepare_alternates(to_pack.repo->objects);\n> +\tfor (source = to_pack.repo->objects->sources; source; source = source->next) {\n> +\t\tif (!source->local)\n> +\t\t\tcontinue;\n\nOK, we dropped the ODB_FOR_EACH_OBJECT_LOCAL_ONLY flag when dispatching\nto the packfile_store iterator, but that's OK, since it's handled above\nhere.\n\nInterestingly, packfile_store_for_each_object_internal() has a similar\ncheck:\n\n    if ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n        continue;\n\n, but I'm wondering whether these are subtly different. Would a\nnon-local source ever have packs for which the p->pack_local bit is set?\nOr is the locality of a pack determined relative to the source\ncontaining it, in which case we'd need to make the check here?\n\n> +\t\tif (packfile_store_for_each_object(source->packfiles, &oi,\n> +\t\t\t\t\t\t   add_object_in_unpacked_pack, NULL,\n> +\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n> +\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n> +\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n> +\t\t\tdie(_(\"cannot open pack index\"));\n> +\t}\n>  }\n>\n>  static int add_loose_object(const struct object_id *oid, const char *path,\n>\n> --\n> 2.53.0.rc0.250.g0ac79233d6.dirty\n>\nThanks,\nTaylor\n"},{"id":"534529","messageId":"aXNCjT6Al-4YLah5@pks.im","threadId":"64809","inReplyTo":"aXK7cSJW2syew89a@nand.local","subject":"Re: [PATCH v3 06/14] packfile: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-23T09:42:37Z","receivedAt":"2026-01-23T09:42:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 22, 2026 at 07:06:09PM -0500, Taylor Blau wrote:\n> On Wed, Jan 21, 2026 at 01:50:22PM +0100, Patrick Steinhardt wrote:\n> > Introduce a new function `packfile_store_for_each_object()`. This\n> > function is the equivalent to `odb_source_loose_for_each_object()` in\n> \n> s/to/of/ ?\n\nHm, isn't \"to\" correct in this case? The remainder of the sentence reads\nweird though.\n\n> > that it:\n> >\n> >   - Works on a single packfile store and thus per object source.\n> \n> s/thus per/thus/ ?\n\nI'll also rephrase this a bit.\n\n> > diff --git a/packfile.c b/packfile.c\n> > index d15a2ce12b..cd45c6f21c 100644\n> > --- a/packfile.c\n> > +++ b/packfile.c\n> > @@ -2360,6 +2360,54 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n> >  \treturn ret ? ret : pack_errors;\n> >  }\n> >\n> > +struct packfile_store_for_each_object_wrapper_data {\n> > +\tstruct packfile_store *store;\n> > +\tstruct object_info *oi;\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 packfile_store_for_each_object_wrapper_data *data = cb_data;\n> > +\n> > +\tif (data->oi) {\n> \n> Interesting. Is it the case that if the caller provides a non-NULL\n> pointer to an object_info struct, that we will reuse the request portion\n> for all iterated objects, updating the response portion as we go along?\n> \n> If so, I am a little uneasy about the potential for us to mix portions\n> of the response from an earlier object with a later one. Skimming\n> packed_object_info(), I don't think that we are in any immediate danger\n> since it overwrites all fields in the response section. But that feels\n> somewhat fragile to me, say, if packed_object_info() were to at some\n> point conditionally assign a field.\n> \n> I wonder if we should split the request/response sections of object_info\n> into their own object_info_req and object_info_resp structs. If we did\n> that, then we could invert the pattern for providing the response,\n> filling it out ourselves and then passing a pointer to it back to the\n> caller via the callback function.\n\nYeah, I agree that the current interfaces we have around reading objects\nis weird because of the mixed in/out behaviour of `struct object_info`.\nI didn't really feel like changing it in this series though because it\nwould lead to a lot changes all over the place.\n\nMaybe this is something we can do as a follow-up?\n\n> TBH, I wonder whether we should push this onto the caller entirely. If\n> they need to make an object_info request for each object, is there any\n> cost to having them do that explicitly themselves?\n\nThere is, yeah. The nice thing about combining iteration with the object\ninfo request is that we have more information available when reading the\nobject info:\n\n  - For packfiles we already have the info where exactly the object\n    sits, so there is no need to do another search for the object.\n\n  - For loose objects we already have the path available, even though\n    this probably doesn't matter too much as the path is trivial to\n    compute.\n\nFor other backends I very much expect that we'll be able to make use of\nsimilar optimizations. For a remote database for example you could craft\nthe query in such a way that we yield all objects with the exact info\nrequired instead of having to perform a separate query for the object\ninfo.\n\nPatrick\n"},{"id":"534530","messageId":"aXNCmnDysVmrMVJ-@pks.im","threadId":"64809","inReplyTo":"aXLBoFnqbnCcylCD@nand.local","subject":"Re: [PATCH v3 08/14] builtin/fsck: refactor to use `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-23T09:42:50Z","receivedAt":"2026-01-23T09:43:00Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 22, 2026 at 07:32:32PM -0500, Taylor Blau wrote:\n> On Wed, Jan 21, 2026 at 01:50:24PM +0100, Patrick Steinhardt wrote:\n> > diff --git a/builtin/fsck.c b/builtin/fsck.c\n> > index 4979bc795e..96107695ae 100644\n> > --- a/builtin/fsck.c\n> > +++ b/builtin/fsck.c\n> > @@ -218,15 +218,17 @@ static int mark_used(struct object *obj, enum object_type type UNUSED,\n> >  \treturn 0;\n> >  }\n> >\n> > -static void mark_unreachable_referents(const struct object_id *oid)\n> > +static int mark_unreachable_referents(const struct object_id *oid,\n> > +\t\t\t\t      struct object_info *io UNUSED,\n> \n> s/io/oi/ ?\n\nWell spotted.\n\n> > +\t\t\t\t      void *data UNUSED)\n> >  {\n> \n> The transformation here makes sense (and I think aloud through the\n> similar mark_object_for_connectivity() transformation below). One\n> thought that I had while reading, though, was how this function behaves\n> when it is passed the same object more than once, since you mention that\n> as a possibility in the commit which introduces odb_for_each_object().\n> \n> I think that this is OK, since we already likely send the same object to\n> this function multiple times if, e.g., we freshened an object from the\n> cruft pack, in which case we'd see it both when iterating packed\n> objects as well as when iterating loose ones.\n> \n> As far as I can tell, that's OK, but it might be nice to provide a brief\n> analysis of that in the commit message, just to be sure and to help\n> future readers.\n\nFair point, will mention.\n\nPatrick\n"},{"id":"534531","messageId":"aXNCpJ6q3HwcwhPG@pks.im","threadId":"64809","inReplyTo":"aXLEzAnNRTf5A6bt@nand.local","subject":"Re: [PATCH v3 10/14] treewide: drop uses of `for_each_{loose,packed}_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-23T09:43:00Z","receivedAt":"2026-01-23T09:43:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 22, 2026 at 07:46:04PM -0500, Taylor Blau wrote:\n> On Wed, Jan 21, 2026 at 01:50:26PM +0100, Patrick Steinhardt wrote:\n> > diff --git a/builtin/cat-file.c b/builtin/cat-file.c\n> > index 6964a5a52c..7d16fbc1b8 100644\n> > --- a/builtin/cat-file.c\n> > +++ b/builtin/cat-file.c\n> > @@ -846,8 +849,15 @@ static void batch_each_object(struct batch_options *opt,\n> >  \t\t.payload = _payload,\n> >  \t};\n> >  \tstruct bitmap_index *bitmap = prepare_bitmap_git(the_repository);\n> > +\tstruct odb_source *source;\n> >\n> > -\tfor_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);\n> > +\todb_prepare_alternates(the_repository->objects);\n> > +\tfor (source = the_repository->objects->sources; source; source = source->next) {\n> > +\t\tint ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi,\n> > +\t\t\t\t\t\t\t   &payload, flags);\n> > +\t\tif (ret)\n> > +\t\t\tbreak;\n> > +\t}\n> \n> OK, I'm guessing that this is one such case where we can't yet use\n> odb_for_each_object() function directly because of the refactoring which\n> you alluded to in the commit message. That seems reasonable, though I\n> wonder if it's worth adding a /* TODO */ comment here to that effect.\n\nSure, I can add a comment.\n\n> Just out of curiosity, what does that refactoring entail? I'm curious\n> because I wonder whether the caller is just written in such a way that\n> it makes it hard to immediately plug into the new API, or whether there\n> are more fundamental issues at play that make the refactoring less than\n> straightforward. If the latter, those could potentially help inform the\n> direction here.\n> \n> (To be clear, I figure that this is likely work that you have already\n> done, I'm just curious to see if the details would yield any benefit to\n> the immediate patch series under discussion.)\n\nWhat this code here intends to do is to filter objects via an object\nfilter (e.g. \"--filter=blobs:none\"). The way I intend do introduce this\nfunctinoality in a subsequent series is to introduce a `struct\nodb_for_each_object_options` that contains optional parameters:\n\n  - An object ID prefix that can be used to iterate over all objects\n    that have a certain matching prefix. This will be used for example\n    in \"object-name.c\".\n\n  - An object filter that can be used to filter objects like we do here.\n\n  - Potentially more things that I haven't discovered yet?\n\nOnce we have that, the filtering can then happen on the source level.\nFor the packfile store it would mean that we can try to filter via the\nbitmap, if available, and that would allow us to move the logic that we\nhave here into the backend.\n\nPatrick\n"},{"id":"534532","messageId":"aXNCq8h94i2Z6uSa@pks.im","threadId":"64809","inReplyTo":"aXLJoDdoEyKXKtBf@nand.local","subject":"Re: [PATCH v3 11/14] odb: introduce mtime fields for object info requests","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-23T09:43:07Z","receivedAt":"2026-01-23T09:43:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 22, 2026 at 08:06:40PM -0500, Taylor Blau wrote:\n> On Wed, Jan 21, 2026 at 01:50:27PM +0100, Patrick Steinhardt wrote:\n> > There are some use cases where we need to figure out the mtime for\n> > objects. Most importantly, this is the case when we want to prune\n> > unreachable objects. But getting at that data requires users to manually\n> > derive the info either via the loose object's mtime, the packfiles'\n> > mtime or via the \".mtimes\" file.\n> >\n> > Introduce a new `struct object_info::mtimep` pointer that allows callers\n> > to request an object's mtime. This new field will be used in a\n> > subsequent commit.\n> \n> The goal seems reasonable to me, but I am a little unsure about whether\n> or not this is the right place to expose this information. I have some\n> more thoughts below...\n> \n> > diff --git a/odb.c b/odb.c\n> > index 65f0447aa5..67decd3908 100644\n> > --- a/odb.c\n> > +++ b/odb.c\n> > @@ -702,6 +702,8 @@ static int do_oid_object_info_extended(struct object_database *odb,\n> >  \t\t\t\toidclr(oi->delta_base_oid, odb->repo->hash_algo);\n> >  \t\t\tif (oi->contentp)\n> >  \t\t\t\t*oi->contentp = xmemdupz(co->buf, co->size);\n> > +\t\t\tif (oi->mtimep)\n> > +\t\t\t\t*oi->mtimep = 0;\n> \n> Assuming that you do not change the object_info request/response\n> semantics, I wonder if it might make sense to zero out the entirety of\n> the response section as a belt-and-suspenders mechanism in case future\n> contributors forget to assign zero to the new fields themselves.\n\nSplitting up the request/response structure as you proposed in a\nprevious patch could definitely help with this. I'd prefer to rather do\nsuch a bigger change as a follow-up though as it would lead to a lot of\nchurn.\n\n> > @@ -1619,16 +1620,34 @@ int packed_object_info(struct packed_git *p,\n> >  \t\t}\n> >  \t}\n> >\n> > -\tif (oi->disk_sizep) {\n> > -\t\tuint32_t pos;\n> > -\t\tif (offset_to_pack_pos(p, obj_offset, &pos) < 0) {\n> > +\tif (oi->disk_sizep || (oi->mtimep && p->is_cruft)) {\n> > +\t\tif (offset_to_pack_pos(p, obj_offset, &pack_pos) < 0) {\n> >  \t\t\terror(\"could not find object at offset %\"PRIuMAX\" \"\n> >  \t\t\t      \"in pack %s\", (uintmax_t)obj_offset, p->pack_name);\n> >  \t\t\tret = -1;\n> >  \t\t\tgoto out;\n> >  \t\t}\n> > +\t}\n> > +\n> > +\tif (oi->disk_sizep)\n> > +\t\t*oi->disk_sizep = pack_pos_to_offset(p, pack_pos + 1) - obj_offset;\n> > +\n> > +\tif (oi->mtimep) {\n> > +\t\tif (p->is_cruft) {\n> > +\t\t\tuint32_t index_pos;\n> > +\n> > +\t\t\tif (load_pack_mtimes(p) < 0)\n> > +\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n> \n> Do you think it would be worth doing instead:\n> \n>     die(_(\"could not load .mtimes for cruft pack '%s'\"), pack_basename(p));\n> \n> ? Most repositories should only ever have one cruft pack in practice\n> (even so, there should still be some value in identifying it by its\n> checksum in case someone is repacking underneath us). But some\n> repositories will have >1 cruft pack, so knowing which one is busted may\n> be useful in that case.\n\nYup, makes sense.\n\n> > +\n> > +\t\t\tif (maybe_index_pos)\n> > +\t\t\t\tindex_pos = *maybe_index_pos;\n> > +\t\t\telse\n> > +\t\t\t\tindex_pos = pack_pos_to_index(p, pack_pos);\n> >\n> > -\t\t*oi->disk_sizep = pack_pos_to_offset(p, pos + 1) - obj_offset;\n> > +\t\t\t*oi->mtimep = nth_packed_mtime(p, index_pos);\n> > +\t\t} else {\n> > +\t\t\t*oi->mtimep = p->mtime;\n> > +\t\t}\n> \n> I am a little stuck here on whether or not this is the right layer to\n> determine an object's mtime. On the one hand, it makes sense to me that\n> callers would want to know the mtime of an object, either by the mtime\n> of the loose object on disk, or the mtime of the contain pack otherwise.\n> \n> But I'm not sure whether the GC-specific definition of \"mtime\" is what\n> the caller would always want. For GC uses, yes, having mtime be aware of\n> cruft packs makes total sense to me. But for non-GC uses, would there\n> ever be a scenario where the caller would want to know the mtime of an\n> object's containing pack, regardless of whether or not that pack is\n> cruft?\n> \n> I suppose they could get around that today by doing something like:\n> \n>     if (oi->whence == OI_PACKED) {\n>         struct packed_git *p = oi->u.packed.p;\n>         if (p->is_cruft) {\n>             /* reinterpret the meaning of mtime... */\n>             *oi->mtimep = p->mtime;\n>         }\n>     }\n> \n> , but that feels a little clunky. I dunno, maybe this hypothetical\n> doesn't really exist and I'm overthinking this. But I have this nagging\n> feeling that we are exposing this information at too low of a level as\n> to make the object store aware of cruft pack/GC-specific mechanics.\n\nI'll answer on your next mail, where you also talk about this.\n\nPatrick\n"},{"id":"534533","messageId":"aXNCtCZwP57Tfu60@pks.im","threadId":"64809","inReplyTo":"aXLNM+AOpdQtmisC@nand.local","subject":"Re: [PATCH v3 12/14] builtin/pack-objects: use `packfile_store_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-23T09:43:16Z","receivedAt":"2026-01-23T09:43:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 22, 2026 at 08:21:55PM -0500, Taylor Blau wrote:\n> On Wed, Jan 21, 2026 at 01:50:28PM +0100, Patrick Steinhardt wrote:\n> >  static int add_object_in_unpacked_pack(const struct object_id *oid,\n> > -\t\t\t\t       struct packed_git *pack,\n> > -\t\t\t\t       uint32_t pos,\n> > +\t\t\t\t       struct object_info *oi,\n> >  \t\t\t\t       void *data UNUSED)\n> >  {\n> >  \tif (cruft) {\n> > -\t\toff_t offset;\n> > -\t\ttime_t mtime;\n> > -\n> > -\t\tif (pack->is_cruft) {\n> > -\t\t\tif (load_pack_mtimes(pack) < 0)\n> > -\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n> > -\t\t\tmtime = nth_packed_mtime(pack, pos);\n> > -\t\t} else {\n> > -\t\t\tmtime = pack->mtime;\n> > -\t\t}\n> > -\t\toffset = nth_packed_object_offset(pack, pos);\n> > -\n> > -\t\tadd_cruft_object_entry(oid, OBJ_NONE, pack, offset,\n> > -\t\t\t\t       NULL, mtime);\n> \n> OK, here's where we see the existing logic for determining the mtime of\n> an object in the GC sense. I see there's a subsequent patch that also\n> makes use of the object_info->mtimep field, and my guess is (not having\n> completely read that patch yet) that having the same notion of mtime\n> between the two callsites is desirable.\n> \n> I still wonder whether imposing that notion of mtime at the object_info\n> layer is the right choice. I wonder if it would make more sense to allow\n> the caller to have a \"statp\" pointer filled out (or alternatively stick\n> a \"struct stat\" in both the packed union type as well as the loose one,\n> though the latter doesn't yet exist).\n\nThe problem with filling out a `struct stat` though is that it will only\napply to backends that actually have a path to stat. There may be other\nbackends that don't. You could of course pretend that there was a file\nand fill in the `st_mtime` field. But I don't really see the benefit\nover having a standalone mtime field.\n\n> Then the caller could do something like:\n> \n> static time_t object_info_gc_mtime(const struct object_info *oi)\n> {\n>     if (!oi->statp)\n>         BUG(\"oops!\");\n> \n>     switch (oi->whence) {\n>     case OI_CACHED:\n>         return 0;\n>     case OI_LOOSE:\n>         return oi->statp->st_mtime;\n>     case OI_PACKED:\n>         struct packed_git *p = oi->u.packed.pack;\n>         if (p->is_cruft) {\n>             uint32_t pack_pos;\n> \n>             if (load_pack_mtimes(p) < 0)\n>                 die(_(\"could not load cruft pack .mtimes for '%s'\"),\n>                     pack_basename(p));\n>             if (offset_to_pack_pos(p, oi->u.packed.offset, &pack_pos) < 0)\n>                 die(_(\"could not find offset for object '%s' in cruft pack '%s'\"),\n>                     oid_to_hex(&oi->oid),\n>                     pack_basename(p));\n> \n>             return nth_packed_mtime(p, pack_pos_to_index(p, pack_pos));\n>         } else {\n>             return p->mtime; /* or oi->statp->st_mtime */\n>         }\n>     default:\n>         BUG(\"unknown oi->whence: %d\", oi->whence);\n>     }\n> }\n> \n> I like the above because it encapsulates the GC-specific interpretation\n> of an object's mtime outside of the object_info layer, while adding\n> information (namely statp) that is generic enough to be potentially\n> useful to other callers who may not be interested in the GC-specific\n> interpretation.\n\nThis isn't achieving the goal of making the logic pluggable though, as\nyou now have backend-specific logic outside of the backends. Also, isn't\nthe end result basically the same as what I have proposed, except that\nmy version _is_ fully pluggable because the logic is entirely contained\nin the backend?\n\n> > @@ -4341,14 +4328,24 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,\n> >\n> >  static void add_objects_in_unpacked_packs(void)\n> >  {\n> > -\tif (for_each_packed_object(to_pack.repo,\n> > -\t\t\t\t   add_object_in_unpacked_pack,\n> > -\t\t\t\t   NULL,\n> > -\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n> > -\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n> > -\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n> > -\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n> > -\t\tdie(_(\"cannot open pack index\"));\n> > +\tstruct odb_source *source;\n> > +\ttime_t mtime;\n> > +\tstruct object_info oi = {\n> > +\t\t.mtimep = &mtime,\n> > +\t};\n> > +\n> > +\todb_prepare_alternates(to_pack.repo->objects);\n> > +\tfor (source = to_pack.repo->objects->sources; source; source = source->next) {\n> > +\t\tif (!source->local)\n> > +\t\t\tcontinue;\n> \n> OK, we dropped the ODB_FOR_EACH_OBJECT_LOCAL_ONLY flag when dispatching\n> to the packfile_store iterator, but that's OK, since it's handled above\n> here.\n> \n> Interestingly, packfile_store_for_each_object_internal() has a similar\n> check:\n> \n>     if ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n>         continue;\n> \n> , but I'm wondering whether these are subtly different. Would a\n> non-local source ever have packs for which the p->pack_local bit is set?\n> Or is the locality of a pack determined relative to the source\n> containing it, in which case we'd need to make the check here?\n\nTo the best of my knowledge we may only ever end up adding a non-local\npack to the source, but not the other way round. This can for example\nhappen in git-index-pack(1).\n\nBut you know, there isn't any good reason to not continue passing this\nflag. Better be safe than sorry.\n\nThanks!\n\nPatrick\n"},{"id":"534534","messageId":"CAPx1Gvd6BGPeVmN5b7WM_r6OFf7Y6KooJ2O1jT5O6LzNzGuEEw@mail.gmail.com","threadId":"64809","inReplyTo":"aXNCjT6Al-4YLah5@pks.im","subject":"Re: [PATCH v3 06/14] packfile: introduce function to iterate through objects","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2026-01-23T09:52:00Z","receivedAt":"2026-01-23T09:52:19Z","isPatch":true,"sender":{"key":"chris.torek@gmail.com","avatar":"https://avatars.githubusercontent.com/u/16826774?v=4"},"body":"On Fri, Jan 23, 2026 at 1:43 AM Patrick Steinhardt <ps@pks.im> wrote:\n> On Thu, Jan 22, 2026 at 07:06:09PM -0500, Taylor Blau wrote:\n> > On Wed, Jan 21, 2026 at 01:50:22PM +0100, Patrick Steinhardt wrote:\n> > > Introduce a new function `packfile_store_for_each_object()`. This\n> > > function is the equivalent to `odb_source_loose_for_each_object()` in\n> >\n> > s/to/of/ ?\n>\n> Hm, isn't \"to\" correct in this case? The remainder of the sentence reads\n> weird though.\n\nDifferent English dialects. The preposition after \"different\" differs...\n\n(It also matters whether you use the definite article, \"the function F1\nis THE equivalent of F2 in case X\" vs \"function F1 is equivalent to F2\nin case X\".)\n\nIndian English uses \"a doubt\" to mean \"a question\", which always\ndrives me up the (a?) wall, but then there are languages without\narticles (Chinese and Russian for instance)...\n\nChris\n"},{"id":"534537","messageId":"aXNUNJudud_KuT33@pks.im","threadId":"64809","inReplyTo":"20260122192337.GC2098026@coredump.intra.peff.net","subject":"Re: [PATCH v3 02/14] odb: fix flags parameter to be unsigned","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-23T10:57:56Z","receivedAt":"2026-01-23T10:58:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 22, 2026 at 02:23:37PM -0500, Jeff King wrote:\n> On Thu, Jan 22, 2026 at 07:41:51AM -0800, Junio C Hamano wrote:\n> \n> > Taylor Blau <me@ttaylorr.com> writes:\n> > \n> > > I agree with you that we should be using an enum in these cases over\n> > > unsigned for the reasons you suggest. I've stumbled over this in the\n> > > past, so perhaps this is worth adding to the CodingGuidelines?\n> > \n> > I am OK with declaring our preference of \"enum\" over \"#define\"d\n> > constants.  The only two minor hesitation I have against the use of\n> > \"enum\", especially for bitset but not for enumeration, are that\n> \n> I don't think there's any disagreement over using enums in general. It's\n> just a question of what type to declare in function interfaces.\n> \n> >  (1) enum gives a false sense of type safety to casual coders. If I\n> >      have two enum types and pass one to as a parameter to a\n> >      function that expects the other one, would the compiler help me\n> >      catch that as a potential mistake?  -Wenum-conversion is not\n> >      enabled even with -Wall so I am assuming that the compiler\n> >      folks fells that it is not reliable enough.\n> \n> It is enabled with -Wextra, which we turn on with DEVELOPER=1. I think\n> gcc will catch the most obvious mismatches like:\n> \n>   enum one { FOO };\n>   enum two { BAR };\n>   void func(enum one value);\n>   void doit(void) { func(BAR); }\n> \n> which yields:\n> \n>   $ gcc -c -Wall -Wextra foo.c\n>   foo.c: In function ‘doit’:\n>   foo.c:4:24: warning: implicit conversion from ‘enum two’ to ‘enum one’ [-Wenum-conversion]\n>       4 | void doit(void) { func(BAR); }\n>         |                        ^~~\n> \n> What it doesn't help with is passing arbitrary integers, which includes\n> #define'd constants. Swapping out \"enum two\" for:\n> \n>   #define BAR 1\n> \n> will not produce a warning. That's the issue that I ran into with the\n> color code in:\n> \n>   https://lore.kernel.org/git/20250916202748.GM612873@coredump.intra.peff.net/\n> \n> Unfortunately bit operations on enum values seem to lose the \"type\" for\n> the purposes of this warning, and just become regular integers. So if we\n> modify our example to:\n> \n>   num one { FOO_A = 1 << 0, FOO_B = 1 << 1 };\n>   enum two { BAR_A = 1 << 0, BAR_B = 1 << 1 };\n>   void func(enum one value);\n>   void doit(void) { func(BAR_A | BAR_B); }\n> \n> it no longer complains.\n> \n> I still think we are better off declaring the flag parameters with the\n> enum type, though. It will catch some problematic cases. And even if\n> there were no compiler support at all, I think the hint to humans about\n> the expected type is worth it.\n\nI don't care strongly enough myself, but do you or Taylor maybe want to\nsend a patch that documents our preference? If so I'll be happy to adapt\nmy series to use whatever style we agree on.\n\nThanks!\n\nPatrick\n"},{"id":"534557","messageId":"xmqq343wjo2v.fsf@gitster.g","threadId":"64809","inReplyTo":"CAPx1Gvd6BGPeVmN5b7WM_r6OFf7Y6KooJ2O1jT5O6LzNzGuEEw@mail.gmail.com","subject":"Re: [PATCH v3 06/14] packfile: introduce function to iterate through objects","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-23T16:22:48Z","receivedAt":"2026-01-23T16:22:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Chris Torek <chris.torek@gmail.com> writes:\n\n>> > > function is the equivalent to `odb_source_loose_for_each_object()` in\n>> >\n>> > s/to/of/ ?\n>>\n>> Hm, isn't \"to\" correct in this case? The remainder of the sentence reads\n>> weird though.\n>\n> Different English dialects. The preposition after \"different\" differs...\n>\n> (It also matters whether you use the definite article, \"the function F1\n> is THE equivalent of F2 in case X\" vs \"function F1 is equivalent to F2\n> in case X\".)\n\nHeh, \"equivalent\" is \"Y is an equivalent of X\" is a noun.  It is\nadjective in \"A is equivalent to B\".  Of course, article is used\nonly with the former (i.e. noun) form, but article is not the\nessential difference, parts of speech is.\n"},{"id":"534570","messageId":"aXOzp4ivyYgPLux4@nand.local","threadId":"64809","inReplyTo":"xmqq343wjo2v.fsf@gitster.g","subject":"Re: [PATCH v3 06/14] packfile: introduce function to iterate through objects","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T17:45:11Z","receivedAt":"2026-01-23T17:45:13Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Jan 23, 2026 at 08:22:48AM -0800, Junio C Hamano wrote:\n> Chris Torek <chris.torek@gmail.com> writes:\n>\n> >> > > function is the equivalent to `odb_source_loose_for_each_object()` in\n> >> >\n> >> > s/to/of/ ?\n> >>\n> >> Hm, isn't \"to\" correct in this case? The remainder of the sentence reads\n> >> weird though.\n> >\n> > Different English dialects. The preposition after \"different\" differs...\n> >\n> > (It also matters whether you use the definite article, \"the function F1\n> > is THE equivalent of F2 in case X\" vs \"function F1 is equivalent to F2\n> > in case X\".)\n>\n> Heh, \"equivalent\" is \"Y is an equivalent of X\" is a noun.  It is\n> adjective in \"A is equivalent to B\".  Of course, article is used\n> only with the former (i.e. noun) form, but article is not the\n> essential difference, parts of speech is.\n\nAn alternative suggestion would be s/the //, making this read:\n\n    This function is equivalent to `odb_source_loose_for_each_object()`\n    in that it [...]\n\nThanks,\nTaylor\n"},{"id":"534573","messageId":"aXO0cNaY3DWu6aQ2@nand.local","threadId":"64809","inReplyTo":"aXNCq8h94i2Z6uSa@pks.im","subject":"Re: [PATCH v3 11/14] odb: introduce mtime fields for object info requests","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T17:48:32Z","receivedAt":"2026-01-23T17:48:36Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Jan 23, 2026 at 10:43:07AM +0100, Patrick Steinhardt wrote:\n> > > diff --git a/odb.c b/odb.c\n> > > index 65f0447aa5..67decd3908 100644\n> > > --- a/odb.c\n> > > +++ b/odb.c\n> > > @@ -702,6 +702,8 @@ static int do_oid_object_info_extended(struct object_database *odb,\n> > >  \t\t\t\toidclr(oi->delta_base_oid, odb->repo->hash_algo);\n> > >  \t\t\tif (oi->contentp)\n> > >  \t\t\t\t*oi->contentp = xmemdupz(co->buf, co->size);\n> > > +\t\t\tif (oi->mtimep)\n> > > +\t\t\t\t*oi->mtimep = 0;\n> >\n> > Assuming that you do not change the object_info request/response\n> > semantics, I wonder if it might make sense to zero out the entirety of\n> > the response section as a belt-and-suspenders mechanism in case future\n> > contributors forget to assign zero to the new fields themselves.\n>\n> Splitting up the request/response structure as you proposed in a\n> previous patch could definitely help with this. I'd prefer to rather do\n> such a bigger change as a follow-up though as it would lead to a lot of\n> churn.\n\nI'm OK with pushing the larger change down the road, but I am a little\nuncomfortable with the interim state being introduced here. Perhaps a\ncompromise here would be to have the caller supply a pointer to an\nobject_info struct, whose request fields we honor. The response fields\nwould then be written into a separate object_info struct via an\nout-parameter.\n\nI don't know. I think that ^ this suggestion is kind of ugly, but I'm\ntrying to come up with something that doesn't introduce the risk I\ndescribed above in the interim between this patch series and the one\nyou're proposing later on.\n\nThanks,\nTaylor\n"},{"id":"534576","messageId":"aXO/YLzRlDXD5IPY@nand.local","threadId":"64809","inReplyTo":"aXNCtCZwP57Tfu60@pks.im","subject":"Re: [PATCH v3 12/14] builtin/pack-objects: use `packfile_store_for_each_object()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2026-01-23T18:35:12Z","receivedAt":"2026-01-23T18:35:14Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Jan 23, 2026 at 10:43:16AM +0100, Patrick Steinhardt wrote:\n> On Thu, Jan 22, 2026 at 08:21:55PM -0500, Taylor Blau wrote:\n> > On Wed, Jan 21, 2026 at 01:50:28PM +0100, Patrick Steinhardt wrote:\n> > >  static int add_object_in_unpacked_pack(const struct object_id *oid,\n> > > -\t\t\t\t       struct packed_git *pack,\n> > > -\t\t\t\t       uint32_t pos,\n> > > +\t\t\t\t       struct object_info *oi,\n> > >  \t\t\t\t       void *data UNUSED)\n> > >  {\n> > >  \tif (cruft) {\n> > > -\t\toff_t offset;\n> > > -\t\ttime_t mtime;\n> > > -\n> > > -\t\tif (pack->is_cruft) {\n> > > -\t\t\tif (load_pack_mtimes(pack) < 0)\n> > > -\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n> > > -\t\t\tmtime = nth_packed_mtime(pack, pos);\n> > > -\t\t} else {\n> > > -\t\t\tmtime = pack->mtime;\n> > > -\t\t}\n> > > -\t\toffset = nth_packed_object_offset(pack, pos);\n> > > -\n> > > -\t\tadd_cruft_object_entry(oid, OBJ_NONE, pack, offset,\n> > > -\t\t\t\t       NULL, mtime);\n> >\n> > OK, here's where we see the existing logic for determining the mtime of\n> > an object in the GC sense. I see there's a subsequent patch that also\n> > makes use of the object_info->mtimep field, and my guess is (not having\n> > completely read that patch yet) that having the same notion of mtime\n> > between the two callsites is desirable.\n> >\n> > I still wonder whether imposing that notion of mtime at the object_info\n> > layer is the right choice. I wonder if it would make more sense to allow\n> > the caller to have a \"statp\" pointer filled out (or alternatively stick\n> > a \"struct stat\" in both the packed union type as well as the loose one,\n> > though the latter doesn't yet exist).\n>\n> The problem with filling out a `struct stat` though is that it will only\n> apply to backends that actually have a path to stat. There may be other\n> backends that don't. You could of course pretend that there was a file\n> and fill in the `st_mtime` field. But I don't really see the benefit\n> over having a standalone mtime field.\n\nI understand what you're saying, but I don't think that this is unique\nto stat. Looking through the object_info struct, there are a handful of\nfields on the request side that are coupled to the objects themselves,\nnot their representation, such as typep, sizep, and contentp.\n\nBut there are a handful of fields in the request section which are *not*\nproperties of the objects themselves, but rather properties of the way\nthose objects are represented as part of the backend-specific\nimplementation.\n\nFor example, disk_sizep suggests that all objects are stored on disk and\nhave a clear notion of how much space they occupy. I could imagine a\nbackend implementation where perhaps the contents of objects are divvied\nup into smaller chunks and deduplicated across many objects. I don't\nthink there is a clear answer to how much \"disk size\" an object occupies\nin that case.\n\ndelta_base_oid is another field that I'd argue is not a property of the\nobject itself, but rather its representation. Of course, objects stored\nin packfiles may or may not be stored as a delta against some other\nobject, and thus being able to ask what that object is makes sense. But\nloose objects don't have the same property as a result of how they are\nstored.\n\nTo me this seems like an example where implementation-specific details\nare already leaking through the object_info struct. So in that sense I\ndon't think that adding a \"struct stat\" here is meaningfully changing\nanything.\n\nBut I think the proposed mtimep field is a special case not only for the\nreasons stated above, but because an object's mtime has multiple\ninterpretations already. For example, if I'm asking about an object's\nmtime, and that object happens to be stored in a cruft pack, which mtime\nam I referring to? Packed objects inherit their mtime from the mtime of\nthe *.pack itself, but cruft objects have an additional interpretation\nwhich is read from the *.mtimes file corresponding to the cruft pack.\n\nI don't love bolting another leaky abstraction onto the object_info\ninterface, but my broader concern is that the information here is not\njust leaky but ambiguous. By adding a statp pointer, I think the\ninformation is less ambiguous since the GC-specific interpretation of\nmtime is done at a layer above stat(2).\n\n> > Then the caller could do something like:\n> >\n> > static time_t object_info_gc_mtime(const struct object_info *oi)\n> > {\n> >     if (!oi->statp)\n> >         BUG(\"oops!\");\n> >\n> >     switch (oi->whence) {\n> >     case OI_CACHED:\n> >         return 0;\n> >     case OI_LOOSE:\n> >         return oi->statp->st_mtime;\n> >     case OI_PACKED:\n> >         struct packed_git *p = oi->u.packed.pack;\n> >         if (p->is_cruft) {\n> >             uint32_t pack_pos;\n> >\n> >             if (load_pack_mtimes(p) < 0)\n> >                 die(_(\"could not load cruft pack .mtimes for '%s'\"),\n> >                     pack_basename(p));\n> >             if (offset_to_pack_pos(p, oi->u.packed.offset, &pack_pos) < 0)\n> >                 die(_(\"could not find offset for object '%s' in cruft pack '%s'\"),\n> >                     oid_to_hex(&oi->oid),\n> >                     pack_basename(p));\n> >\n> >             return nth_packed_mtime(p, pack_pos_to_index(p, pack_pos));\n> >         } else {\n> >             return p->mtime; /* or oi->statp->st_mtime */\n> >         }\n> >     default:\n> >         BUG(\"unknown oi->whence: %d\", oi->whence);\n> >     }\n> > }\n> >\n> > I like the above because it encapsulates the GC-specific interpretation\n> > of an object's mtime outside of the object_info layer, while adding\n> > information (namely statp) that is generic enough to be potentially\n> > useful to other callers who may not be interested in the GC-specific\n> > interpretation.\n>\n> This isn't achieving the goal of making the logic pluggable though, as\n> you now have backend-specific logic outside of the backends. Also, isn't\n> the end result basically the same as what I have proposed, except that\n> my version _is_ fully pluggable because the logic is entirely contained\n> in the backend?\n\nYes, the end result is the same, both your patch and what I wrote here\nimplement the same GC-specific definition of an object's \"mtime\". I am\nnot following the argument about pluggability, though. The concern I\nhave above is that we are pushing domain-specific logic into the object\nstorage backend, not the other way around.\n\nThanks,\nTaylor\n"},{"id":"534636","messageId":"aXcrftLpfcG4S5AX@pks.im","threadId":"64809","inReplyTo":"aXO/YLzRlDXD5IPY@nand.local","subject":"Re: [PATCH v3 12/14] builtin/pack-objects: use `packfile_store_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T08:53:18Z","receivedAt":"2026-01-26T08:53:30Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Jan 23, 2026 at 01:35:12PM -0500, Taylor Blau wrote:\n> On Fri, Jan 23, 2026 at 10:43:16AM +0100, Patrick Steinhardt wrote:\n> > On Thu, Jan 22, 2026 at 08:21:55PM -0500, Taylor Blau wrote:\n> > > On Wed, Jan 21, 2026 at 01:50:28PM +0100, Patrick Steinhardt wrote:\n> > > >  static int add_object_in_unpacked_pack(const struct object_id *oid,\n> > > > -\t\t\t\t       struct packed_git *pack,\n> > > > -\t\t\t\t       uint32_t pos,\n> > > > +\t\t\t\t       struct object_info *oi,\n> > > >  \t\t\t\t       void *data UNUSED)\n> > > >  {\n> > > >  \tif (cruft) {\n> > > > -\t\toff_t offset;\n> > > > -\t\ttime_t mtime;\n> > > > -\n> > > > -\t\tif (pack->is_cruft) {\n> > > > -\t\t\tif (load_pack_mtimes(pack) < 0)\n> > > > -\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n> > > > -\t\t\tmtime = nth_packed_mtime(pack, pos);\n> > > > -\t\t} else {\n> > > > -\t\t\tmtime = pack->mtime;\n> > > > -\t\t}\n> > > > -\t\toffset = nth_packed_object_offset(pack, pos);\n> > > > -\n> > > > -\t\tadd_cruft_object_entry(oid, OBJ_NONE, pack, offset,\n> > > > -\t\t\t\t       NULL, mtime);\n> > >\n> > > OK, here's where we see the existing logic for determining the mtime of\n> > > an object in the GC sense. I see there's a subsequent patch that also\n> > > makes use of the object_info->mtimep field, and my guess is (not having\n> > > completely read that patch yet) that having the same notion of mtime\n> > > between the two callsites is desirable.\n> > >\n> > > I still wonder whether imposing that notion of mtime at the object_info\n> > > layer is the right choice. I wonder if it would make more sense to allow\n> > > the caller to have a \"statp\" pointer filled out (or alternatively stick\n> > > a \"struct stat\" in both the packed union type as well as the loose one,\n> > > though the latter doesn't yet exist).\n> >\n> > The problem with filling out a `struct stat` though is that it will only\n> > apply to backends that actually have a path to stat. There may be other\n> > backends that don't. You could of course pretend that there was a file\n> > and fill in the `st_mtime` field. But I don't really see the benefit\n> > over having a standalone mtime field.\n> \n> I understand what you're saying, but I don't think that this is unique\n> to stat. Looking through the object_info struct, there are a handful of\n> fields on the request side that are coupled to the objects themselves,\n> not their representation, such as typep, sizep, and contentp.\n> \n> But there are a handful of fields in the request section which are *not*\n> properties of the objects themselves, but rather properties of the way\n> those objects are represented as part of the backend-specific\n> implementation.\n\nYes, the specific interpretation will change for some fields. But in\ngeneral, most of the fields still apply to all backends.\n\n> For example, disk_sizep suggests that all objects are stored on disk and\n> have a clear notion of how much space they occupy. I could imagine a\n> backend implementation where perhaps the contents of objects are divvied\n> up into smaller chunks and deduplicated across many objects. I don't\n> think there is a clear answer to how much \"disk size\" an object occupies\n> in that case.\n\nThis would still apply to other backends though. It's true that \"disk\"\nsize is a bit of a misnomer now, and that it should probably rather be\nrenamed to \"storage\" size. But overall, no matter the backend, you will\nstill eventually end up storing the object data somewhere, and that\ntakes up space.\n\n> delta_base_oid is another field that I'd argue is not a property of the\n> object itself, but rather its representation. Of course, objects stored\n> in packfiles may or may not be stored as a delta against some other\n> object, and thus being able to ask what that object is makes sense. But\n> loose objects don't have the same property as a result of how they are\n> stored.\n\nYup. This field is specific to the packed backend indeed and ideally\nshouldn't be part of the `struct object_info`. In the best case it could\nbe lifted into `struct object_info::u`, but I'm not sure whether that's\neasily possible.\n\n> To me this seems like an example where implementation-specific details\n> are already leaking through the object_info struct. So in that sense I\n> don't think that adding a \"struct stat\" here is meaningfully changing\n> anything.\n\nThe thing is that I'm trying to clean up all the different messes that\nwe have. So instead of adding _more_ leakiness, I'd rather prefer to\nremove some of it.\n\n> But I think the proposed mtimep field is a special case not only for the\n> reasons stated above, but because an object's mtime has multiple\n> interpretations already. For example, if I'm asking about an object's\n> mtime, and that object happens to be stored in a cruft pack, which mtime\n> am I referring to? Packed objects inherit their mtime from the mtime of\n> the *.pack itself, but cruft objects have an additional interpretation\n> which is read from the *.mtimes file corresponding to the cruft pack.\n\nThis is a good question indeed though.\n\n> I don't love bolting another leaky abstraction onto the object_info\n> interface, but my broader concern is that the information here is not\n> just leaky but ambiguous. By adding a statp pointer, I think the\n> information is less ambiguous since the GC-specific interpretation of\n> mtime is done at a layer above stat(2).\n\nFair point. For the current backend, mtime can be ambiguous as the same\nobject may be stored multiple times: either as a loose object, or as\npart of any of the packfiles.\n\nIn the context of `odb_for_each_object()` that info is not ambigous\nthough: we would yield the same object multiple times, and every time we\nyield it we will may have a different mtime. And this is working as\nexpected for the two callsites:\n\n  - In \"reachable.c\" we use the mtimep field in the context of recent\n    objects. So if at least one of the objects has a new-enough mtime we\n    would eventually see it.\n\n - In \"builtin/pack-objects.c\" we use basically the same logic as we\n   use after my patch seires, where we use either the cruft time or the\n   pack time. So things work as expected over there, too.\n\nSo I'd claim that this is working sensibly for `odb_for_each_object()`,\nand there is no ambiguity involved. It's the caller that has to\ndisambiguite, and that's already happening.\n\nBut things are a bit different if you invoke `odb_read_object_info()`\ndirectly, as we have no way to disambiguate there. We only want to yield\n_a_ representation of an object, so the mtime will be derived from\nwhatever data structure the object was found in first. This could be\nhelped with better documentation.\n\n> > > Then the caller could do something like:\n> > >\n> > > static time_t object_info_gc_mtime(const struct object_info *oi)\n> > > {\n> > >     if (!oi->statp)\n> > >         BUG(\"oops!\");\n> > >\n> > >     switch (oi->whence) {\n> > >     case OI_CACHED:\n> > >         return 0;\n> > >     case OI_LOOSE:\n> > >         return oi->statp->st_mtime;\n> > >     case OI_PACKED:\n> > >         struct packed_git *p = oi->u.packed.pack;\n> > >         if (p->is_cruft) {\n> > >             uint32_t pack_pos;\n> > >\n> > >             if (load_pack_mtimes(p) < 0)\n> > >                 die(_(\"could not load cruft pack .mtimes for '%s'\"),\n> > >                     pack_basename(p));\n> > >             if (offset_to_pack_pos(p, oi->u.packed.offset, &pack_pos) < 0)\n> > >                 die(_(\"could not find offset for object '%s' in cruft pack '%s'\"),\n> > >                     oid_to_hex(&oi->oid),\n> > >                     pack_basename(p));\n> > >\n> > >             return nth_packed_mtime(p, pack_pos_to_index(p, pack_pos));\n> > >         } else {\n> > >             return p->mtime; /* or oi->statp->st_mtime */\n> > >         }\n> > >     default:\n> > >         BUG(\"unknown oi->whence: %d\", oi->whence);\n> > >     }\n> > > }\n> > >\n> > > I like the above because it encapsulates the GC-specific interpretation\n> > > of an object's mtime outside of the object_info layer, while adding\n> > > information (namely statp) that is generic enough to be potentially\n> > > useful to other callers who may not be interested in the GC-specific\n> > > interpretation.\n> >\n> > This isn't achieving the goal of making the logic pluggable though, as\n> > you now have backend-specific logic outside of the backends. Also, isn't\n> > the end result basically the same as what I have proposed, except that\n> > my version _is_ fully pluggable because the logic is entirely contained\n> > in the backend?\n> \n> Yes, the end result is the same, both your patch and what I wrote here\n> implement the same GC-specific definition of an object's \"mtime\". I am\n> not following the argument about pluggability, though. The concern I\n> have above is that we are pushing domain-specific logic into the object\n> storage backend, not the other way around.\n\nTo expand on the pluggability bit: every time you add a new backend\nyou'll have to extend the above logic to understand how it represents\nthe mtime. That by itself might be doable, but let's for example\nconsider a backend that is a black box to us (like a shared library that\nmay plug in arbitrary storage logic). In that case you would not even be\nable to derive the information unless you have a generic layer that lets\nyou convey it to the caller.\n\nSo overall I agree with you that there are nuances here, and that the\nmtimep pointer _can_ be used incorrectly. But I still think that the\nconcept is generic enough across backends, and the refactored logic\nstill works as extended. I'll try to expand the docs and commit message\na bit to cover this discussion.\n\nThanks!\n\nPatrick\n"},{"id":"534637","messageId":"aXcrii58bIdLttI2@pks.im","threadId":"64809","inReplyTo":"aXO0cNaY3DWu6aQ2@nand.local","subject":"Re: [PATCH v3 11/14] odb: introduce mtime fields for object info requests","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T08:53:30Z","receivedAt":"2026-01-26T08:53:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Jan 23, 2026 at 12:48:32PM -0500, Taylor Blau wrote:\n> On Fri, Jan 23, 2026 at 10:43:07AM +0100, Patrick Steinhardt wrote:\n> > > > diff --git a/odb.c b/odb.c\n> > > > index 65f0447aa5..67decd3908 100644\n> > > > --- a/odb.c\n> > > > +++ b/odb.c\n> > > > @@ -702,6 +702,8 @@ static int do_oid_object_info_extended(struct object_database *odb,\n> > > >  \t\t\t\toidclr(oi->delta_base_oid, odb->repo->hash_algo);\n> > > >  \t\t\tif (oi->contentp)\n> > > >  \t\t\t\t*oi->contentp = xmemdupz(co->buf, co->size);\n> > > > +\t\t\tif (oi->mtimep)\n> > > > +\t\t\t\t*oi->mtimep = 0;\n> > >\n> > > Assuming that you do not change the object_info request/response\n> > > semantics, I wonder if it might make sense to zero out the entirety of\n> > > the response section as a belt-and-suspenders mechanism in case future\n> > > contributors forget to assign zero to the new fields themselves.\n> >\n> > Splitting up the request/response structure as you proposed in a\n> > previous patch could definitely help with this. I'd prefer to rather do\n> > such a bigger change as a follow-up though as it would lead to a lot of\n> > churn.\n> \n> I'm OK with pushing the larger change down the road, but I am a little\n> uncomfortable with the interim state being introduced here. Perhaps a\n> compromise here would be to have the caller supply a pointer to an\n> object_info struct, whose request fields we honor. The response fields\n> would then be written into a separate object_info struct via an\n> out-parameter.\n> \n> I don't know. I think that ^ this suggestion is kind of ugly, but I'm\n> trying to come up with something that doesn't introduce the risk I\n> described above in the interim between this patch series and the one\n> you're proposing later on.\n\nI think it's actually not _that_ ugly, and I like the additional safety\nthat it brings us.\n\nThanks!\n\nPatrick\n\ndiff --git a/object-file.c b/object-file.c\nindex bc5209f2fe..6785821c8c 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1804,7 +1804,7 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \n struct for_each_object_wrapper_data {\n \tstruct odb_source *source;\n-\tstruct object_info *oi;\n+\tconst struct object_info *request;\n \todb_for_each_object_cb cb;\n \tvoid *cb_data;\n };\n@@ -1814,21 +1814,28 @@ static int for_each_object_wrapper_cb(const struct object_id *oid,\n \t\t\t\t      void *cb_data)\n {\n \tstruct for_each_object_wrapper_data *data = cb_data;\n-\tif (data->oi &&\n-\t    read_object_info_from_path(data->source, path, oid, data->oi, 0) < 0)\n+\n+\tif (data->request) {\n+\t\tstruct object_info oi = *data->request;\n+\n+\t\tif (read_object_info_from_path(data->source, path, oid, &oi, 0) < 0)\n \t\t\treturn -1;\n-\treturn data->cb(oid, data->oi, data->cb_data);\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 int odb_source_loose_for_each_object(struct odb_source *source,\n-\t\t\t\t     struct object_info *oi,\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     unsigned flags)\n {\n \tstruct for_each_object_wrapper_data data = {\n \t\t.source = source,\n-\t\t.oi = oi,\n+\t\t.request = request,\n \t\t.cb = cb,\n \t\t.cb_data = cb_data,\n \t};\ndiff --git a/object-file.h b/object-file.h\nindex af7f57d2a1..d9979baea8 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -128,12 +128,13 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \n /*\n  * Iterate through all loose objects in the given object database source and\n- * invoke the callback function for each of them. If given, the object info\n- * will be populated with the object's data as if you had called\n- * `odb_source_loose_read_object_info()` on the object.\n+ * invoke the callback function for each of them. If an object info request is\n+ * given, then the object info will be read for every individual object and\n+ * passed to the callback as if `odb_source_loose_read_object_info()` was\n+ * called for the object.\n  */\n int odb_source_loose_for_each_object(struct odb_source *source,\n-\t\t\t\t     struct object_info *oi,\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     unsigned flags);\ndiff --git a/odb.c b/odb.c\nindex 67decd3908..9d9a3fad62 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -998,7 +998,7 @@ int odb_freshen_object(struct object_database *odb,\n }\n \n int odb_for_each_object(struct object_database *odb,\n-\t\t\tstruct object_info *oi,\n+\t\t\tconst struct object_info *request,\n \t\t\todb_for_each_object_cb cb,\n \t\t\tvoid *cb_data,\n \t\t\tunsigned flags)\n@@ -1011,12 +1011,14 @@ int odb_for_each_object(struct object_database *odb,\n \t\t\tcontinue;\n \n \t\tif (!(flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {\n-\t\t\tret = odb_source_loose_for_each_object(source, oi, cb, cb_data, flags);\n+\t\t\tret = odb_source_loose_for_each_object(source, request,\n+\t\t\t\t\t\t\t       cb, cb_data, flags);\n \t\t\tif (ret)\n \t\t\t\treturn ret;\n \t\t}\n \n-\t\tret = packfile_store_for_each_object(source->packfiles, oi, cb, cb_data, flags);\n+\t\tret = packfile_store_for_each_object(source->packfiles, request,\n+\t\t\t\t\t\t     cb, cb_data, flags);\n \t\tif (ret)\n \t\t\treturn ret;\n \t}\ndiff --git a/odb.h b/odb.h\nindex 72d69ffcb3..8ad0fcc02f 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -492,6 +492,9 @@ typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n  * Iterate through all objects contained in the object database. Note that\n  * objects may be iterated over multiple times in case they are either stored\n  * in different backends or in case they are stored in multiple sources.\n+ * If an object info request is given, then the object info will be read and\n+ * passed to the callback as if `odb_read_object_info()` was called for the\n+ * object.\n  *\n  * Returning a non-zero error code from the callback function will cause\n  * iteration to abort. The error code will be propagated.\n@@ -500,7 +503,7 @@ typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n  * an arbitrary non-zero error code returned by the callback itself.\n  */\n int odb_for_each_object(struct object_database *odb,\n-\t\t\tstruct object_info *oi,\n+\t\t\tconst struct object_info *request,\n \t\t\todb_for_each_object_cb cb,\n \t\t\tvoid *cb_data,\n \t\t\tunsigned flags);\ndiff --git a/packfile.c b/packfile.c\nindex e455150d65..57fbf51876 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2329,7 +2329,7 @@ int for_each_object_in_pack(struct packed_git *p,\n \n struct packfile_store_for_each_object_wrapper_data {\n \tstruct packfile_store *store;\n-\tstruct object_info *oi;\n+\tconst struct object_info *request;\n \todb_for_each_object_cb cb;\n \tvoid *cb_data;\n };\n@@ -2341,28 +2341,31 @@ static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n {\n \tstruct packfile_store_for_each_object_wrapper_data *data = cb_data;\n \n-\tif (data->oi) {\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, data->oi) < 0) {\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-\t}\n \n-\treturn data->cb(oid, data->oi, data->cb_data);\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 int packfile_store_for_each_object(struct packfile_store *store,\n-\t\t\t\t   struct object_info *oi,\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   unsigned flags)\n {\n \tstruct packfile_store_for_each_object_wrapper_data data = {\n \t\t.store = store,\n-\t\t.oi = oi,\n+\t\t.request = request,\n \t\t.cb = cb,\n \t\t.cb_data = cb_data,\n \t};\ndiff --git a/packfile.h b/packfile.h\nindex 8e0d2b7661..1a1b720764 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -343,14 +343,15 @@ int for_each_object_in_pack(struct packed_git *p,\n \n /*\n  * Iterate through all packed objects in the given packfile store and invoke\n- * the callback function for each of them. If given, the object info will be\n- * populated with the object's data as if you had called\n- * `packfile_store_read_object_info()` on the object.\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 packfile_store *store,\n-\t\t\t\t   struct object_info *oi,\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   unsigned flags);\n\n"},{"id":"534640","messageId":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im","subject":"[PATCH v4 00/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:16Z","receivedAt":"2026-01-26T09:51:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis patch series introduces a generic `odb_for_each_object()` function\nto iterate through objects and adapts callers to use it. The intent is\nto make iteration through objects independent of the actual storage\nbackend.\n\nThe series is structured as follows:\n\n  - Commits 1 to 2 do some cleanups for the for-each-object flags.\n\n  - Commits 3 to 7 introduce the infrastructure for\n    `odb_for_each_object()`.\n\n  - Commits 8 to 13 convert a couple of callers to use the new\n    interfaces.\n\n  - Commit 14 drops now-unused functions.\n\nThe patch series is built on top of 8745eae506 (The 17th batch,\n2026-01-11) with the following two series merged into it:\n\n  - ps/read-object-info-improvements at a282a8f163 (packfile: move MIDX\n    into packfile store, 2026-01-09).\n\n  - ps/packfile-store-in-odb-source at 12d3b58b55 (packfile: drop\n    repository parameter from `packed_object_info()`, 2026-01-12) .\n\nChanges in v4:\n  - Convert the `odb_for_each_object()` object info into a read-only\n    request parameter. Instead, we now read into a \"fresh\" object info\n    in the backends so that there can be no stale data.\n  - Fix typo in `struct object_info *io` parameter.\n  - Document what's still missing to convert `batch_each_object()` to\n    use the generic `odb_for_each_object()` function.\n  - Document ambiguity of the `mtime`.\n  - Re-add the `ODB_FOR_EACH_OBJECT_LOCAL_ONLY` flag in\n    `add_object_in_unpacked_pack()`. It shouldn't make any difference,\n    but it makes the conversion a bit more straight-forward.\n  - Link to v3: https://lore.kernel.org/r/20260121-pks-odb-for-each-object-v3-0-12c4dfd24227@pks.im\n\nChanges in v3:\n  - Fix error code propagation in last commit.\n  - Link to v2: https://lore.kernel.org/r/20260120-pks-odb-for-each-object-v2-0-d05cbfd3d6f8@pks.im\n\nChanges in v2:\n  - Clarify the comment of `odb_for_each_object()` to point out that\n    it's the callback that can abort iteration by returning a non-zero\n    error code.\n  - Document in the commit message that we don't yet convert all sites\n    to use `odb_for_each_object()`.\n  - Link to v1: https://lore.kernel.org/r/20260115-pks-odb-for-each-object-v1-0-5418a91d5d99@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (14):\n      odb: rename `FOR_EACH_OBJECT_*` flags\n      odb: fix flags parameter to be unsigned\n      object-file: extract function to read object info from path\n      object-file: introduce function to iterate through objects\n      packfile: extract function to iterate through objects of a store\n      packfile: introduce function to iterate through objects\n      odb: introduce `odb_for_each_object()`\n      builtin/fsck: refactor to use `odb_for_each_object()`\n      treewide: enumerate promisor objects via `odb_for_each_object()`\n      treewide: drop uses of `for_each_{loose,packed}_object()`\n      odb: introduce mtime fields for object info requests\n      builtin/pack-objects: use `packfile_store_for_each_object()`\n      reachable: convert to use `odb_for_each_object()`\n      odb: drop unused `for_each_{loose,packed}_object()` functions\n\n builtin/cat-file.c     |  36 ++++++++--\n builtin/fsck.c         |  57 ++++-----------\n builtin/pack-objects.c |  48 +++++++------\n commit-graph.c         |  46 +++++++++----\n object-file.c          | 125 ++++++++++++++++++++++-----------\n object-file.h          |  22 +++---\n odb.c                  |  31 +++++++++\n odb.h                  |  58 ++++++++++++++--\n packfile.c             | 184 +++++++++++++++++++++++++++++++++----------------\n packfile.h             |  19 ++++-\n reachable.c            | 129 ++++++++++------------------------\n repack-promisor.c      |   8 +--\n revision.c             |  10 ++-\n 13 files changed, 462 insertions(+), 311 deletions(-)\n\nRange-diff versus v3:\n\n 1:  a080e62c44 =  1:  e7fa63f733 odb: rename `FOR_EACH_OBJECT_*` flags\n 2:  7980f241a9 =  2:  b462808c07 odb: fix flags parameter to be unsigned\n 3:  14b9251711 =  3:  00d77e9e45 object-file: extract function to read object info from path\n 4:  93af71f3c7 !  4:  b9899bd1cb object-file: introduce function to iterate through objects\n    @@ object-file.c: int for_each_loose_object(struct object_database *odb,\n      \n     +struct for_each_object_wrapper_data {\n     +\tstruct odb_source *source;\n    -+\tstruct object_info *oi;\n    ++\tconst struct object_info *request;\n     +\todb_for_each_object_cb cb;\n     +\tvoid *cb_data;\n     +};\n    @@ object-file.c: int for_each_loose_object(struct object_database *odb,\n     +\t\t\t\t      void *cb_data)\n     +{\n     +\tstruct for_each_object_wrapper_data *data = cb_data;\n    -+\tif (data->oi &&\n    -+\t    read_object_info_from_path(data->source, path, oid, data->oi, 0) < 0)\n    ++\n    ++\tif (data->request) {\n    ++\t\tstruct object_info oi = *data->request;\n    ++\n    ++\t\tif (read_object_info_from_path(data->source, path, oid, &oi, 0) < 0)\n     +\t\t\treturn -1;\n    -+\treturn data->cb(oid, data->oi, data->cb_data);\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     +int odb_source_loose_for_each_object(struct odb_source *source,\n    -+\t\t\t\t     struct object_info *oi,\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     unsigned flags)\n     +{\n     +\tstruct for_each_object_wrapper_data data = {\n     +\t\t.source = source,\n    -+\t\t.oi = oi,\n    ++\t\t.request = request,\n     +\t\t.cb = cb,\n     +\t\t.cb_data = cb_data,\n     +\t};\n    @@ object-file.h: int for_each_loose_object(struct object_database *odb,\n     + * `odb_source_loose_read_object_info()` on the object.\n     + */\n     +int odb_source_loose_for_each_object(struct odb_source *source,\n    -+\t\t\t\t     struct object_info *oi,\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     unsigned flags);\n 5:  ad0a28e2bb =  5:  03fe7d5b3b packfile: extract function to iterate through objects of a store\n 6:  e87126ddee !  6:  4648a18a9b packfile: introduce function to iterate through objects\n    @@ Commit message\n         packfile: introduce function to iterate through objects\n     \n         Introduce a new function `packfile_store_for_each_object()`. This\n    -    function is the equivalent to `odb_source_loose_for_each_object()` in\n    +    function is equivalent to `odb_source_loose_for_each_object()`, except\n         that it:\n     \n    -      - Works on a single packfile store and thus per object source.\n    +      - Works on a single packfile store instead of working on the object\n    +        database level. Consequently, it will only yield packed objects of a\n    +        single object database source.\n     \n           - Passes a `struct object_info` to the callback function.\n     \n    @@ packfile.c: int for_each_packed_object(struct repository *repo, each_packed_obje\n      \n     +struct packfile_store_for_each_object_wrapper_data {\n     +\tstruct packfile_store *store;\n    -+\tstruct object_info *oi;\n    ++\tconst struct object_info *request;\n     +\todb_for_each_object_cb cb;\n     +\tvoid *cb_data;\n     +};\n    @@ packfile.c: int for_each_packed_object(struct repository *repo, each_packed_obje\n     +{\n     +\tstruct packfile_store_for_each_object_wrapper_data *data = cb_data;\n     +\n    -+\tif (data->oi) {\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(pack, offset, data->oi) < 0) {\n    ++\t\tif (packed_object_info(pack, offset, &oi) < 0) {\n     +\t\t\tmark_bad_packed_object(pack, oid);\n     +\t\t\treturn -1;\n     +\t\t}\n    -+\t}\n     +\n    -+\treturn data->cb(oid, data->oi, data->cb_data);\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     +int packfile_store_for_each_object(struct packfile_store *store,\n    -+\t\t\t\t   struct object_info *oi,\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   unsigned flags)\n     +{\n     +\tstruct packfile_store_for_each_object_wrapper_data data = {\n     +\t\t.store = store,\n    -+\t\t.oi = oi,\n    ++\t\t.request = request,\n     +\t\t.cb = cb,\n     +\t\t.cb_data = cb_data,\n     +\t};\n    @@ packfile.h: int for_each_object_in_pack(struct packed_git *p,\n      \n     +/*\n     + * Iterate through all packed objects in the given packfile store and invoke\n    -+ * the callback function for each of them. If given, the object info will be\n    -+ * populated with the object's data as if you had called\n    -+ * `packfile_store_read_object_info()` on the object.\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 packfile_store *store,\n    -+\t\t\t\t   struct object_info *oi,\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   unsigned flags);\n 7:  f437198d7a !  7:  3ec85ee10f odb: introduce `odb_for_each_object()`\n    @@ Commit message\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    + ## object-file.h ##\n    +@@ object-file.h: int for_each_loose_object(struct object_database *odb,\n    + \n    + /*\n    +  * Iterate through all loose objects in the given object database source and\n    +- * invoke the callback function for each of them. If given, the object info\n    +- * will be populated with the object's data as if you had called\n    +- * `odb_source_loose_read_object_info()` on the object.\n    ++ * invoke the callback function for each of them. If an object info request is\n    ++ * given, then the object info will be read for every individual object and\n    ++ * passed to the callback as if `odb_source_loose_read_object_info()` was\n    ++ * called for the object.\n    +  */\n    + int odb_source_loose_for_each_object(struct odb_source *source,\n    + \t\t\t\t     const struct object_info *request,\n    +\n      ## odb.c ##\n     @@ odb.c: int odb_freshen_object(struct object_database *odb,\n      \treturn 0;\n      }\n      \n     +int odb_for_each_object(struct object_database *odb,\n    -+\t\t\tstruct object_info *oi,\n    ++\t\t\tconst struct object_info *request,\n     +\t\t\todb_for_each_object_cb cb,\n     +\t\t\tvoid *cb_data,\n     +\t\t\tunsigned flags)\n    @@ odb.c: int odb_freshen_object(struct object_database *odb,\n     +\t\t\tcontinue;\n     +\n     +\t\tif (!(flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {\n    -+\t\t\tret = odb_source_loose_for_each_object(source, oi, cb, cb_data, flags);\n    ++\t\t\tret = odb_source_loose_for_each_object(source, request,\n    ++\t\t\t\t\t\t\t       cb, cb_data, flags);\n     +\t\t\tif (ret)\n     +\t\t\t\treturn ret;\n     +\t\t}\n     +\n    -+\t\tret = packfile_store_for_each_object(source->packfiles, oi, cb, cb_data, flags);\n    ++\t\tret = packfile_store_for_each_object(source->packfiles, request,\n    ++\t\t\t\t\t\t     cb, cb_data, flags);\n     +\t\tif (ret)\n     +\t\t\treturn ret;\n     +\t}\n    @@ odb.h: typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n     + * Iterate through all objects contained in the object database. Note that\n     + * objects may be iterated over multiple times in case they are either stored\n     + * in different backends or in case they are stored in multiple sources.\n    ++ * If an object info request is given, then the object info will be read and\n    ++ * passed to the callback as if `odb_read_object_info()` was called for the\n    ++ * object.\n     + *\n     + * Returning a non-zero error code from the callback function will cause\n     + * iteration to abort. The error code will be propagated.\n    @@ odb.h: typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n     + * an arbitrary non-zero error code returned by the callback itself.\n     + */\n     +int odb_for_each_object(struct object_database *odb,\n    -+\t\t\tstruct object_info *oi,\n    ++\t\t\tconst struct object_info *request,\n     +\t\t\todb_for_each_object_cb cb,\n     +\t\t\tvoid *cb_data,\n     +\t\t\tunsigned flags);\n 8:  75c0e7fb54 !  8:  069bcb600b builtin/fsck: refactor to use `odb_for_each_object()`\n    @@ Commit message\n     \n         Refactor these callsites accordingly.\n     \n    +    Note that `odb_for_each_object()` may iterate over the same object\n    +    multiple times, for example when it exists both in packed and loose\n    +    format. But this has already been the case beforehand, so this does not\n    +    result in a change in behaviour.\n    +\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## builtin/fsck.c ##\n    @@ builtin/fsck.c: static int mark_used(struct object *obj, enum object_type type U\n      \n     -static void mark_unreachable_referents(const struct object_id *oid)\n     +static int mark_unreachable_referents(const struct object_id *oid,\n    -+\t\t\t\t      struct object_info *io UNUSED,\n    ++\t\t\t\t      struct object_info *oi UNUSED,\n     +\t\t\t\t      void *data UNUSED)\n      {\n      \tstruct fsck_options options = FSCK_OPTIONS_DEFAULT;\n 9:  5a1c71af5f =  9:  cb472da9d5 treewide: enumerate promisor objects via `odb_for_each_object()`\n10:  b6dcd01b19 ! 10:  505243613c treewide: drop uses of `for_each_{loose,packed}_object()`\n    @@ builtin/cat-file.c: static void batch_each_object(struct batch_options *opt,\n     +\tstruct odb_source *source;\n      \n     -\tfor_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);\n    ++\t/*\n    ++\t * TODO: we still need to tap into implementation details of the object\n    ++\t * database sources. Ideally, we should extend `odb_for_each_object()`\n    ++\t * to handle object filters itself so that we can move the filtering\n    ++\t * logic into the individual sources.\n    ++\t */\n     +\todb_prepare_alternates(the_repository->objects);\n     +\tfor (source = the_repository->objects->sources; source; source = source->next) {\n     +\t\tint ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi,\n11:  92a8225bca ! 11:  3dc547bb9d odb: introduce mtime fields for object info requests\n    @@ Commit message\n         to request an object's mtime. This new field will be used in a\n         subsequent commit.\n     \n    +    Note that the concept of \"mtime\" is ambiguous: given an object, it may\n    +    be stored multiple times in the object database, and each of these\n    +    instances may have a different mtime. Disambiguating these mtimes is\n    +    nothing that can happen on the generic ODB layer: the caller may search\n    +    for the oldest object, the newest object, or even the relation of object\n    +    mtimes depending on the specific source they are located in. As such, it\n    +    is the responsibility of the caller to disambiguate mtimes.\n    +\n    +    A consequence of this is that it's most likely incorrect to look up the\n    +    mtime via `odb_read_object_info()`, as this interface does not give us\n    +    enough information to disambiguate the mtime. Document this accordingly\n    +    and tell users to use `odb_for_each_object()` instead.\n    +\n    +    Even with this gotcha though it's sensible to have this request as part\n    +    of the object info, as the mtime is a property of the object storage\n    +    format. If we for example had a \"black-box\" storage backend, we'd still\n    +    need to be able to query it for the mtime info in a generic way.\n    +\n    +    We could introduce a safety mechanism that for example calls `BUG()` in\n    +    case we look up the mtime outside of `odb_for_each_object()`. But that\n    +    feels somewhat heavy-handed.\n    +\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## object-file.c ##\n    @@ odb.c: static int do_oid_object_info_extended(struct object_database *odb,\n     \n      ## odb.h ##\n     @@ odb.h: struct object_info {\n    - \toff_t *disk_sizep;\n      \tstruct object_id *delta_base_oid;\n      \tvoid **contentp;\n    -+\ttime_t *mtimep;\n      \n    ++\t/*\n    ++\t * The time the given looked-up object has been last modified.\n    ++\t *\n    ++\t * Note: the mtime may be ambiguous in case the object exists multiple\n    ++\t * times in the object database. It is thus _not_ recommended to use\n    ++\t * this field outside of contexts where you would read every instance\n    ++\t * of the object, like for example with `odb_for_each_object()`. As it\n    ++\t * is impossible to say at the ODB level what the intent of the caller\n    ++\t * is (e.g. whether to find the oldest or newest object), it is the\n    ++\t * responsibility of the caller to disambiguate the mtimes.\n    ++\t */\n    ++\ttime_t *mtimep;\n    ++\n      \t/* Response */\n      \tenum {\n    + \t\tOI_CACHED,\n     \n      ## packfile.c ##\n     @@ packfile.c: static void add_delta_base_cache(struct packed_git *p, off_t base_offset,\n    @@ packfile.c: int packed_object_info(struct packed_git *p,\n     +\t\t\tuint32_t index_pos;\n     +\n     +\t\t\tif (load_pack_mtimes(p) < 0)\n    -+\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n    ++\t\t\t\tdie(_(\"could not load .mtimes for cruft pack '%s'\"),\n    ++\t\t\t\t    pack_basename(p));\n     +\n     +\t\t\tif (maybe_index_pos)\n     +\t\t\t\tindex_pos = *maybe_index_pos;\n    @@ packfile.c: int packed_object_info(struct packed_git *p,\n      \t\t\t\t    struct pack_window **w_curs,\n      \t\t\t\t    off_t curpos,\n     @@ packfile.c: static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n    - \tif (data->oi) {\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(pack, offset, data->oi) < 0) {\n    +-\t\tif (packed_object_info(pack, offset, &oi) < 0) {\n     +\t\tif (packed_object_info_with_index_pos(pack, offset,\n    -+\t\t\t\t\t\t      &index_pos, data->oi) < 0) {\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}\n12:  658cbf8f12 ! 12:  0047a40d16 builtin/pack-objects: use `packfile_store_for_each_object()`\n    @@ builtin/pack-objects.c: static int add_object_in_unpacked_pack(const struct obje\n     +\t\tif (packfile_store_for_each_object(source->packfiles, &oi,\n     +\t\t\t\t\t\t   add_object_in_unpacked_pack, NULL,\n     +\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n    ++\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n     +\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n     +\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n     +\t\t\tdie(_(\"cannot open pack index\"));\n13:  a28907a4b6 = 13:  c3bde2e822 reachable: convert to use `odb_for_each_object()`\n14:  7d235b6529 ! 14:  bf2f3c39a6 odb: drop unused `for_each_{loose,packed}_object()` functions\n    @@ object-file.c: int for_each_loose_file_in_source(struct odb_source *source,\n     -\n      struct for_each_object_wrapper_data {\n      \tstruct odb_source *source;\n    - \tstruct object_info *oi;\n    + \tconst struct object_info *request;\n     \n      ## object-file.h ##\n     @@ object-file.h: int for_each_loose_file_in_source(struct odb_source *source,\n    @@ object-file.h: int for_each_loose_file_in_source(struct odb_source *source,\n     -\n      /*\n       * Iterate through all loose objects in the given object database source and\n    -  * invoke the callback function for each of them. If given, the object info\n    +  * invoke the callback function for each of them. If an object info request is\n     \n      ## packfile.c ##\n     @@ packfile.c: int for_each_object_in_pack(struct packed_git *p,\n    @@ packfile.c: int for_each_object_in_pack(struct packed_git *p,\n     -\n      struct packfile_store_for_each_object_wrapper_data {\n      \tstruct packfile_store *store;\n    - \tstruct object_info *oi;\n    + \tconst struct object_info *request;\n     @@ packfile.c: int packfile_store_for_each_object(struct packfile_store *store,\n      \t\t.cb = cb,\n      \t\t.cb_data = cb_data,\n\n---\nbase-commit: 1ff0e42d332523a11cc3d61b8d8463db5f9f14e8\nchange-id: 20260115-pks-odb-for-each-object-60b78cde09fd\n\n"},{"id":"534641","messageId":"20260126-pks-odb-for-each-object-v4-1-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 01/14] odb: rename `FOR_EACH_OBJECT_*` flags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:17Z","receivedAt":"2026-01-26T09:51:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename the `FOR_EACH_OBJECT_*` flags to have an `ODB_` prefix. This\nprepares us for a new upcoming `odb_for_each_object()` function and\nensures that both the function and its flags have the same prefix.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c     |  2 +-\n builtin/pack-objects.c | 10 +++++-----\n commit-graph.c         |  4 ++--\n object-file.c          |  4 ++--\n object-file.h          |  2 +-\n odb.h                  | 13 +++++++------\n packfile.c             | 20 ++++++++++----------\n packfile.h             |  4 ++--\n reachable.c            |  8 ++++----\n repack-promisor.c      |  2 +-\n revision.c             |  2 +-\n 11 files changed, 36 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 2ad712e9f8..6964a5a52c 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -922,7 +922,7 @@ static int batch_objects(struct batch_options *opt)\n \t\t\tcb.seen = &seen;\n \n \t\t\tbatch_each_object(opt, batch_unordered_object,\n-\t\t\t\t\t  FOR_EACH_OBJECT_PACK_ORDER, &cb);\n+\t\t\t\t\t  ODB_FOR_EACH_OBJECT_PACK_ORDER, &cb);\n \n \t\t\toidset_clear(&seen);\n \t\t} else {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 6ee31d48c9..74317051fd 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3912,7 +3912,7 @@ static void read_packs_list_from_stdin(struct rev_info *revs)\n \t\tfor_each_object_in_pack(p,\n \t\t\t\t\tadd_object_entry_from_pack,\n \t\t\t\t\trevs,\n-\t\t\t\t\tFOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\tODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t}\n \n \tstrbuf_release(&buf);\n@@ -4344,10 +4344,10 @@ static void add_objects_in_unpacked_packs(void)\n \tif (for_each_packed_object(to_pack.repo,\n \t\t\t\t   add_object_in_unpacked_pack,\n \t\t\t\t   NULL,\n-\t\t\t\t   FOR_EACH_OBJECT_PACK_ORDER |\n-\t\t\t\t   FOR_EACH_OBJECT_LOCAL_ONLY |\n-\t\t\t\t   FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n-\t\t\t\t   FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n+\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n \t\tdie(_(\"cannot open pack index\"));\n }\n \ndiff --git a/commit-graph.c b/commit-graph.c\nindex 6b1f02e179..7f1145a082 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1927,7 +1927,7 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,\n \t\t\tgoto cleanup;\n \t\t}\n \t\tfor_each_object_in_pack(p, add_packed_commits, ctx,\n-\t\t\t\t\tFOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\tODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\tclose_pack(p);\n \t\tfree(p);\n \t}\n@@ -1965,7 +1965,7 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n \t\t\t_(\"Finding commits for commit graph among packed objects\"),\n \t\t\tctx->approx_nr_objects);\n \tfor_each_packed_object(ctx->r, add_packed_commits, ctx,\n-\t\t\t       FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \tif (ctx->progress_done < ctx->approx_nr_objects)\n \t\tdisplay_progress(ctx->progress, ctx->approx_nr_objects);\n \tstop_progress(&ctx->progress);\ndiff --git a/object-file.c b/object-file.c\nindex e7e4c3348f..64e9e239dc 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1789,7 +1789,7 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \n int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn cb, void *data,\n-\t\t\t  enum for_each_object_flags flags)\n+\t\t\t  enum odb_for_each_object_flags flags)\n {\n \tstruct odb_source *source;\n \n@@ -1800,7 +1800,7 @@ int for_each_loose_object(struct object_database *odb,\n \t\tif (r)\n \t\t\treturn r;\n \n-\t\tif (flags & FOR_EACH_OBJECT_LOCAL_ONLY)\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY)\n \t\t\tbreak;\n \t}\n \ndiff --git a/object-file.h b/object-file.h\nindex 1229d5f675..42bb50e10c 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -134,7 +134,7 @@ int for_each_loose_file_in_source(struct odb_source *source,\n  */\n int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn, void *,\n-\t\t\t  enum for_each_object_flags flags);\n+\t\t\t  enum odb_for_each_object_flags flags);\n \n \n /**\ndiff --git a/odb.h b/odb.h\nindex bab07755f4..74503addf1 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -442,24 +442,25 @@ static inline void obj_read_unlock(void)\n \tif(obj_read_use_lock)\n \t\tpthread_mutex_unlock(&obj_read_mutex);\n }\n+\n /* Flags for for_each_*_object(). */\n-enum for_each_object_flags {\n+enum odb_for_each_object_flags {\n \t/* Iterate only over local objects, not alternates. */\n-\tFOR_EACH_OBJECT_LOCAL_ONLY = (1<<0),\n+\tODB_FOR_EACH_OBJECT_LOCAL_ONLY = (1<<0),\n \n \t/* Only iterate over packs obtained from the promisor remote. */\n-\tFOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1),\n+\tODB_FOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1),\n \n \t/*\n \t * Visit objects within a pack in packfile order rather than .idx order\n \t */\n-\tFOR_EACH_OBJECT_PACK_ORDER = (1<<2),\n+\tODB_FOR_EACH_OBJECT_PACK_ORDER = (1<<2),\n \n \t/* Only iterate over packs that are not marked as kept in-core. */\n-\tFOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),\n+\tODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),\n \n \t/* Only iterate over packs that do not have .keep files. */\n-\tFOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n+\tODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n };\n \n enum {\ndiff --git a/packfile.c b/packfile.c\nindex 402c3b5dc7..b65f0b43f1 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2259,12 +2259,12 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn cb, void *data,\n-\t\t\t    enum for_each_object_flags flags)\n+\t\t\t    enum odb_for_each_object_flags flags)\n {\n \tuint32_t i;\n \tint r = 0;\n \n-\tif (flags & FOR_EACH_OBJECT_PACK_ORDER) {\n+\tif (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER) {\n \t\tif (load_pack_revindex(p->repo, p))\n \t\t\treturn -1;\n \t}\n@@ -2285,7 +2285,7 @@ int for_each_object_in_pack(struct packed_git *p,\n \t\t *   - in pack-order, it is pack position, which we must\n \t\t *     convert to an index position in order to get the oid.\n \t\t */\n-\t\tif (flags & FOR_EACH_OBJECT_PACK_ORDER)\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER)\n \t\t\tindex_pos = pack_pos_to_index(p, i);\n \t\telse\n \t\t\tindex_pos = i;\n@@ -2302,7 +2302,7 @@ int for_each_object_in_pack(struct packed_git *p,\n }\n \n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum for_each_object_flags flags)\n+\t\t\t   void *data, enum odb_for_each_object_flags flags)\n {\n \tstruct odb_source *source;\n \tint r = 0;\n@@ -2318,15 +2318,15 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\tfor (e = packfile_store_get_packs(source->packfiles); e; e = e->next) {\n \t\t\tstruct packed_git *p = e->pack;\n \n-\t\t\tif ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n \t\t\t    !p->pack_promisor)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n \t\t\t    p->pack_keep_in_core)\n \t\t\t\tcontinue;\n-\t\t\tif ((flags & FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n+\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n \t\t\t    p->pack_keep)\n \t\t\t\tcontinue;\n \t\t\tif (open_pack_index(p)) {\n@@ -2413,8 +2413,8 @@ int is_promisor_object(struct repository *r, const struct object_id *oid)\n \t\tif (repo_has_promisor_remote(r)) {\n \t\t\tfor_each_packed_object(r, add_promisor_object,\n \t\t\t\t\t       &promisor_objects,\n-\t\t\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY |\n-\t\t\t\t\t       FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY |\n+\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\t}\n \t\tpromisor_objects_prepared = 1;\n \t}\ndiff --git a/packfile.h b/packfile.h\nindex acc5c55ad5..15551258bd 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -339,9 +339,9 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n \t\t\t\t  void *data);\n 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 for_each_object_flags flags);\n+\t\t\t    enum odb_for_each_object_flags flags);\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum for_each_object_flags flags);\n+\t\t\t   void *data, enum odb_for_each_object_flags flags);\n \n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\ndiff --git a/reachable.c b/reachable.c\nindex 4b532039d5..82676b2668 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -307,7 +307,7 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \t\t\t\t\t   int ignore_in_core_kept_packs)\n {\n \tstruct recent_data data;\n-\tenum for_each_object_flags flags;\n+\tenum odb_for_each_object_flags flags;\n \tint r;\n \n \tdata.revs = revs;\n@@ -319,13 +319,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \tdata.extra_recent_oids_loaded = 0;\n \n \tr = for_each_loose_object(the_repository->objects, add_recent_loose, &data,\n-\t\t\t\t  FOR_EACH_OBJECT_LOCAL_ONLY);\n+\t\t\t\t  ODB_FOR_EACH_OBJECT_LOCAL_ONLY);\n \tif (r)\n \t\tgoto done;\n \n-\tflags = FOR_EACH_OBJECT_LOCAL_ONLY | FOR_EACH_OBJECT_PACK_ORDER;\n+\tflags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER;\n \tif (ignore_in_core_kept_packs)\n-\t\tflags |= FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n+\t\tflags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n \n \tr = for_each_packed_object(revs->repo, add_recent_packed, &data, flags);\n \ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex ee6e0669f6..45c330b9a5 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -56,7 +56,7 @@ void repack_promisor_objects(struct repository *repo,\n \tctx.cmd = &cmd;\n \tctx.algop = repo->hash_algo;\n \tfor_each_packed_object(repo, write_oid, &ctx,\n-\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n \t\t/* No packed objects; cmd was never started */\ndiff --git a/revision.c b/revision.c\nindex b65a763770..5aadf46dac 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3938,7 +3938,7 @@ int prepare_revision_walk(struct rev_info *revs)\n \n \tif (revs->exclude_promisor_objects) {\n \t\tfor_each_packed_object(revs->repo, mark_uninteresting, revs,\n-\t\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \t}\n \n \tif (!revs->reflog_info)\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534642","messageId":"20260126-pks-odb-for-each-object-v4-2-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 02/14] odb: fix flags parameter to be unsigned","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:18Z","receivedAt":"2026-01-26T09:51:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `flags` parameter accepted by various `for_each_object()` functions\nis a bitfield of multiple flags. Such parameters are typically unsigned\nin the Git codebase, but we use `enum odb_for_each_object_flags` in\nsome places.\n\nAdapt these function signatures to use the correct type.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 3 ++-\n object-file.h | 3 ++-\n packfile.c    | 4 ++--\n packfile.h    | 4 ++--\n 4 files changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 64e9e239dc..8fa461dd59 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -414,7 +414,8 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)\n \n int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\t\t      const struct object_id *oid,\n-\t\t\t\t      struct object_info *oi, int flags)\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags)\n {\n \tint ret;\n \tint fd;\ndiff --git a/object-file.h b/object-file.h\nindex 42bb50e10c..2acf19fb91 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -47,7 +47,8 @@ void odb_source_loose_reprepare(struct odb_source *source);\n \n int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\t\t      const struct object_id *oid,\n-\t\t\t\t      struct object_info *oi, int flags);\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags);\n \n int odb_source_loose_read_object_stream(struct odb_read_stream **out,\n \t\t\t\t\tstruct odb_source *source,\ndiff --git a/packfile.c b/packfile.c\nindex b65f0b43f1..79fe64a25b 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2259,7 +2259,7 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid,\n \n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn cb, void *data,\n-\t\t\t    enum odb_for_each_object_flags flags)\n+\t\t\t    unsigned flags)\n {\n \tuint32_t i;\n \tint r = 0;\n@@ -2302,7 +2302,7 @@ int for_each_object_in_pack(struct packed_git *p,\n }\n \n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum odb_for_each_object_flags flags)\n+\t\t\t   void *data, unsigned flags)\n {\n \tstruct odb_source *source;\n \tint r = 0;\ndiff --git a/packfile.h b/packfile.h\nindex 15551258bd..447c44c4a7 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -339,9 +339,9 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n \t\t\t\t  void *data);\n 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+\t\t\t    unsigned flags);\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, enum odb_for_each_object_flags flags);\n+\t\t\t   void *data, unsigned flags);\n \n /* A hook to report invalid files in pack directory */\n #define PACKDIR_FILE_PACK 1\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534643","messageId":"20260126-pks-odb-for-each-object-v4-3-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 03/14] object-file: extract function to read object info from path","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:19Z","receivedAt":"2026-01-26T09:51:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Extract a new function that allows us to read object info for a specific\nloose object via a user-supplied path. This function will be used in a\nsubsequent commit.\n\nNote that this also allows us to drop `stat_loose_object()`, which is\na simple wrapper around `odb_loose_path()` plus lstat(3p).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 39 ++++++++++++++++-----------------------\n 1 file changed, 16 insertions(+), 23 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 8fa461dd59..a651129426 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -165,30 +165,13 @@ int stream_object_signature(struct repository *r, const struct object_id *oid)\n }\n \n /*\n- * Find \"oid\" as a loose object in given source.\n- * Returns 0 on success, negative on failure.\n+ * Find \"oid\" as a loose object in given source, open the object and return its\n+ * file descriptor. Returns the file descriptor on success, negative on failure.\n  *\n  * The \"path\" out-parameter will give the path of the object we found (if any).\n  * Note that it may point to static storage and is only valid until another\n  * call to stat_loose_object().\n  */\n-static int stat_loose_object(struct odb_source_loose *loose,\n-\t\t\t     const struct object_id *oid,\n-\t\t\t     struct stat *st, const char **path)\n-{\n-\tstatic struct strbuf buf = STRBUF_INIT;\n-\n-\t*path = odb_loose_path(loose->source, &buf, oid);\n-\tif (!lstat(*path, st))\n-\t\treturn 0;\n-\n-\treturn -1;\n-}\n-\n-/*\n- * Like stat_loose_object(), but actually open the object and return the\n- * descriptor. See the caveats on the \"path\" parameter above.\n- */\n static int open_loose_object(struct odb_source_loose *loose,\n \t\t\t     const struct object_id *oid, const char **path)\n {\n@@ -412,7 +395,8 @@ static int parse_loose_header(const char *hdr, struct object_info *oi)\n \treturn 0;\n }\n \n-int odb_source_loose_read_object_info(struct odb_source *source,\n+static int read_object_info_from_path(struct odb_source *source,\n+\t\t\t\t      const char *path,\n \t\t\t\t      const struct object_id *oid,\n \t\t\t\t      struct object_info *oi,\n \t\t\t\t      unsigned flags)\n@@ -420,7 +404,6 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \tint ret;\n \tint fd;\n \tunsigned long mapsize;\n-\tconst char *path;\n \tvoid *map = NULL;\n \tgit_zstream stream, *stream_to_end = NULL;\n \tchar hdr[MAX_HEADER_LEN];\n@@ -443,7 +426,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (stat_loose_object(source->loose, oid, &st, &path) < 0) {\n+\t\tif (lstat(path, &st) < 0) {\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n@@ -455,7 +438,7 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \t\tgoto out;\n \t}\n \n-\tfd = open_loose_object(source->loose, oid, &path);\n+\tfd = git_open(path);\n \tif (fd < 0) {\n \t\tif (errno != ENOENT)\n \t\t\terror_errno(_(\"unable to open loose object %s\"), oid_to_hex(oid));\n@@ -534,6 +517,16 @@ int odb_source_loose_read_object_info(struct odb_source *source,\n \treturn ret;\n }\n \n+int odb_source_loose_read_object_info(struct odb_source *source,\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      unsigned flags)\n+{\n+\tstatic struct strbuf buf = STRBUF_INIT;\n+\todb_loose_path(source, &buf, oid);\n+\treturn read_object_info_from_path(source, buf.buf, oid, oi, flags);\n+}\n+\n static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c,\n \t\t\t     const void *buf, unsigned long len,\n \t\t\t     struct object_id *oid,\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534644","messageId":"20260126-pks-odb-for-each-object-v4-4-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 04/14] object-file: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:20Z","receivedAt":"2026-01-26T09:51:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have multiple divergent interfaces to iterate through objects of a\nspecific backend:\n\n  - `for_each_loose_object()` yields all loose objects.\n\n  - `for_each_packed_object()` (somewhat obviously) yields all packed\n    objects.\n\nThese functions have different function signatures, which makes it hard\nto create a common abstraction layer that covers both of these.\n\nIntroduce a new function `odb_source_loose_for_each_object()` to plug\nthis gap. This function doesn't take any data specific to loose objects,\nbut instead it accepts a `struct object_info` that will be populated the\nexact same as if `odb_source_loose_read_object()` was called.\n\nThe benefit of this new interface is that we can continue to pass\nbackend-specific data, as `struct object_info` contains a union for\nthese exact use cases. This will allow us to unify how we iterate\nthrough objects across both loose and packed objects in a subsequent\ncommit.\n\nThe `for_each_loose_object()` function continues to exist for now, but\nit will be removed at the end of this patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++\n object-file.h | 11 +++++++++++\n odb.h         | 12 ++++++++++++\n 3 files changed, 71 insertions(+)\n\ndiff --git a/object-file.c b/object-file.c\nindex a651129426..ef2c7618c1 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1801,6 +1801,54 @@ int for_each_loose_object(struct object_database *odb,\n \treturn 0;\n }\n \n+struct for_each_object_wrapper_data {\n+\tstruct odb_source *source;\n+\tconst struct object_info *request;\n+\todb_for_each_object_cb cb;\n+\tvoid *cb_data;\n+};\n+\n+static int for_each_object_wrapper_cb(const struct object_id *oid,\n+\t\t\t\t      const char *path,\n+\t\t\t\t      void *cb_data)\n+{\n+\tstruct for_each_object_wrapper_data *data = cb_data;\n+\n+\tif (data->request) {\n+\t\tstruct object_info oi = *data->request;\n+\n+\t\tif (read_object_info_from_path(data->source, path, oid, &oi, 0) < 0)\n+\t\t\treturn -1;\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+int odb_source_loose_for_each_object(struct odb_source *source,\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     unsigned flags)\n+{\n+\tstruct for_each_object_wrapper_data data = {\n+\t\t.source = source,\n+\t\t.request = request,\n+\t\t.cb = cb,\n+\t\t.cb_data = cb_data,\n+\t};\n+\n+\t/* There are no loose promisor objects, so we can return immediately. */\n+\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY))\n+\t\treturn 0;\n+\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local)\n+\t\treturn 0;\n+\n+\treturn for_each_loose_file_in_source(source, for_each_object_wrapper_cb,\n+\t\t\t\t\t     NULL, NULL, &data);\n+}\n+\n static int append_loose_object(const struct object_id *oid,\n \t\t\t       const char *path UNUSED,\n \t\t\t       void *data)\ndiff --git a/object-file.h b/object-file.h\nindex 2acf19fb91..5b9641cd89 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -137,6 +137,17 @@ int for_each_loose_object(struct object_database *odb,\n \t\t\t  each_loose_object_fn, void *,\n \t\t\t  enum odb_for_each_object_flags flags);\n \n+/*\n+ * Iterate through all loose objects in the given object database source and\n+ * invoke the callback function for each of them. If given, the object info\n+ * will be populated with the object's data as if you had called\n+ * `odb_source_loose_read_object_info()` on the object.\n+ */\n+int odb_source_loose_for_each_object(struct odb_source *source,\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     unsigned flags);\n \n /**\n  * format_object_header() is a thin wrapper around s xsnprintf() that\ndiff --git a/odb.h b/odb.h\nindex 74503addf1..f97f249580 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -463,6 +463,18 @@ enum odb_for_each_object_flags {\n \tODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n };\n \n+/*\n+ * A callback function that can be used to iterate through objects. If given,\n+ * the optional `oi` parameter will be populated the same as if you would call\n+ * `odb_read_object_info()`.\n+ *\n+ * Returning a non-zero error code will cause iteration to abort. The error\n+ * code will be propagated.\n+ */\n+typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n+\t\t\t\t      struct object_info *oi,\n+\t\t\t\t      void *cb_data);\n+\n enum {\n \t/*\n \t * By default, `odb_write_object()` does not actually write anything\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534645","messageId":"20260126-pks-odb-for-each-object-v4-5-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 05/14] packfile: extract function to iterate through objects of a store","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:21Z","receivedAt":"2026-01-26T09:51:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In the next commit we're about to introduce a new function that knows to\niterate through objects of a given packfile store. Same as with the\nequivalent function for loose objects, this new function will also be\nagnostic of backends by using a `struct object_info`.\n\nPrepare for this by extracting a new shared function to iterate through\na single packfile store.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 78 ++++++++++++++++++++++++++++++++++++--------------------------\n 1 file changed, 45 insertions(+), 33 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex 79fe64a25b..d15a2ce12b 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2301,51 +2301,63 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags)\n+static int packfile_store_for_each_object_internal(struct packfile_store *store,\n+\t\t\t\t\t\t   each_packed_object_fn cb,\n+\t\t\t\t\t\t   void *data,\n+\t\t\t\t\t\t   unsigned flags,\n+\t\t\t\t\t\t   int *pack_errors)\n {\n-\tstruct odb_source *source;\n-\tint r = 0;\n-\tint pack_errors = 0;\n+\tstruct packfile_list_entry *e;\n+\tint ret = 0;\n \n-\todb_prepare_alternates(repo->objects);\n+\tstore->skip_mru_updates = true;\n \n-\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\tstruct packfile_list_entry *e;\n+\tfor (e = packfile_store_get_packs(store); e; e = e->next) {\n+\t\tstruct packed_git *p = e->pack;\n \n-\t\tsource->packfiles->skip_mru_updates = true;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((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 ((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\t*pack_errors = 1;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\tfor (e = packfile_store_get_packs(source->packfiles); e; e = e->next) {\n-\t\t\tstruct packed_git *p = e->pack;\n+\t\tret = for_each_object_in_pack(p, cb, data, flags);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n \n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t\t    !p->pack_promisor)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&\n-\t\t\t    p->pack_keep_in_core)\n-\t\t\t\tcontinue;\n-\t\t\tif ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&\n-\t\t\t    p->pack_keep)\n-\t\t\t\tcontinue;\n-\t\t\tif (open_pack_index(p)) {\n-\t\t\t\tpack_errors = 1;\n-\t\t\t\tcontinue;\n-\t\t\t}\n+\tstore->skip_mru_updates = false;\n \n-\t\t\tr = for_each_object_in_pack(p, cb, data, flags);\n-\t\t\tif (r)\n-\t\t\t\tbreak;\n-\t\t}\n+\treturn ret;\n+}\n \n-\t\tsource->packfiles->skip_mru_updates = false;\n+int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n+\t\t\t   void *data, unsigned flags)\n+{\n+\tstruct odb_source *source;\n+\tint pack_errors = 0;\n+\tint ret = 0;\n \n-\t\tif (r)\n+\todb_prepare_alternates(repo->objects);\n+\n+\tfor (source = repo->objects->sources; source; source = source->next) {\n+\t\tret = packfile_store_for_each_object_internal(source->packfiles, cb, data,\n+\t\t\t\t\t\t\t      flags, &pack_errors);\n+\t\tif (ret)\n \t\t\tbreak;\n \t}\n \n-\treturn r ? r : pack_errors;\n+\treturn ret ? ret : pack_errors;\n }\n \n static int add_promisor_object(const struct object_id *oid,\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534646","messageId":"20260126-pks-odb-for-each-object-v4-6-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 06/14] packfile: introduce function to iterate through objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:22Z","receivedAt":"2026-01-26T09:51:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Introduce a new function `packfile_store_for_each_object()`. This\nfunction is equivalent to `odb_source_loose_for_each_object()`, except\nthat it:\n\n  - Works on a single packfile store instead of working on the object\n    database level. Consequently, it will only yield packed objects of a\n    single object database source.\n\n  - Passes a `struct object_info` to the callback function.\n\nAs such, it provides the same callback interface as we already provide\nfor loose objects now. These functions will be used in a subsequent step\nto implement `odb_for_each_object()`.\n\nThe `for_each_packed_object()` function continues to exist for now, but\nit will be removed at the end of this patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++\n packfile.h | 15 +++++++++++++++\n 2 files changed, 66 insertions(+)\n\ndiff --git a/packfile.c b/packfile.c\nindex d15a2ce12b..c35d5ea655 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2360,6 +2360,57 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \treturn ret ? ret : pack_errors;\n }\n \n+struct packfile_store_for_each_object_wrapper_data {\n+\tstruct packfile_store *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 packfile_store_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(pack, offset, &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+int packfile_store_for_each_object(struct packfile_store *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   unsigned flags)\n+{\n+\tstruct packfile_store_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+\tint pack_errors = 0, ret;\n+\n+\tret = packfile_store_for_each_object_internal(store, packfile_store_for_each_object_wrapper,\n+\t\t\t\t\t\t      &data, flags, &pack_errors);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn pack_errors ? -1 : 0;\n+}\n+\n static int add_promisor_object(const struct object_id *oid,\n \t\t\t       struct packed_git *pack,\n \t\t\t       uint32_t pos UNUSED,\ndiff --git a/packfile.h b/packfile.h\nindex 447c44c4a7..b7964f0289 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -343,6 +343,21 @@ int for_each_object_in_pack(struct packed_git *p,\n int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n \t\t\t   void *data, unsigned 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 packfile_store *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   unsigned flags);\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.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534647","messageId":"20260126-pks-odb-for-each-object-v4-7-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 07/14] odb: introduce `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:23Z","receivedAt":"2026-01-26T09:51:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Introduce a new function `odb_for_each_object()` that knows to iterate\nthrough all objects part of a given object database. This function is\nessentially a simple wrapper around the object database sources.\n\nSubsequent commits will adapt callers to use this new function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.h |  7 ++++---\n odb.c         | 29 +++++++++++++++++++++++++++++\n odb.h         | 20 ++++++++++++++++++++\n 3 files changed, 53 insertions(+), 3 deletions(-)\n\ndiff --git a/object-file.h b/object-file.h\nindex 5b9641cd89..b5eac0349e 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -139,9 +139,10 @@ int for_each_loose_object(struct object_database *odb,\n \n /*\n  * Iterate through all loose objects in the given object database source and\n- * invoke the callback function for each of them. If given, the object info\n- * will be populated with the object's data as if you had called\n- * `odb_source_loose_read_object_info()` on the object.\n+ * invoke the callback function for each of them. If an object info request is\n+ * given, then the object info will be read for every individual object and\n+ * passed to the callback as if `odb_source_loose_read_object_info()` was\n+ * called for the object.\n  */\n int odb_source_loose_for_each_object(struct odb_source *source,\n \t\t\t\t     const struct object_info *request,\ndiff --git a/odb.c b/odb.c\nindex ac70b6a099..13a415c2c3 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -995,6 +995,35 @@ int odb_freshen_object(struct object_database *odb,\n \treturn 0;\n }\n \n+int odb_for_each_object(struct object_database *odb,\n+\t\t\tconst struct object_info *request,\n+\t\t\todb_for_each_object_cb cb,\n+\t\t\tvoid *cb_data,\n+\t\t\tunsigned flags)\n+{\n+\tint ret;\n+\n+\todb_prepare_alternates(odb);\n+\tfor (struct odb_source *source = odb->sources; source; source = source->next) {\n+\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local)\n+\t\t\tcontinue;\n+\n+\t\tif (!(flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) {\n+\t\t\tret = odb_source_loose_for_each_object(source, request,\n+\t\t\t\t\t\t\t       cb, cb_data, flags);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\n+\t\tret = packfile_store_for_each_object(source->packfiles, request,\n+\t\t\t\t\t\t     cb, cb_data, flags);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n void odb_assert_oid_type(struct object_database *odb,\n \t\t\t const struct object_id *oid, enum object_type expect)\n {\ndiff --git a/odb.h b/odb.h\nindex f97f249580..b5d28bc188 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -475,6 +475,26 @@ typedef int (*odb_for_each_object_cb)(const struct object_id *oid,\n \t\t\t\t      struct object_info *oi,\n \t\t\t\t      void *cb_data);\n \n+/*\n+ * Iterate through all objects contained in the object database. Note that\n+ * objects may be iterated over multiple times in case they are either stored\n+ * in different backends or in case they are stored in multiple sources.\n+ * If an object info request is given, then the object info will be read and\n+ * passed to the callback as if `odb_read_object_info()` was called for the\n+ * object.\n+ *\n+ * Returning a non-zero error code from the callback function will cause\n+ * iteration to abort. The error code will be propagated.\n+ *\n+ * Returns 0 on success, a negative error code in case a failure occurred, or\n+ * an arbitrary non-zero error code returned by the callback itself.\n+ */\n+int odb_for_each_object(struct object_database *odb,\n+\t\t\tconst struct object_info *request,\n+\t\t\todb_for_each_object_cb cb,\n+\t\t\tvoid *cb_data,\n+\t\t\tunsigned flags);\n+\n enum {\n \t/*\n \t * By default, `odb_write_object()` does not actually write anything\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534648","messageId":"20260126-pks-odb-for-each-object-v4-8-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 08/14] builtin/fsck: refactor to use `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:24Z","receivedAt":"2026-01-26T09:51:45Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In git-fsck(1) we have two callsites where we iterate over all objects\nvia `for_each_loose_object()` and `for_each_packed_object()`. Both of\nthese are trivially convertible with `odb_for_each_object()`.\n\nRefactor these callsites accordingly.\n\nNote that `odb_for_each_object()` may iterate over the same object\nmultiple times, for example when it exists both in packed and loose\nformat. But this has already been the case beforehand, so this does not\nresult in a change in behaviour.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 57 ++++++++++++---------------------------------------------\n 1 file changed, 12 insertions(+), 45 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 4979bc795e..2ebe77d58e 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -218,15 +218,17 @@ static int mark_used(struct object *obj, enum object_type type UNUSED,\n \treturn 0;\n }\n \n-static void mark_unreachable_referents(const struct object_id *oid)\n+static int mark_unreachable_referents(const struct object_id *oid,\n+\t\t\t\t      struct object_info *oi UNUSED,\n+\t\t\t\t      void *data UNUSED)\n {\n \tstruct fsck_options options = FSCK_OPTIONS_DEFAULT;\n \tstruct object *obj = lookup_object(the_repository, oid);\n \n \tif (!obj || !(obj->flags & HAS_OBJ))\n-\t\treturn; /* not part of our original set */\n+\t\treturn 0; /* not part of our original set */\n \tif (obj->flags & REACHABLE)\n-\t\treturn; /* reachable objects already traversed */\n+\t\treturn 0; /* reachable objects already traversed */\n \n \t/*\n \t * Avoid passing OBJ_NONE to fsck_walk, which will parse the object\n@@ -243,22 +245,7 @@ static void mark_unreachable_referents(const struct object_id *oid)\n \tfsck_walk(obj, NULL, &options);\n \tif (obj->type == OBJ_TREE)\n \t\tfree_tree_buffer((struct tree *)obj);\n-}\n \n-static int mark_loose_unreachable_referents(const struct object_id *oid,\n-\t\t\t\t\t    const char *path UNUSED,\n-\t\t\t\t\t    void *data UNUSED)\n-{\n-\tmark_unreachable_referents(oid);\n-\treturn 0;\n-}\n-\n-static int mark_packed_unreachable_referents(const struct object_id *oid,\n-\t\t\t\t\t     struct packed_git *pack UNUSED,\n-\t\t\t\t\t     uint32_t pos UNUSED,\n-\t\t\t\t\t     void *data UNUSED)\n-{\n-\tmark_unreachable_referents(oid);\n \treturn 0;\n }\n \n@@ -394,12 +381,8 @@ static void check_connectivity(void)\n \t\t * and ignore any that weren't present in our earlier\n \t\t * traversal.\n \t\t */\n-\t\tfor_each_loose_object(the_repository->objects,\n-\t\t\t\t      mark_loose_unreachable_referents, NULL, 0);\n-\t\tfor_each_packed_object(the_repository,\n-\t\t\t\t       mark_packed_unreachable_referents,\n-\t\t\t\t       NULL,\n-\t\t\t\t       0);\n+\t\todb_for_each_object(the_repository->objects, NULL,\n+\t\t\t\t    mark_unreachable_referents, NULL, 0);\n \t}\n \n \t/* Look up all the requirements, warn about missing objects.. */\n@@ -848,26 +831,12 @@ static void fsck_index(struct index_state *istate, const char *index_path,\n \tfsck_resolve_undo(istate, index_path);\n }\n \n-static void mark_object_for_connectivity(const struct object_id *oid)\n+static int mark_object_for_connectivity(const struct object_id *oid,\n+\t\t\t\t\tstruct object_info *oi UNUSED,\n+\t\t\t\t\tvoid *cb_data UNUSED)\n {\n \tstruct object *obj = lookup_unknown_object(the_repository, oid);\n \tobj->flags |= HAS_OBJ;\n-}\n-\n-static int mark_loose_for_connectivity(const struct object_id *oid,\n-\t\t\t\t       const char *path UNUSED,\n-\t\t\t\t       void *data UNUSED)\n-{\n-\tmark_object_for_connectivity(oid);\n-\treturn 0;\n-}\n-\n-static int mark_packed_for_connectivity(const struct object_id *oid,\n-\t\t\t\t\tstruct packed_git *pack UNUSED,\n-\t\t\t\t\tuint32_t pos UNUSED,\n-\t\t\t\t\tvoid *data UNUSED)\n-{\n-\tmark_object_for_connectivity(oid);\n \treturn 0;\n }\n \n@@ -1001,10 +970,8 @@ int cmd_fsck(int argc,\n \t\tfsck_refs(the_repository);\n \n \tif (connectivity_only) {\n-\t\tfor_each_loose_object(the_repository->objects,\n-\t\t\t\t      mark_loose_for_connectivity, NULL, 0);\n-\t\tfor_each_packed_object(the_repository,\n-\t\t\t\t       mark_packed_for_connectivity, NULL, 0);\n+\t\todb_for_each_object(the_repository->objects, NULL,\n+\t\t\t\t    mark_object_for_connectivity, NULL, 0);\n \t} else {\n \t\todb_prepare_alternates(the_repository->objects);\n \t\tfor (source = the_repository->objects->sources; source; source = source->next)\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534649","messageId":"20260126-pks-odb-for-each-object-v4-9-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 09/14] treewide: enumerate promisor objects via `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:25Z","receivedAt":"2026-01-26T09:51:49Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have multiple callsites where we enumerate all promisor objects in\nthe object database via `for_each_packed_object()`. This is done by\npassing the `ODB_FOR_EACH_OBJECT_PROMISOR_ONLY` flag, which causes us to\nskip over all non-promisor objects.\n\nThese callsites can be trivially converted to `odb_for_each_object()` as\nwe know to skip enumeration of loose objects in case the `PROMISOR_ONLY`\nflag was passed by the caller.\n\nRefactor the sites accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n packfile.c        | 37 ++++++++++++++++++++++---------------\n repack-promisor.c |  8 ++++----\n revision.c        | 10 ++++------\n 3 files changed, 30 insertions(+), 25 deletions(-)\n\ndiff --git a/packfile.c b/packfile.c\nindex c35d5ea655..c54deabd64 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2411,28 +2411,32 @@ int packfile_store_for_each_object(struct packfile_store *store,\n \treturn pack_errors ? -1 : 0;\n }\n \n+struct add_promisor_object_data {\n+\tstruct repository *repo;\n+\tstruct oidset *set;\n+};\n+\n static int add_promisor_object(const struct object_id *oid,\n-\t\t\t       struct packed_git *pack,\n-\t\t\t       uint32_t pos UNUSED,\n-\t\t\t       void *set_)\n+\t\t\t       struct object_info *oi UNUSED,\n+\t\t\t       void *cb_data)\n {\n-\tstruct oidset *set = set_;\n+\tstruct add_promisor_object_data *data = cb_data;\n \tstruct object *obj;\n \tint we_parsed_object;\n \n-\tobj = lookup_object(pack->repo, oid);\n+\tobj = lookup_object(data->repo, oid);\n \tif (obj && obj->parsed) {\n \t\twe_parsed_object = 0;\n \t} else {\n \t\twe_parsed_object = 1;\n-\t\tobj = parse_object_with_flags(pack->repo, oid,\n+\t\tobj = parse_object_with_flags(data->repo, oid,\n \t\t\t\t\t      PARSE_OBJECT_SKIP_HASH_CHECK);\n \t}\n \n \tif (!obj)\n \t\treturn 1;\n \n-\toidset_insert(set, oid);\n+\toidset_insert(data->set, oid);\n \n \t/*\n \t * If this is a tree, commit, or tag, the objects it refers\n@@ -2450,19 +2454,19 @@ static int add_promisor_object(const struct object_id *oid,\n \t\t\t */\n \t\t\treturn 0;\n \t\twhile (tree_entry_gently(&desc, &entry))\n-\t\t\toidset_insert(set, &entry.oid);\n+\t\t\toidset_insert(data->set, &entry.oid);\n \t\tif (we_parsed_object)\n \t\t\tfree_tree_buffer(tree);\n \t} else if (obj->type == OBJ_COMMIT) {\n \t\tstruct commit *commit = (struct commit *) obj;\n \t\tstruct commit_list *parents = commit->parents;\n \n-\t\toidset_insert(set, get_commit_tree_oid(commit));\n+\t\toidset_insert(data->set, get_commit_tree_oid(commit));\n \t\tfor (; parents; parents = parents->next)\n-\t\t\toidset_insert(set, &parents->item->object.oid);\n+\t\t\toidset_insert(data->set, &parents->item->object.oid);\n \t} else if (obj->type == OBJ_TAG) {\n \t\tstruct tag *tag = (struct tag *) obj;\n-\t\toidset_insert(set, get_tagged_oid(tag));\n+\t\toidset_insert(data->set, get_tagged_oid(tag));\n \t}\n \treturn 0;\n }\n@@ -2474,10 +2478,13 @@ int is_promisor_object(struct repository *r, const struct object_id *oid)\n \n \tif (!promisor_objects_prepared) {\n \t\tif (repo_has_promisor_remote(r)) {\n-\t\t\tfor_each_packed_object(r, add_promisor_object,\n-\t\t\t\t\t       &promisor_objects,\n-\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY |\n-\t\t\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\t\t\tstruct add_promisor_object_data data = {\n+\t\t\t\t.repo = r,\n+\t\t\t\t.set = &promisor_objects,\n+\t\t\t};\n+\n+\t\t\todb_for_each_object(r->objects, NULL, add_promisor_object, &data,\n+\t\t\t\t\t    ODB_FOR_EACH_OBJECT_PROMISOR_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER);\n \t\t}\n \t\tpromisor_objects_prepared = 1;\n \t}\ndiff --git a/repack-promisor.c b/repack-promisor.c\nindex 45c330b9a5..35c4073632 100644\n--- a/repack-promisor.c\n+++ b/repack-promisor.c\n@@ -17,8 +17,8 @@ struct write_oid_context {\n  * necessary.\n  */\n static int write_oid(const struct object_id *oid,\n-\t\t     struct packed_git *pack UNUSED,\n-\t\t     uint32_t pos UNUSED, void *data)\n+\t\t     struct object_info *oi UNUSED,\n+\t\t     void *data)\n {\n \tstruct write_oid_context *ctx = data;\n \tstruct child_process *cmd = ctx->cmd;\n@@ -55,8 +55,8 @@ void repack_promisor_objects(struct repository *repo,\n \t */\n \tctx.cmd = &cmd;\n \tctx.algop = repo->hash_algo;\n-\tfor_each_packed_object(repo, write_oid, &ctx,\n-\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\todb_for_each_object(repo->objects, NULL, write_oid, &ctx,\n+\t\t\t    ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n \t\t/* No packed objects; cmd was never started */\ndiff --git a/revision.c b/revision.c\nindex 5aadf46dac..e34bcd8e88 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3626,8 +3626,7 @@ void reset_revision_walk(void)\n }\n \n static int mark_uninteresting(const struct object_id *oid,\n-\t\t\t      struct packed_git *pack UNUSED,\n-\t\t\t      uint32_t pos UNUSED,\n+\t\t\t      struct object_info *oi UNUSED,\n \t\t\t      void *cb)\n {\n \tstruct rev_info *revs = cb;\n@@ -3936,10 +3935,9 @@ int prepare_revision_walk(struct rev_info *revs)\n \t    (revs->limited && limiting_can_increase_treesame(revs)))\n \t\trevs->treesame.name = \"treesame\";\n \n-\tif (revs->exclude_promisor_objects) {\n-\t\tfor_each_packed_object(revs->repo, mark_uninteresting, revs,\n-\t\t\t\t       ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n-\t}\n+\tif (revs->exclude_promisor_objects)\n+\t\todb_for_each_object(revs->repo->objects, NULL, mark_uninteresting,\n+\t\t\t\t    revs, ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (!revs->reflog_info)\n \t\tprepare_to_use_bloom_filter(revs);\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534650","messageId":"20260126-pks-odb-for-each-object-v4-10-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 10/14] treewide: drop uses of `for_each_{loose,packed}_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:26Z","receivedAt":"2026-01-26T09:51:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're using `for_each_loose_object()` and `for_each_packed_object()` at\na couple of callsites to enumerate all loose and packed objects,\nrespectively. These functions will be removed in a subsequent commit in\nfavor of the newly introduced `odb_source_loose_for_each_object()` and\n`packfile_store_for_each_object()` replacements.\n\nPrepare for this by refactoring the sites accordingly.\n\nNote that ideally, we'd convert all callsites to use the generic\n`odb_for_each_object()` function already. But for some callers this is\nnot possible (yet), and it would require some significant refactorings\nto make this work. Converting these site will thus be deferred to a\nlater patch series.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c | 34 ++++++++++++++++++++++++++++------\n commit-graph.c     | 44 +++++++++++++++++++++++++++++++-------------\n 2 files changed, 59 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 6964a5a52c..e2c63dbedf 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -806,11 +806,14 @@ struct for_each_object_payload {\n \tvoid *payload;\n };\n \n-static int batch_one_object_loose(const struct object_id *oid,\n-\t\t\t\t  const char *path UNUSED,\n-\t\t\t\t  void *_payload)\n+static int batch_one_object_oi(const struct object_id *oid,\n+\t\t\t       struct object_info *oi,\n+\t\t\t       void *_payload)\n {\n \tstruct for_each_object_payload *payload = _payload;\n+\tif (oi && oi->whence == OI_PACKED)\n+\t\treturn payload->callback(oid, oi->u.packed.pack, oi->u.packed.offset,\n+\t\t\t\t\t payload->payload);\n \treturn payload->callback(oid, NULL, 0, payload->payload);\n }\n \n@@ -846,8 +849,21 @@ static void batch_each_object(struct batch_options *opt,\n \t\t.payload = _payload,\n \t};\n \tstruct bitmap_index *bitmap = prepare_bitmap_git(the_repository);\n+\tstruct odb_source *source;\n \n-\tfor_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);\n+\t/*\n+\t * TODO: we still need to tap into implementation details of the object\n+\t * database sources. Ideally, we should extend `odb_for_each_object()`\n+\t * to handle object filters itself so that we can move the filtering\n+\t * logic into the individual sources.\n+\t */\n+\todb_prepare_alternates(the_repository->objects);\n+\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\t\tint ret = odb_source_loose_for_each_object(source, NULL, batch_one_object_oi,\n+\t\t\t\t\t\t\t   &payload, flags);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n \n \tif (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,\n \t\t\t\t\t\t batch_one_object_bitmapped, &payload)) {\n@@ -861,8 +877,14 @@ static void batch_each_object(struct batch_options *opt,\n \t\t\t\t\t\t&payload, flags);\n \t\t}\n \t} else {\n-\t\tfor_each_packed_object(the_repository, batch_one_object_packed,\n-\t\t\t\t       &payload, flags);\n+\t\tstruct object_info oi = { 0 };\n+\n+\t\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\t\t\tint ret = packfile_store_for_each_object(source->packfiles, &oi,\n+\t\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}\n \t}\n \n \tfree_bitmap_index(bitmap);\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 7f1145a082..a3087d7883 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1479,30 +1479,38 @@ static int write_graph_chunk_bloom_data(struct hashfile *f,\n \treturn 0;\n }\n \n+static int add_packed_commits_oi(const struct object_id *oid,\n+\t\t\t\t struct object_info *oi,\n+\t\t\t\t void *data)\n+{\n+\tstruct write_commit_graph_context *ctx = (struct write_commit_graph_context*)data;\n+\n+\tif (ctx->progress)\n+\t\tdisplay_progress(ctx->progress, ++ctx->progress_done);\n+\n+\tif (*oi->typep != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\toid_array_append(&ctx->oids, oid);\n+\tset_commit_pos(ctx->r, oid);\n+\n+\treturn 0;\n+}\n+\n static int add_packed_commits(const struct object_id *oid,\n \t\t\t      struct packed_git *pack,\n \t\t\t      uint32_t pos,\n \t\t\t      void *data)\n {\n-\tstruct write_commit_graph_context *ctx = (struct write_commit_graph_context*)data;\n \tenum object_type type;\n \toff_t offset = nth_packed_object_offset(pack, pos);\n \tstruct object_info oi = OBJECT_INFO_INIT;\n \n-\tif (ctx->progress)\n-\t\tdisplay_progress(ctx->progress, ++ctx->progress_done);\n-\n \toi.typep = &type;\n \tif (packed_object_info(pack, offset, &oi) < 0)\n \t\tdie(_(\"unable to get type of object %s\"), oid_to_hex(oid));\n \n-\tif (type != OBJ_COMMIT)\n-\t\treturn 0;\n-\n-\toid_array_append(&ctx->oids, oid);\n-\tset_commit_pos(ctx->r, oid);\n-\n-\treturn 0;\n+\treturn add_packed_commits_oi(oid, &oi, data);\n }\n \n static void add_missing_parents(struct write_commit_graph_context *ctx, struct commit *commit)\n@@ -1959,13 +1967,23 @@ static int fill_oids_from_commits(struct write_commit_graph_context *ctx,\n \n static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx)\n {\n+\tstruct odb_source *source;\n+\tenum object_type type;\n+\tstruct object_info oi = {\n+\t\t.typep = &type,\n+\t};\n+\n \tif (ctx->report_progress)\n \t\tctx->progress = start_delayed_progress(\n \t\t\tctx->r,\n \t\t\t_(\"Finding commits for commit graph among packed objects\"),\n \t\t\tctx->approx_nr_objects);\n-\tfor_each_packed_object(ctx->r, add_packed_commits, ctx,\n-\t\t\t       ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\n+\todb_prepare_alternates(ctx->r->objects);\n+\tfor (source = ctx->r->objects->sources; source; source = source->next)\n+\t\tpackfile_store_for_each_object(source->packfiles, &oi, add_packed_commits_oi,\n+\t\t\t\t\t       ctx, ODB_FOR_EACH_OBJECT_PACK_ORDER);\n+\n \tif (ctx->progress_done < ctx->approx_nr_objects)\n \t\tdisplay_progress(ctx->progress, ctx->approx_nr_objects);\n \tstop_progress(&ctx->progress);\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534651","messageId":"20260126-pks-odb-for-each-object-v4-11-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 11/14] odb: introduce mtime fields for object info requests","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:27Z","receivedAt":"2026-01-26T09:51:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are some use cases where we need to figure out the mtime for\nobjects. Most importantly, this is the case when we want to prune\nunreachable objects. But getting at that data requires users to manually\nderive the info either via the loose object's mtime, the packfiles'\nmtime or via the \".mtimes\" file.\n\nIntroduce a new `struct object_info::mtimep` pointer that allows callers\nto request an object's mtime. This new field will be used in a\nsubsequent commit.\n\nNote that the concept of \"mtime\" is ambiguous: given an object, it may\nbe stored multiple times in the object database, and each of these\ninstances may have a different mtime. Disambiguating these mtimes is\nnothing that can happen on the generic ODB layer: the caller may search\nfor the oldest object, the newest object, or even the relation of object\nmtimes depending on the specific source they are located in. As such, it\nis the responsibility of the caller to disambiguate mtimes.\n\nA consequence of this is that it's most likely incorrect to look up the\nmtime via `odb_read_object_info()`, as this interface does not give us\nenough information to disambiguate the mtime. Document this accordingly\nand tell users to use `odb_for_each_object()` instead.\n\nEven with this gotcha though it's sensible to have this request as part\nof the object info, as the mtime is a property of the object storage\nformat. If we for example had a \"black-box\" storage backend, we'd still\nneed to be able to query it for the mtime info in a generic way.\n\nWe could introduce a safety mechanism that for example calls `BUG()` in\ncase we look up the mtime outside of `odb_for_each_object()`. But that\nfeels somewhat heavy-handed.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 29 +++++++++++++++++++++++++----\n odb.c         |  2 ++\n odb.h         | 13 +++++++++++++\n packfile.c    | 41 ++++++++++++++++++++++++++++++++++-------\n 4 files changed, 74 insertions(+), 11 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex ef2c7618c1..5537ab2c37 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -409,6 +409,7 @@ static int read_object_info_from_path(struct odb_source *source,\n \tchar hdr[MAX_HEADER_LEN];\n \tunsigned long size_scratch;\n \tenum object_type type_scratch;\n+\tstruct stat st;\n \n \t/*\n \t * If we don't care about type or size, then we don't\n@@ -421,7 +422,7 @@ static int read_object_info_from_path(struct odb_source *source,\n \tif (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) {\n \t\tstruct stat st;\n \n-\t\tif ((!oi || !oi->disk_sizep) && (flags & OBJECT_INFO_QUICK)) {\n+\t\tif ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) {\n \t\t\tret = quick_has_loose(source->loose, oid) ? 0 : -1;\n \t\t\tgoto out;\n \t\t}\n@@ -431,8 +432,12 @@ static int read_object_info_from_path(struct odb_source *source,\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (oi && oi->disk_sizep)\n-\t\t\t*oi->disk_sizep = st.st_size;\n+\t\tif (oi) {\n+\t\t\tif (oi->disk_sizep)\n+\t\t\t\t*oi->disk_sizep = st.st_size;\n+\t\t\tif (oi->mtimep)\n+\t\t\t\t*oi->mtimep = st.st_mtime;\n+\t\t}\n \n \t\tret = 0;\n \t\tgoto out;\n@@ -446,7 +451,21 @@ static int read_object_info_from_path(struct odb_source *source,\n \t\tgoto out;\n \t}\n \n-\tmap = map_fd(fd, path, &mapsize);\n+\tif (fstat(fd, &st)) {\n+\t\tclose(fd);\n+\t\tret = -1;\n+\t\tgoto out;\n+\t}\n+\n+\tmapsize = xsize_t(st.st_size);\n+\tif (!mapsize) {\n+\t\tclose(fd);\n+\t\tret = error(_(\"object file %s is empty\"), path);\n+\t\tgoto out;\n+\t}\n+\n+\tmap = xmmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);\n+\tclose(fd);\n \tif (!map) {\n \t\tret = -1;\n \t\tgoto out;\n@@ -454,6 +473,8 @@ static int read_object_info_from_path(struct odb_source *source,\n \n \tif (oi->disk_sizep)\n \t\t*oi->disk_sizep = mapsize;\n+\tif (oi->mtimep)\n+\t\t*oi->mtimep = st.st_mtime;\n \n \tstream_to_end = &stream;\n \ndiff --git a/odb.c b/odb.c\nindex 13a415c2c3..9d9a3fad62 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -702,6 +702,8 @@ static int do_oid_object_info_extended(struct object_database *odb,\n \t\t\t\toidclr(oi->delta_base_oid, odb->repo->hash_algo);\n \t\t\tif (oi->contentp)\n \t\t\t\t*oi->contentp = xmemdupz(co->buf, co->size);\n+\t\t\tif (oi->mtimep)\n+\t\t\t\t*oi->mtimep = 0;\n \t\t\toi->whence = OI_CACHED;\n \t\t}\n \t\treturn 0;\ndiff --git a/odb.h b/odb.h\nindex b5d28bc188..8ad0fcc02f 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -318,6 +318,19 @@ struct object_info {\n \tstruct object_id *delta_base_oid;\n \tvoid **contentp;\n \n+\t/*\n+\t * The time the given looked-up object has been last modified.\n+\t *\n+\t * Note: the mtime may be ambiguous in case the object exists multiple\n+\t * times in the object database. It is thus _not_ recommended to use\n+\t * this field outside of contexts where you would read every instance\n+\t * of the object, like for example with `odb_for_each_object()`. As it\n+\t * is impossible to say at the ODB level what the intent of the caller\n+\t * is (e.g. whether to find the oldest or newest object), it is the\n+\t * responsibility of the caller to disambiguate the mtimes.\n+\t */\n+\ttime_t *mtimep;\n+\n \t/* Response */\n \tenum {\n \t\tOI_CACHED,\ndiff --git a/packfile.c b/packfile.c\nindex c54deabd64..845633139f 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -1578,13 +1578,14 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,\n \thashmap_add(&delta_base_cache, &ent->ent);\n }\n \n-int packed_object_info(struct packed_git *p,\n-\t\t       off_t obj_offset, struct object_info *oi)\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 {\n \tstruct pack_window *w_curs = NULL;\n \tunsigned long size;\n \toff_t curpos = obj_offset;\n \tenum object_type type = OBJ_NONE;\n+\tuint32_t pack_pos;\n \tint ret;\n \n \t/*\n@@ -1619,16 +1620,35 @@ int packed_object_info(struct packed_git *p,\n \t\t}\n \t}\n \n-\tif (oi->disk_sizep) {\n-\t\tuint32_t pos;\n-\t\tif (offset_to_pack_pos(p, obj_offset, &pos) < 0) {\n+\tif (oi->disk_sizep || (oi->mtimep && p->is_cruft)) {\n+\t\tif (offset_to_pack_pos(p, obj_offset, &pack_pos) < 0) {\n \t\t\terror(\"could not find object at offset %\"PRIuMAX\" \"\n \t\t\t      \"in pack %s\", (uintmax_t)obj_offset, p->pack_name);\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n+\t}\n+\n+\tif (oi->disk_sizep)\n+\t\t*oi->disk_sizep = pack_pos_to_offset(p, pack_pos + 1) - obj_offset;\n+\n+\tif (oi->mtimep) {\n+\t\tif (p->is_cruft) {\n+\t\t\tuint32_t index_pos;\n+\n+\t\t\tif (load_pack_mtimes(p) < 0)\n+\t\t\t\tdie(_(\"could not load .mtimes for cruft pack '%s'\"),\n+\t\t\t\t    pack_basename(p));\n+\n+\t\t\tif (maybe_index_pos)\n+\t\t\t\tindex_pos = *maybe_index_pos;\n+\t\t\telse\n+\t\t\t\tindex_pos = pack_pos_to_index(p, pack_pos);\n \n-\t\t*oi->disk_sizep = pack_pos_to_offset(p, pos + 1) - obj_offset;\n+\t\t\t*oi->mtimep = nth_packed_mtime(p, index_pos);\n+\t\t} else {\n+\t\t\t*oi->mtimep = p->mtime;\n+\t\t}\n \t}\n \n \tif (oi->typep) {\n@@ -1681,6 +1701,12 @@ int packed_object_info(struct packed_git *p,\n \treturn ret;\n }\n \n+int packed_object_info(struct packed_git *p, off_t obj_offset,\n+\t\t       struct object_info *oi)\n+{\n+\treturn packed_object_info_with_index_pos(p, obj_offset, NULL, oi);\n+}\n+\n static void *unpack_compressed_entry(struct packed_git *p,\n \t\t\t\t    struct pack_window **w_curs,\n \t\t\t\t    off_t curpos,\n@@ -2378,7 +2404,8 @@ static int packfile_store_for_each_object_wrapper(const struct object_id *oid,\n \t\toff_t offset = nth_packed_object_offset(pack, index_pos);\n \t\tstruct object_info oi = *data->request;\n \n-\t\tif (packed_object_info(pack, offset, &oi) < 0) {\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-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534652","messageId":"20260126-pks-odb-for-each-object-v4-12-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 12/14] builtin/pack-objects: use `packfile_store_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:28Z","receivedAt":"2026-01-26T09:51:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When enumerating objects that are supposed to be stored in a new cruft\npack we use `for_each_packed_object()` and then derive each object's\nmtime individually. Refactor this logic to instead use the new\n`packfile_store_for_each_object()` function with an object info request\nthat asks for the respective mtimes.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/pack-objects.c | 46 ++++++++++++++++++++++------------------------\n 1 file changed, 22 insertions(+), 24 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 74317051fd..a6d37366ff 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4314,25 +4314,12 @@ static void show_edge(struct commit *commit)\n }\n \n static int add_object_in_unpacked_pack(const struct object_id *oid,\n-\t\t\t\t       struct packed_git *pack,\n-\t\t\t\t       uint32_t pos,\n+\t\t\t\t       struct object_info *oi,\n \t\t\t\t       void *data UNUSED)\n {\n \tif (cruft) {\n-\t\toff_t offset;\n-\t\ttime_t mtime;\n-\n-\t\tif (pack->is_cruft) {\n-\t\t\tif (load_pack_mtimes(pack) < 0)\n-\t\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n-\t\t\tmtime = nth_packed_mtime(pack, pos);\n-\t\t} else {\n-\t\t\tmtime = pack->mtime;\n-\t\t}\n-\t\toffset = nth_packed_object_offset(pack, pos);\n-\n-\t\tadd_cruft_object_entry(oid, OBJ_NONE, pack, offset,\n-\t\t\t\t       NULL, mtime);\n+\t\tadd_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack,\n+\t\t\t\t       oi->u.packed.offset, NULL, *oi->mtimep);\n \t} else {\n \t\tadd_object_entry(oid, OBJ_NONE, \"\", 0);\n \t}\n@@ -4341,14 +4328,25 @@ static int add_object_in_unpacked_pack(const struct object_id *oid,\n \n static void add_objects_in_unpacked_packs(void)\n {\n-\tif (for_each_packed_object(to_pack.repo,\n-\t\t\t\t   add_object_in_unpacked_pack,\n-\t\t\t\t   NULL,\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n-\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n-\t\tdie(_(\"cannot open pack index\"));\n+\tstruct odb_source *source;\n+\ttime_t mtime;\n+\tstruct object_info oi = {\n+\t\t.mtimep = &mtime,\n+\t};\n+\n+\todb_prepare_alternates(to_pack.repo->objects);\n+\tfor (source = to_pack.repo->objects->sources; source; source = source->next) {\n+\t\tif (!source->local)\n+\t\t\tcontinue;\n+\n+\t\tif (packfile_store_for_each_object(source->packfiles, &oi,\n+\t\t\t\t\t\t   add_object_in_unpacked_pack, NULL,\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_PACK_ORDER |\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_LOCAL_ONLY |\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |\n+\t\t\t\t\t\t   ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))\n+\t\t\tdie(_(\"cannot open pack index\"));\n+\t}\n }\n \n static int add_loose_object(const struct object_id *oid, const char *path,\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534653","messageId":"20260126-pks-odb-for-each-object-v4-13-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 13/14] reachable: convert to use `odb_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:29Z","receivedAt":"2026-01-26T09:51:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"To figure out which objects expired objects we enumerate all loose and\npacked objects individually so that we can figure out their respective\nmtimes. Refactor the code to instead use `odb_for_each_object()` with a\nrequest that ask for the object mtime instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n reachable.c | 125 +++++++++++++++++-------------------------------------------\n 1 file changed, 35 insertions(+), 90 deletions(-)\n\ndiff --git a/reachable.c b/reachable.c\nindex 82676b2668..101cfc2727 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -191,30 +191,27 @@ static int obj_is_recent(const struct object_id *oid, timestamp_t mtime,\n \treturn oidset_contains(&data->extra_recent_oids, oid);\n }\n \n-static void add_recent_object(const struct object_id *oid,\n-\t\t\t      struct packed_git *pack,\n-\t\t\t      off_t offset,\n-\t\t\t      timestamp_t mtime,\n-\t\t\t      struct recent_data *data)\n+static int want_recent_object(struct recent_data *data,\n+\t\t\t      const struct object_id *oid)\n {\n-\tstruct object *obj;\n-\tenum object_type type;\n+\tif (data->ignore_in_core_kept_packs &&\n+\t    has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE))\n+\t\treturn 0;\n+\treturn 1;\n+}\n \n-\tif (!obj_is_recent(oid, mtime, data))\n-\t\treturn;\n+static int add_recent_object(const struct object_id *oid,\n+\t\t\t     struct object_info *oi,\n+\t\t\t     void *cb_data)\n+{\n+\tstruct recent_data *data = cb_data;\n+\tstruct object *obj;\n \n-\t/*\n-\t * We do not want to call parse_object here, because\n-\t * inflating blobs and trees could be very expensive.\n-\t * However, we do need to know the correct type for\n-\t * later processing, and the revision machinery expects\n-\t * commits and tags to have been parsed.\n-\t */\n-\ttype = odb_read_object_info(the_repository->objects, oid, NULL);\n-\tif (type < 0)\n-\t\tdie(\"unable to get object info for %s\", oid_to_hex(oid));\n+\tif (!want_recent_object(data, oid) ||\n+\t    !obj_is_recent(oid, *oi->mtimep, data))\n+\t\treturn 0;\n \n-\tswitch (type) {\n+\tswitch (*oi->typep) {\n \tcase OBJ_TAG:\n \tcase OBJ_COMMIT:\n \t\tobj = parse_object_or_die(the_repository, oid, NULL);\n@@ -227,77 +224,22 @@ static void add_recent_object(const struct object_id *oid,\n \t\tbreak;\n \tdefault:\n \t\tdie(\"unknown object type for %s: %s\",\n-\t\t    oid_to_hex(oid), type_name(type));\n+\t\t    oid_to_hex(oid), type_name(*oi->typep));\n \t}\n \n \tif (!obj)\n \t\tdie(\"unable to lookup %s\", oid_to_hex(oid));\n-\n-\tadd_pending_object(data->revs, obj, \"\");\n-\tif (data->cb)\n-\t\tdata->cb(obj, pack, offset, mtime);\n-}\n-\n-static int want_recent_object(struct recent_data *data,\n-\t\t\t      const struct object_id *oid)\n-{\n-\tif (data->ignore_in_core_kept_packs &&\n-\t    has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE))\n+\tif (obj->flags & SEEN)\n \t\treturn 0;\n-\treturn 1;\n-}\n \n-static int add_recent_loose(const struct object_id *oid,\n-\t\t\t    const char *path, void *data)\n-{\n-\tstruct stat st;\n-\tstruct object *obj;\n-\n-\tif (!want_recent_object(data, oid))\n-\t\treturn 0;\n-\n-\tobj = lookup_object(the_repository, oid);\n-\n-\tif (obj && obj->flags & SEEN)\n-\t\treturn 0;\n-\n-\tif (stat(path, &st) < 0) {\n-\t\t/*\n-\t\t * It's OK if an object went away during our iteration; this\n-\t\t * could be due to a simultaneous repack. But anything else\n-\t\t * we should abort, since we might then fail to mark objects\n-\t\t * which should not be pruned.\n-\t\t */\n-\t\tif (errno == ENOENT)\n-\t\t\treturn 0;\n-\t\treturn error_errno(\"unable to stat %s\", oid_to_hex(oid));\n+\tadd_pending_object(data->revs, obj, \"\");\n+\tif (data->cb) {\n+\t\tif (oi->whence == OI_PACKED)\n+\t\t\tdata->cb(obj, oi->u.packed.pack, oi->u.packed.offset, *oi->mtimep);\n+\t\telse\n+\t\t\tdata->cb(obj, NULL, 0, *oi->mtimep);\n \t}\n \n-\tadd_recent_object(oid, NULL, 0, st.st_mtime, data);\n-\treturn 0;\n-}\n-\n-static int add_recent_packed(const struct object_id *oid,\n-\t\t\t     struct packed_git *p,\n-\t\t\t     uint32_t pos,\n-\t\t\t     void *data)\n-{\n-\tstruct object *obj;\n-\ttimestamp_t mtime = p->mtime;\n-\n-\tif (!want_recent_object(data, oid))\n-\t\treturn 0;\n-\n-\tobj = lookup_object(the_repository, oid);\n-\n-\tif (obj && obj->flags & SEEN)\n-\t\treturn 0;\n-\tif (p->is_cruft) {\n-\t\tif (load_pack_mtimes(p) < 0)\n-\t\t\tdie(_(\"could not load cruft pack .mtimes\"));\n-\t\tmtime = nth_packed_mtime(p, pos);\n-\t}\n-\tadd_recent_object(oid, p, nth_packed_object_offset(p, pos), mtime, data);\n \treturn 0;\n }\n \n@@ -307,7 +249,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \t\t\t\t\t   int ignore_in_core_kept_packs)\n {\n \tstruct recent_data data;\n-\tenum odb_for_each_object_flags flags;\n+\tunsigned flags;\n+\tenum object_type type;\n+\ttime_t mtime;\n+\tstruct object_info oi = {\n+\t\t.mtimep = &mtime,\n+\t\t.typep = &type,\n+\t};\n \tint r;\n \n \tdata.revs = revs;\n@@ -318,16 +266,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \toidset_init(&data.extra_recent_oids, 0);\n \tdata.extra_recent_oids_loaded = 0;\n \n-\tr = for_each_loose_object(the_repository->objects, add_recent_loose, &data,\n-\t\t\t\t  ODB_FOR_EACH_OBJECT_LOCAL_ONLY);\n-\tif (r)\n-\t\tgoto done;\n-\n \tflags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER;\n \tif (ignore_in_core_kept_packs)\n \t\tflags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS;\n \n-\tr = for_each_packed_object(revs->repo, add_recent_packed, &data, flags);\n+\tr = odb_for_each_object(revs->repo->objects, &oi, add_recent_object, &data, flags);\n+\tif (r)\n+\t\tgoto done;\n \n done:\n \toidset_clear(&data.extra_recent_oids);\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534654","messageId":"20260126-pks-odb-for-each-object-v4-14-5a64a038c791@pks.im","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"[PATCH v4 14/14] odb: drop unused `for_each_{loose,packed}_object()` functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-26T09:51:30Z","receivedAt":"2026-01-26T09:52:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have converted all callers of `for_each_loose_object()` and\n`for_each_packed_object()` to use their new replacement functions\ninstead. We can thus remove them now.\n\nDo so and inline `packfile_store_for_each_object_internal()` now that it\nonly has a single callsite again. This makes it a bit easier to follow\nthe callback indirection that is happening there.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 20 ------------\n object-file.h | 11 -------\n packfile.c    | 99 +++++++++++++++++++++--------------------------------------\n packfile.h    |  2 --\n 4 files changed, 35 insertions(+), 97 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 5537ab2c37..6785821c8c 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1802,26 +1802,6 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \treturn r;\n }\n \n-int for_each_loose_object(struct object_database *odb,\n-\t\t\t  each_loose_object_fn cb, void *data,\n-\t\t\t  enum odb_for_each_object_flags flags)\n-{\n-\tstruct odb_source *source;\n-\n-\todb_prepare_alternates(odb);\n-\tfor (source = odb->sources; source; source = source->next) {\n-\t\tint r = for_each_loose_file_in_source(source, cb, NULL,\n-\t\t\t\t\t\t      NULL, data);\n-\t\tif (r)\n-\t\t\treturn r;\n-\n-\t\tif (flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY)\n-\t\t\tbreak;\n-\t}\n-\n-\treturn 0;\n-}\n-\n struct for_each_object_wrapper_data {\n \tstruct odb_source *source;\n \tconst struct object_info *request;\ndiff --git a/object-file.h b/object-file.h\nindex b5eac0349e..d9979baea8 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -126,17 +126,6 @@ int for_each_loose_file_in_source(struct odb_source *source,\n \t\t\t\t  each_loose_subdir_fn subdir_cb,\n \t\t\t\t  void *data);\n \n-/*\n- * Iterate over all accessible loose objects without respect to\n- * reachability. By default, this includes both local and alternate objects.\n- * The order in which objects are visited is unspecified.\n- *\n- * Any flags specific to packs are ignored.\n- */\n-int for_each_loose_object(struct object_database *odb,\n-\t\t\t  each_loose_object_fn, void *,\n-\t\t\t  enum odb_for_each_object_flags flags);\n-\n /*\n  * Iterate through all loose objects in the given object database source and\n  * invoke the callback function for each of them. If an object info request is\ndiff --git a/packfile.c b/packfile.c\nindex 845633139f..57fbf51876 100644\n--- a/packfile.c\n+++ b/packfile.c\n@@ -2327,65 +2327,6 @@ int for_each_object_in_pack(struct packed_git *p,\n \treturn r;\n }\n \n-static int packfile_store_for_each_object_internal(struct packfile_store *store,\n-\t\t\t\t\t\t   each_packed_object_fn cb,\n-\t\t\t\t\t\t   void *data,\n-\t\t\t\t\t\t   unsigned flags,\n-\t\t\t\t\t\t   int *pack_errors)\n-{\n-\tstruct packfile_list_entry *e;\n-\tint ret = 0;\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 ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n-\t\t\tcontinue;\n-\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n-\t\t    !p->pack_promisor)\n-\t\t\tcontinue;\n-\t\tif ((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 ((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\t*pack_errors = 1;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tret = for_each_object_in_pack(p, cb, data, flags);\n-\t\tif (ret)\n-\t\t\tbreak;\n-\t}\n-\n-\tstore->skip_mru_updates = false;\n-\n-\treturn ret;\n-}\n-\n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags)\n-{\n-\tstruct odb_source *source;\n-\tint pack_errors = 0;\n-\tint ret = 0;\n-\n-\todb_prepare_alternates(repo->objects);\n-\n-\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\tret = packfile_store_for_each_object_internal(source->packfiles, cb, data,\n-\t\t\t\t\t\t\t      flags, &pack_errors);\n-\t\tif (ret)\n-\t\t\tbreak;\n-\t}\n-\n-\treturn ret ? ret : pack_errors;\n-}\n-\n struct packfile_store_for_each_object_wrapper_data {\n \tstruct packfile_store *store;\n \tconst struct object_info *request;\n@@ -2428,14 +2369,44 @@ int packfile_store_for_each_object(struct packfile_store *store,\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-\tret = packfile_store_for_each_object_internal(store, packfile_store_for_each_object_wrapper,\n-\t\t\t\t\t\t      &data, flags, &pack_errors);\n-\tif (ret)\n-\t\treturn ret;\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 ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local)\n+\t\t\tcontinue;\n+\t\tif ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) &&\n+\t\t    !p->pack_promisor)\n+\t\t\tcontinue;\n+\t\tif ((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 ((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, flags);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\t}\n+\n+\tret = 0;\n \n-\treturn pack_errors ? -1 : 0;\n+out:\n+\tstore->skip_mru_updates = false;\n+\n+\tif (!ret && pack_errors)\n+\t\tret = -1;\n+\treturn ret;\n }\n \n struct add_promisor_object_data {\ndiff --git a/packfile.h b/packfile.h\nindex b7964f0289..1a1b720764 100644\n--- a/packfile.h\n+++ b/packfile.h\n@@ -340,8 +340,6 @@ typedef int each_packed_object_fn(const struct object_id *oid,\n int for_each_object_in_pack(struct packed_git *p,\n \t\t\t    each_packed_object_fn, void *data,\n \t\t\t    unsigned flags);\n-int for_each_packed_object(struct repository *repo, each_packed_object_fn cb,\n-\t\t\t   void *data, unsigned flags);\n \n /*\n  * Iterate through all packed objects in the given packfile store and invoke\n\n-- \n2.53.0.rc1.267.g6e3a78c723.dirty\n\n"},{"id":"534695","messageId":"xmqqsebsgg46.fsf@gitster.g","threadId":"64809","inReplyTo":"20260122192337.GC2098026@coredump.intra.peff.net","subject":"Re: [PATCH v3 02/14] odb: fix flags parameter to be unsigned","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-26T22:32:09Z","receivedAt":"2026-01-26T22:32:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I don't think there's any disagreement over using enums in general. It's\n> just a question of what type to declare in function interfaces.\n>\n>>  (1) enum gives a false sense of type safety to casual coders. If I\n>>      have two enum types and pass one to as a parameter to a\n>>      function that expects the other one, would the compiler help me\n>>      catch that as a potential mistake?  -Wenum-conversion is not\n>>      enabled even with -Wall so I am assuming that the compiler\n>>      folks fells that it is not reliable enough.\n>\n> It is enabled with -Wextra, which we turn on with DEVELOPER=1. I think\n> gcc will catch the most obvious mismatches like:\n>\n>   enum one { FOO };\n>   enum two { BAR };\n>   void func(enum one value);\n>   void doit(void) { func(BAR); }\n>\n> which yields:\n>\n>   $ gcc -c -Wall -Wextra foo.c\n>   foo.c: In function ‘doit’:\n>   foo.c:4:24: warning: implicit conversion from ‘enum two’ to ‘enum one’ [-Wenum-conversion]\n>       4 | void doit(void) { func(BAR); }\n>         |                        ^~~\n\nThis is good.  I think we just saw a potential use of this feature\nin Patrick's topic to turn a #define to an enum in <odb.h>.\n\n>>  (2) it is not easy to force an enum type to be unsigned, unless you\n>>      are at C23 or above.  If shifting enums are warned by the\n>>      compilers by default, I wouldn't worry about it, but use of\n>>      unsigned is more explicit in this regard.\n>\n> Do we need to force unsignedness for bit-flags? The compiler will use a\n> type that is sufficiently large for the enum values defined, and I would\n> not expect anybody to shift them.\n\nYes, as long as nobody shifts, it does not matter.  It's just not\nhaving to worry about it trumps having to declare that we would\nimmediately notice if anybody does something strange like that ;-)\n\n"},{"id":"534803","messageId":"20260129110839.GA1285720@coredump.intra.peff.net","threadId":"64809","inReplyTo":"aXcrftLpfcG4S5AX@pks.im","subject":"Re: [PATCH v3 12/14] builtin/pack-objects: use `packfile_store_for_each_object()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-01-29T11:08:39Z","receivedAt":"2026-01-29T11:08:41Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jan 26, 2026 at 09:53:18AM +0100, Patrick Steinhardt wrote:\n\n> > Yes, the end result is the same, both your patch and what I wrote here\n> > implement the same GC-specific definition of an object's \"mtime\". I am\n> > not following the argument about pluggability, though. The concern I\n> > have above is that we are pushing domain-specific logic into the object\n> > storage backend, not the other way around.\n> \n> To expand on the pluggability bit: every time you add a new backend\n> you'll have to extend the above logic to understand how it represents\n> the mtime. That by itself might be doable, but let's for example\n> consider a backend that is a black box to us (like a shared library that\n> may plug in arbitrary storage logic). In that case you would not even be\n> able to derive the information unless you have a generic layer that lets\n> you convey it to the caller.\n> \n> So overall I agree with you that there are nuances here, and that the\n> mtimep pointer _can_ be used incorrectly. But I still think that the\n> concept is generic enough across backends, and the refactored logic\n> still works as extended. I'll try to expand the docs and commit message\n> a bit to cover this discussion.\n\nThere's a related concept that I saw while reading some of the earlier\npatches. When you converted fsck, I wondered how you would handle the\ncall to read_loose_object(), which takes an actual path. And it needs to\ndo so, because we want to make sure we are opening and reading that\nparticular copy of the object, and not one from elsewhere.\n\nThe answer is that you punted on it for this series, and we still get\nthe path via for_each_loose_file_in_source(). ;) That is OK, but I think\nit will eventually run into the same issue: we will need some kind of\ncursor or context for the iterator to be able to get extended\ninformation about a particular copy of an object.\n\nI think there are probably two approaches here:\n\n  1. The abstract odb API tries to share as little as possible. It gives\n     the caller back an opaque context struct, and that struct can be\n     handed back to the odb to get object contents or other information\n     (perhaps even an mtime!). Under the hood for the current odb\n     implementation this is probably just a pointer to a string with the\n     filesystem path for loose objects, and the usual packed_git/offset\n     pair for packed objects.\n\n  2. The odb API provides a set of information that a particular backend\n     _might_ implement, and callers can poke at that information and\n     decide how to handle it when it's not available. And so that might\n     include a filesystem path for loose objects, which some backends\n     may choose to leave NULL.\n\nOption (1) presents a cleaner API for the odb, but it's also more\nrestrictive. Anything that a caller _might_ want to do has to be pushed\ndown into the API, and it has to start learning about things like\nmtimes. And how to decide what \"mtime\" means for non-filesystem\nbackends.\n\nOption (2) pushes more work onto the callers. They need to not only look\nup the mtimes themselves (like they do now), but they have to decide how\nto handle the case when no path is available. Which in the worst case\nmeans a special case for each type of backend, though I think in\npractice they'd probably fall into rough groups.\n\nI think one thing that appeals to me about option 2, though, is that it\nkeeps a lot of the specialized \"business logic\" together in those\ncallers. Most code doesn't are about concepts like mtime or specific\ncopies of objects. But when it does, like in repack or fsck, there are\noften subtle assumptions and interpretations. I'd rather see all of that\nlumped together in the fsck code than have it split half-and-half\nbetween them and the odb code (which is really going to be some backends\nidea of how its concepts can be shoe-horned into the abstract API).\n\n-Peff\n"},{"id":"534862","messageId":"aXyqPhWDNeKJv2re@pks.im","threadId":"64809","inReplyTo":"20260129110839.GA1285720@coredump.intra.peff.net","subject":"Re: [PATCH v3 12/14] builtin/pack-objects: use `packfile_store_for_each_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-30T12:57:45Z","receivedAt":"2026-01-30T12:58:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 29, 2026 at 06:08:39AM -0500, Jeff King wrote:\n> On Mon, Jan 26, 2026 at 09:53:18AM +0100, Patrick Steinhardt wrote:\n> \n> > > Yes, the end result is the same, both your patch and what I wrote here\n> > > implement the same GC-specific definition of an object's \"mtime\". I am\n> > > not following the argument about pluggability, though. The concern I\n> > > have above is that we are pushing domain-specific logic into the object\n> > > storage backend, not the other way around.\n> > \n> > To expand on the pluggability bit: every time you add a new backend\n> > you'll have to extend the above logic to understand how it represents\n> > the mtime. That by itself might be doable, but let's for example\n> > consider a backend that is a black box to us (like a shared library that\n> > may plug in arbitrary storage logic). In that case you would not even be\n> > able to derive the information unless you have a generic layer that lets\n> > you convey it to the caller.\n> > \n> > So overall I agree with you that there are nuances here, and that the\n> > mtimep pointer _can_ be used incorrectly. But I still think that the\n> > concept is generic enough across backends, and the refactored logic\n> > still works as extended. I'll try to expand the docs and commit message\n> > a bit to cover this discussion.\n> \n> There's a related concept that I saw while reading some of the earlier\n> patches. When you converted fsck, I wondered how you would handle the\n> call to read_loose_object(), which takes an actual path. And it needs to\n> do so, because we want to make sure we are opening and reading that\n> particular copy of the object, and not one from elsewhere.\n> \n> The answer is that you punted on it for this series, and we still get\n> the path via for_each_loose_file_in_source(). ;) That is OK, but I think\n> it will eventually run into the same issue: we will need some kind of\n> cursor or context for the iterator to be able to get extended\n> information about a particular copy of an object.\n> \n> I think there are probably two approaches here:\n> \n>   1. The abstract odb API tries to share as little as possible. It gives\n>      the caller back an opaque context struct, and that struct can be\n>      handed back to the odb to get object contents or other information\n>      (perhaps even an mtime!). Under the hood for the current odb\n>      implementation this is probably just a pointer to a string with the\n>      filesystem path for loose objects, and the usual packed_git/offset\n>      pair for packed objects.\n> \n>   2. The odb API provides a set of information that a particular backend\n>      _might_ implement, and callers can poke at that information and\n>      decide how to handle it when it's not available. And so that might\n>      include a filesystem path for loose objects, which some backends\n>      may choose to leave NULL.\n> \n> Option (1) presents a cleaner API for the odb, but it's also more\n> restrictive. Anything that a caller _might_ want to do has to be pushed\n> down into the API, and it has to start learning about things like\n> mtimes. And how to decide what \"mtime\" means for non-filesystem\n> backends.\n> \n> Option (2) pushes more work onto the callers. They need to not only look\n> up the mtimes themselves (like they do now), but they have to decide how\n> to handle the case when no path is available. Which in the worst case\n> means a special case for each type of backend, though I think in\n> practice they'd probably fall into rough groups.\n> \n> I think one thing that appeals to me about option 2, though, is that it\n> keeps a lot of the specialized \"business logic\" together in those\n> callers. Most code doesn't are about concepts like mtime or specific\n> copies of objects. But when it does, like in repack or fsck, there are\n> often subtle assumptions and interpretations. I'd rather see all of that\n> lumped together in the fsck code than have it split half-and-half\n> between them and the odb code (which is really going to be some backends\n> idea of how its concepts can be shoe-horned into the abstract API).\n\nYup. One thing that I'm planning to do in one of the subsequent patch\nseries is to expand `struct object_info` to handle this.\n\nRight now, the sturcture contains a `whence` pointer that tells us which\nbackend the information is stored in. But that concept can be extended\nto surface more info: instead of only telling the caller the type, we\ncan instead return the actual source that the object has been looked up\nin.\n\nFurthermore, the `struct object_info::u` union already contains enough\ninformation for us to uniquely identify a specific option. So what we\nwould do then is to call `odb_source_read_object_info()` on the specific\nsource and pass it the union.\n\nThe loose source wouldn't have to do anything in that case, as the\nlocation of the object is deterministic and there can only be one copy.\nBut the packed source would inspect `u.packed.pack` and thus know which\nspecific object we refer to.\n\nThis still hinges on a couple intermediate steps, but I think with this\nplan we should be able to handle this issue in a way where the caller\ndoesn't need to know _anything_ about how exactly the ODB source itself\nworks.\n\nThanks!\n\nPatrick\n"},{"id":"536560","messageId":"xmqqbjhjuilx.fsf@gitster.g","threadId":"64809","inReplyTo":"20260126-pks-odb-for-each-object-v4-0-5a64a038c791@pks.im","subject":"Re: [PATCH v4 00/14] odb: introduce `odb_for_each_object()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-20T22:59:22Z","receivedAt":"2026-02-20T22:59:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> this patch series introduces a generic `odb_for_each_object()` function\n> to iterate through objects and adapts callers to use it. The intent is\n> to make iteration through objects independent of the actual storage\n> backend.\n\nThis topic has been dormant for too long, but we saw quite a lot of\nthings changed over the course of its evolution.  Perhaps we are now\nat the sweet \"good enough\" place?\n\nLet me mark the topic for 'next', if that is the case.  Thanks.\n"}]}