odb: introduce object filters to `odb_for_each_object()`

The function `for_each_bitmapped_object()` can be used to iterate through all objects covered by a bitmap. The benefit of this function is that it allows the caller to efficiently handle some object filters. For example, this can be used to filter out objects of a specific type with some simple bitmap operations. But callers are currently required to manually wire up the use of bitmaps though, and to do so they have to reach into internals of a given object database source. Introduce a new `struct odb_for_each_object_options::filter` field so that the interface becomes generic. When set, then a backend may optionally use the filter to skip some objects that it would have otherwise yielded. Note that the respective backends are free to ignore this field if they cannot meaningfully optimize for a given filter, and consequently callers need to verify whether they actually want the returned objects. While annoying, we cannot easily lift this restriction anyway as the object filter infrastructure supports some filters that cannot be answered by the object database alone. An alternative might be to limit the filters to only those that _can_ be answered by backends. But ultimately, the filters that can be answered efficiently by the "packed" backend are completely disjunct from those that can be answered by the "loose" backend, and consequently the set of filters supported by all backends would be empty. Furthermore, it would require us to make assumptions about capabilities of future backends, which may be able to efficiently handle more filters than current ones. So in the end, this alternative would only limit us artificially. Implement the logic for the "packed" source. Note that we use the new function `prepare_bitmap_git_for_source()` to open the bitmap: as the backend operates on a single object source, we must only use bitmaps that belong to that specific source. Otherwise we might yield objects that are not part of the source at all, and with multiple sources we would enumerate the same bitmap once per source. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jul 15, 2026 at 08:22 UTC 204daf5e5c6f931352ddde16236e9705075d15bf
4 files changed +78 -2
odb.h
+12
@@ -8,6 +8,7 @@
8 #include "thread-utils.h"
9
10 struct cached_object_entry;
11 +struct list_objects_filter_options;
12 struct odb_source_inmemory;
13 struct packed_git;
14 struct repository;
@@ -490,6 +491,17 @@ struct odb_for_each_object_options {
491 */
492 const struct object_id *prefix;
493 size_t prefix_hex_len;
494 +
495 + /*
496 + * Optional object filter that allows backends to skip yielding
497 + * objects that are excluded by the filter as an optimization. The
498 + * filter is a best-effort hint: backends may use it to skip
499 + * excluded objects (e.g. by consulting a reachability bitmap), but
500 + * are also free to ignore it entirely and yield every object. As a
501 + * consequence, callers must re-apply the filter on yielded objects
502 + * if they require strict filtering semantics.
503 + */
504 + const struct list_objects_filter_options *filter;
505 };
506
507 /*
odb/source-packed.c
+62
@@ -3,11 +3,13 @@
3 #include "chdir-notify.h"
4 #include "dir.h"
5 #include "git-zlib.h"
6 +#include "list-objects-filter-options.h"
7 #include "mergesort.h"
8 #include "midx.h"
9 #include "odb/source-packed.h"
10 #include "odb/streaming.h"
11 #include "packfile.h"
12 +#include "pack-bitmap.h"
13
14 static int find_pack_entry(struct odb_source_packed *store,
15 const struct object_id *oid,
@@ -315,6 +317,37 @@ out:
317 return ret;
318 }
319
320 +struct bitmapped_for_each_object_data {
321 + struct odb_source_packed *packed;
322 + const struct object_info *request;
323 + const struct odb_for_each_object_options *opts;
324 + odb_for_each_object_cb cb;
325 + void *cb_data;
326 +};
327 +
328 +static int bitmapped_for_each_object(const struct object_id *oid,
329 + enum object_type type UNUSED,
330 + int flags UNUSED,
331 + uint32_t hash UNUSED,
332 + struct packed_git *pack,
333 + off_t offset,
334 + void *cb_data)
335 +{
336 + struct bitmapped_for_each_object_data *data = cb_data;
337 +
338 + if (should_exclude_pack(pack, data->opts->flags))
339 + return 0;
340 +
341 + if (data->request) {
342 + struct object_info oi = *data->request;
343 + if (packed_object_info(data->packed, pack, offset, &oi) < 0)
344 + return -1;
345 + return data->cb(oid, &oi, data->cb_data);
346 + }
347 +
348 + return data->cb(oid, NULL, data->cb_data);
349 +}
350 +
351 static int odb_source_packed_for_each_object(struct odb_source *source,
352 const struct object_info *request,
353 odb_for_each_object_cb cb,
@@ -328,12 +361,33 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
361 .cb = cb,
362 .cb_data = cb_data,
363 };
364 + struct bitmap_index *bitmap = NULL;
365 struct packfile_list_entry *e;
366 int pack_errors = 0, ret;
367
368 if (opts->prefix)
369 return odb_source_packed_for_each_prefixed_object(packed, opts, &data);
370
371 + if (opts->filter &&
372 + opts->filter->choice != LOFC_DISABLED &&
373 + can_filter_bitmap(opts->filter))
374 + bitmap = prepare_bitmap_git_for_source(packed);
375 + if (bitmap) {
376 + struct bitmapped_for_each_object_data bitmap_data = {
377 + .packed = packed,
378 + .request = request,
379 + .opts = opts,
380 + .cb = cb,
381 + .cb_data = cb_data,
382 + };
383 +
384 + ret = for_each_bitmapped_object(bitmap, opts->filter,
385 + bitmapped_for_each_object,
386 + &bitmap_data);
387 + if (ret)
388 + goto out;
389 + }
390 +
391 packed->skip_mru_updates = true;
392
393 for (e = packfile_store_get_packs(packed); e; e = e->next) {
@@ -342,6 +396,13 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
396 if (should_exclude_pack(p, opts->flags))
397 continue;
398
399 + /*
400 + * Objects covered by the bitmap have already been yielded
401 + * above; skip them here to avoid duplicates.
402 + */
403 + if (bitmap && bitmap_index_contains_pack(bitmap, p))
404 + continue;
405 +
406 if (open_pack_index(p)) {
407 pack_errors = 1;
408 continue;
@@ -357,6 +418,7 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
418
419 out:
420 packed->skip_mru_updates = false;
421 + free_bitmap_index(bitmap);
422
423 if (!ret && pack_errors)
424 ret = -1;
pack-bitmap.c
+1 -2
@@ -2039,12 +2039,11 @@ static int filter_bitmap(struct bitmap_index *bitmap_git,
2039 return -1;
2040 }
2041
2042 -static int can_filter_bitmap(const struct list_objects_filter_options *filter)
2042 +bool can_filter_bitmap(const struct list_objects_filter_options *filter)
2043 {
2044 return !filter_bitmap(NULL, NULL, NULL, filter);
2045 }
2046
2047 -
2047 static void filter_packed_objects_from_bitmap(struct bitmap_index *bitmap_git,
2048 struct bitmap *result)
2049 {
pack-bitmap.h
+3
@@ -92,6 +92,9 @@ int test_bitmap_pseudo_merge_objects(struct repository *r, uint32_t n);
92
93 struct list_objects_filter_options;
94
95 +/* Check whether the filter can be computed via the bitmap. */
96 +bool can_filter_bitmap(const struct list_objects_filter_options *filter);
97 +
98 /*
99 * Filter bitmapped objects and iterate through all resulting objects,
100 * executing `show_reach` for each of them. Returns `-1` in case the filter is