{"thread":{"id":"66217","subject":"[PATCH 00/10] odb: make consistency checks pluggable","startedAt":"2026-08-25T14:30:16Z","lastAt":"2026-09-11T13:27:55Z","messageCount":54,"participants":["Patrick Steinhardt","Karthik Nayak","Toon Claes"],"isPatch":true,"patchVersion":1,"patchTotal":10},"messages":[{"id":"551182","messageId":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":null,"subject":"[PATCH 00/10] odb: make consistency checks pluggable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:02Z","receivedAt":"2026-08-25T14:30:16Z","isPatch":true,"body":"Hi,\n\nthis patch series makes object database consistency checks pluggable.\n\nThis series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\nwith the following two dependencsie merged into it:\n\n  - ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n    `alternates_db` field, 2026-08-17)\n\n  - ps/odb-pluggable-pack-generation at 5176dd3d05 (bundle: generate\n    packfiles via the object database, 2026-08-21)\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (10):\n      builtin/fsck: use `fsck_obj_buffer()` when checking loose objects\n      builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`\n      builtin/fsck: de-globalize option handling\n      builtin/fsck: don't check alternates with \"--no-full\"\n      odb: provide infrastructure for pluggable fsck checks\n      builtin/fsck: move packfile verification into the packed source\n      builtin/fsck: move reverse index verification into the packed source\n      builtin/fsck: move bitmap verification into the packed source\n      builtin/fsck: move multi-pack index verification into the packed source\n      builtin/fsck: move loose object verification into the loose source\n\n builtin/fsck.c                | 296 ++++++++----------------------------------\n odb.c                         |   9 ++\n odb.h                         |  33 +++++\n odb/source-files.c            |  13 ++\n odb/source-inmemory.c         |   8 ++\n odb/source-loose.c            |  92 +++++++++++++\n odb/source-packed.c           | 117 +++++++++++++++++\n odb/source.h                  |  21 +++\n pack-bitmap.c                 |  26 ++--\n pack-bitmap.h                 |   2 +-\n t/t1450-fsck.sh               |   5 +\n t/t5319-multi-pack-index.sh   |  13 ++\n t/t5325-reverse-index.sh      |   8 ++\n t/t5326-multi-pack-bitmaps.sh |  10 +-\n 14 files changed, 394 insertions(+), 259 deletions(-)\n\n\n---\nbase-commit: 6b08999fb1b3ad0bad04d492dc206ad42839e274\nchange-id: 20260810-pks-odb-source-fsck-e64772c7ee5f\n\n"},{"id":"551183","messageId":"20260825-pks-odb-source-fsck-v1-1-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH 01/10] builtin/fsck: use `fsck_obj_buffer()` when checking loose objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:03Z","receivedAt":"2026-08-25T14:30:18Z","isPatch":true,"body":"When checking loose objects we manually parse the object buffer we have\nread from the on-disk file, mark the object and then call `fsck_obj()`.\nAlmost the exact same steps are also performed by `fsck_obj_buffer()`.\n\nStop open-coding this logic and call `fsck_obj_buffer()` instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 17 +----------------\n 1 file changed, 1 insertion(+), 16 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 892c5661d9..3c4127f4d8 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -722,7 +722,6 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n \t\t      void *cb_data)\n {\n \tstruct for_each_loose_cb *data = cb_data;\n-\tstruct object *obj;\n \tenum object_type type = OBJ_NONE;\n \tsize_t size;\n \tvoid *contents = NULL;\n@@ -751,21 +750,7 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n \tif (!contents && type != OBJ_BLOB)\n \t\tBUG(\"read_loose_object streamed a non-blob\");\n \n-\tobj = parse_object_buffer(data->repo, oid, type, size,\n-\t\t\t\t  contents, &eaten);\n-\n-\tif (!obj) {\n-\t\terrors_found |= ERROR_OBJECT;\n-\t\terror(_(\"%s: object could not be parsed: %s\"),\n-\t\t      oid_to_hex(oid), path);\n-\t\tif (!eaten)\n-\t\t\tfree(contents);\n-\t\treturn 0; /* keep checking other objects */\n-\t}\n-\n-\tobj->flags &= ~(REACHABLE | SEEN);\n-\tobj->flags |= HAS_OBJ;\n-\tif (fsck_obj(data->repo, obj, contents, size))\n+\tif (fsck_obj_buffer(oid, type, size, contents, &eaten, data->repo))\n \t\terrors_found |= ERROR_OBJECT;\n \n \tif (!eaten)\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551184","messageId":"20260825-pks-odb-source-fsck-v1-2-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH 02/10] builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:04Z","receivedAt":"2026-08-25T14:30:20Z","isPatch":true,"body":"The interfaces of the functions `fsck_obj()` and `fsck_obj_buffer()` are\nsomewhat similar to one another. The only difference between those two\nis that `fsck_obj()` takes an already-parsed object as input, whereas\n`fsck_obj_buffer()` parses the buffer and then calls `fsck_obj()`.\n\nFurthermore, `fsck_obj()` has no callers other than `fsck_obj_buffer()`.\n\nRefactor the code by merging those two functions. This makes it obvious\nwhich function does what, and it allows us to get rid of the early in\n`fsck_obj()` in case `SEEN` is set as the only caller unconditionally\nclears that bit before calling it anyway.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 47 ++++++++++++++++++++---------------------------\n 1 file changed, 20 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 3c4127f4d8..bed8481893 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -401,14 +401,27 @@ static void check_connectivity(struct repository *repo)\n \t}\n }\n \n-static int fsck_obj(struct repository *repo,\n-\t\t    struct object *obj, void *buffer, unsigned long size)\n+static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,\n+\t\t\t   unsigned long size, void *buffer, int *eaten, void *cb_data)\n {\n+\tstruct repository *repo = cb_data;\n+\tstruct object *obj;\n \tint err;\n \n-\tif (obj->flags & SEEN)\n-\t\treturn 0;\n-\tobj->flags |= SEEN;\n+\t/*\n+\t * Note, buffer may be NULL if type is OBJ_BLOB. See\n+\t * verify_packfile(), data_valid variable for details.\n+\t */\n+\tobj = parse_object_buffer(repo, oid, type, size, buffer, eaten);\n+\tif (!obj) {\n+\t\terrors_found |= ERROR_OBJECT;\n+\t\terr = error(_(\"%s: object corrupt or missing\"),\n+\t\t\t    oid_to_hex(oid));\n+\t\tgoto out;\n+\t}\n+\n+\tobj->flags &= ~REACHABLE;\n+\tobj->flags |= HAS_OBJ | SEEN;\n \n \tif (verbose)\n \t\tfprintf_ln(stderr, _(\"Checking %s %s\"),\n@@ -417,6 +430,7 @@ static int fsck_obj(struct repository *repo,\n \n \tif (fsck_walk(obj, NULL, &fsck_obj_options))\n \t\tobjerror(repo, obj, _(\"broken links\"));\n+\n \terr = fsck_object(obj, buffer, size, &fsck_obj_options);\n \tif (err)\n \t\tgoto out;\n@@ -442,32 +456,11 @@ static int fsck_obj(struct repository *repo,\n \t}\n \n out:\n-\tif (obj->type == OBJ_TREE)\n+\tif (obj && obj->type == OBJ_TREE)\n \t\tfree_tree_buffer((struct tree *)obj);\n \treturn err;\n }\n \n-static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,\n-\t\t\t   unsigned long size, void *buffer, int *eaten, void *cb_data)\n-{\n-\tstruct repository *repo = cb_data;\n-\tstruct object *obj;\n-\n-\t/*\n-\t * Note, buffer may be NULL if type is OBJ_BLOB. See\n-\t * verify_packfile(), data_valid variable for details.\n-\t */\n-\tobj = parse_object_buffer(repo, oid, type, size, buffer, eaten);\n-\tif (!obj) {\n-\t\terrors_found |= ERROR_OBJECT;\n-\t\treturn error(_(\"%s: object corrupt or missing\"),\n-\t\t\t     oid_to_hex(oid));\n-\t}\n-\tobj->flags &= ~(REACHABLE | SEEN);\n-\tobj->flags |= HAS_OBJ;\n-\treturn fsck_obj(repo, obj, buffer, size);\n-}\n-\n static int default_refs;\n \n static void fsck_handle_reflog_oid(struct repository *repo,\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551185","messageId":"20260825-pks-odb-source-fsck-v1-3-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH 03/10] builtin/fsck: de-globalize option handling","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:05Z","receivedAt":"2026-08-25T14:30:21Z","isPatch":true,"body":"In subsequent commits we're about to rework some of the option handling\nin git-fsck(1) a bit. It is currently a bit of a mess though due to lots\nof global state that makes it hard to see which flags are used where\nexactly.\n\nRefactor the code by moving the fsck options into `cmd_fsck()`. This\nallows us to convert some of the options into function-local variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 45 ++++++++++++++++++++++-----------------------\n 1 file changed, 22 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex bed8481893..5132ff0f15 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -37,10 +37,8 @@ static int show_root;\n static int show_tags;\n static int show_unreachable;\n static int include_reflogs = 1;\n-static int check_full = 1;\n static int connectivity_only;\n static int check_strict;\n-static int keep_cache_objects;\n static struct fsck_options fsck_walk_options;\n static struct fsck_options fsck_obj_options;\n static int errors_found;\n@@ -48,8 +46,6 @@ static int write_lost_and_found;\n static int verbose;\n static int show_progress = -1;\n static int show_dangling = 1;\n-static int name_objects;\n-static int check_references = 1;\n static timestamp_t now;\n #define ERROR_OBJECT 01\n #define ERROR_REACHABLE 02\n@@ -964,30 +960,33 @@ static char const * const fsck_usage[] = {\n \tNULL\n };\n \n-static struct option fsck_opts[] = {\n-\tOPT__VERBOSE(&verbose, N_(\"be verbose\")),\n-\tOPT_BOOL(0, \"unreachable\", &show_unreachable, N_(\"show unreachable objects\")),\n-\tOPT_BOOL(0, \"dangling\", &show_dangling, N_(\"show dangling objects\")),\n-\tOPT_BOOL(0, \"tags\", &show_tags, N_(\"report tags\")),\n-\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n-\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n-\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n-\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n-\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n-\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n-\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n-\t\t\t\tN_(\"write dangling objects in .git/lost-found\")),\n-\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n-\tOPT_BOOL(0, \"name-objects\", &name_objects, N_(\"show verbose names for reachable objects\")),\n-\tOPT_BOOL(0, \"references\", &check_references, N_(\"check reference database consistency\")),\n-\tOPT_END(),\n-};\n-\n int cmd_fsck(int argc,\n \t     const char **argv,\n \t     const char *prefix,\n \t     struct repository *repo)\n {\n+\tint check_full = 1;\n+\tint keep_cache_objects = 0;\n+\tint name_objects = 0;\n+\tint check_references = 1;\n+\tstruct option fsck_opts[] = {\n+\t\tOPT__VERBOSE(&verbose, N_(\"be verbose\")),\n+\t\tOPT_BOOL(0, \"unreachable\", &show_unreachable, N_(\"show unreachable objects\")),\n+\t\tOPT_BOOL(0, \"dangling\", &show_dangling, N_(\"show dangling objects\")),\n+\t\tOPT_BOOL(0, \"tags\", &show_tags, N_(\"report tags\")),\n+\t\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n+\t\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n+\t\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n+\t\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n+\t\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n+\t\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n+\t\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n+\t\t\t\t\tN_(\"write dangling objects in .git/lost-found\")),\n+\t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"name-objects\", &name_objects, N_(\"show verbose names for reachable objects\")),\n+\t\tOPT_BOOL(0, \"references\", &check_references, N_(\"check reference database consistency\")),\n+\t\tOPT_END(),\n+\t};\n \tstruct odb_source *source;\n \tstruct snapshot snap = {\n \t\t.nr = 0,\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551186","messageId":"20260825-pks-odb-source-fsck-v1-4-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH 04/10] builtin/fsck: don't check alternates with \"--no-full\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:06Z","receivedAt":"2026-08-25T14:30:25Z","isPatch":true,"body":"According to git-fsck(1), the \"--full\" option behaves in the following\nway:\n\n  Check not just objects in GIT_OBJECT_DIRECTORY ($GIT_DIR/objects), but\n  also the ones found in alternate object pools listed in\n  GIT_ALTERNATE_OBJECT_DIRECTORIES or $GIT_DIR/objects/info/alternates,\n  and in packed Git archives found in $GIT_DIR/objects/pack and\n  corresponding pack subdirectories in alternate object pools.\n\nSo ultimately, it is supposed to control two things: (1) whether we only\ncheck the main object directory, and (2) whether we check packfiles.\n\nIn its current state though, the flag only controls whether we check\npackfiles or not, and if so we verify packfiles of all attached sources.\nBut we also have checks for loose objects in git-fsck(1), and here we\nunconditionally check them in all sources.\n\nThe flag is arguably conflating two unrelated concerns with one another,\nand it really should be split up into two flags: one that controls how\nthorough we want to check individual sources, and one that controls\nwhich sources we want to check in the first place. So ideally, we would\nhave:\n\n  - \"--include-alternates\": check all sources, not only the local one.\n\n  - \"--include-optimized-objects\": check not only loose objects, but\n    also those that have been packed. Note that we explicitly don't say\n    \"--include-packed-objects\" here to be more backend-agnostic.\n\n  - \"--full\": implies both of the above flags.\n\nThis feels out of scope for this series though. So for now, simply fix\nthe code by honoring locality of the sources for loose objects.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c  | 3 ++-\n t/t1450-fsck.sh | 5 +++++\n 2 files changed, 7 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 5132ff0f15..3f6056535f 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -1047,7 +1047,8 @@ int cmd_fsck(int argc,\n \t\t\t\t    mark_object_for_connectivity, repo, 0);\n \t} else {\n \t\tfor (source = repo->objects->sources; source; source = source->next)\n-\t\t\tfsck_source(repo, source);\n+\t\t\tif (check_full || source->local)\n+\t\t\t\tfsck_source(repo, source);\n \n \t\tif (check_full) {\n \t\t\tstruct packed_git *p;\ndiff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh\nindex 77cd96de78..1b4074304c 100755\n--- a/t/t1450-fsck.sh\n+++ b/t/t1450-fsck.sh\n@@ -844,6 +844,11 @@ test_expect_success 'alternate objects are correctly blamed' '\n \techo \"../../alt.git/objects\" >.git/objects/info/alternates &&\n \tmkdir alt.git/objects/$(dirname $path) &&\n \t>alt.git/objects/$(dirname $path)/$(basename $path) &&\n+\n+\t# Without \"--full\", only the local object source is checked.\n+\tgit fsck --no-full >out 2>&1 &&\n+\ttest_must_be_empty out &&\n+\n \ttest_must_fail git fsck >out 2>&1 &&\n \ttest_grep alt.git out\n '\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551187","messageId":"20260825-pks-odb-source-fsck-v1-5-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH 05/10] odb: provide infrastructure for pluggable fsck checks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:07Z","receivedAt":"2026-08-25T14:30:27Z","isPatch":true,"body":"The on-disk consistency checks in git-fsck(1) are conceptually\nbackend-specific: while connectivity checks and object-level parsing\nchecks are generic, verifying the physical integrity of packfiles and\nloose objects is meaningful only to backends that use these formats:\nHaving these checks live in \"builtin/fsck.c\" violates that layering,\nbecause it forces the command to reach directly into format-specific\ninternals.\n\nProvide new infrastructure to make these format-specific checks\npluggable and implement stubs for the different source types we already\nhave. In subsequent commits we'll move functionality over piece by\npiece.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c        | 16 +++++++++++-----\n odb.c                 |  9 +++++++++\n odb.h                 | 23 +++++++++++++++++++++++\n odb/source-files.c    | 13 +++++++++++++\n odb/source-inmemory.c |  8 ++++++++\n odb/source-loose.c    |  7 +++++++\n odb/source-packed.c   |  8 ++++++++\n odb/source.h          | 21 +++++++++++++++++++++\n 8 files changed, 100 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 3f6056535f..adbe192e56 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -965,7 +965,9 @@ int cmd_fsck(int argc,\n \t     const char *prefix,\n \t     struct repository *repo)\n {\n-\tint check_full = 1;\n+\tstruct odb_fsck_options odb_fsck_opts = {\n+\t\t.flags = ODB_FSCK_FULL,\n+\t};\n \tint keep_cache_objects = 0;\n \tint name_objects = 0;\n \tint check_references = 1;\n@@ -977,7 +979,8 @@ int cmd_fsck(int argc,\n \t\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n \t\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n \t\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n-\t\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n+\t\tOPT_BIT(0, \"full\", &odb_fsck_opts.flags,\n+\t\t\tN_(\"also consider packs and alternate objects\"), ODB_FSCK_FULL),\n \t\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n \t\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n \t\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n@@ -1018,7 +1021,7 @@ int cmd_fsck(int argc,\n \t\tshow_progress = 0;\n \n \tif (write_lost_and_found) {\n-\t\tcheck_full = 1;\n+\t\todb_fsck_opts.flags |= ODB_FSCK_FULL;\n \t\tinclude_reflogs = 0;\n \t}\n \n@@ -1047,10 +1050,13 @@ int cmd_fsck(int argc,\n \t\t\t\t    mark_object_for_connectivity, repo, 0);\n \t} else {\n \t\tfor (source = repo->objects->sources; source; source = source->next)\n-\t\t\tif (check_full || source->local)\n+\t\t\tif ((odb_fsck_opts.flags & ODB_FSCK_FULL) || source->local)\n \t\t\t\tfsck_source(repo, source);\n \n-\t\tif (check_full) {\n+\t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n+\t\t\terrors_found |= ERROR_OBJECT;\n+\n+\t\tif (odb_fsck_opts.flags & ODB_FSCK_FULL) {\n \t\t\tstruct packed_git *p;\n \t\t\tuint32_t total = 0, count = 0;\n \t\t\tstruct progress *progress = NULL;\ndiff --git a/odb.c b/odb.c\nindex 1fe20808eb..766043b685 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -1177,3 +1177,12 @@ void odb_reprepare(struct object_database *o)\n {\n \todb_prepare(o, ODB_PREPARE_FLUSH_CACHES);\n }\n+\n+int odb_fsck(struct object_database *odb, struct odb_fsck_options *options)\n+{\n+\tint ret = 0;\n+\tfor (struct odb_source *source = odb->sources; source; source = source->next)\n+\t\tif ((options->flags & ODB_FSCK_FULL) || source->local)\n+\t\t\tret |= odb_source_fsck(source, options);\n+\treturn ret;\n+}\ndiff --git a/odb.h b/odb.h\nindex e60174070f..76c15e48f5 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -206,6 +206,29 @@ void odb_prepare(struct object_database *o, enum odb_prepare_flags flags);\n /* Equivalent to `odb_prepare(o, ODB_PREPARE_FLUSH_CACHES)`. */\n void odb_reprepare(struct object_database *o);\n \n+enum odb_fsck_flags {\n+\t/*\n+\t * If set, perform a full consistency check for the full object\n+\t * database, including all of its sources and the contents of their\n+\t * optimized formats. Otherwise, only check the local source, and\n+\t * restrict checks of its optimized formats to cheap structural\n+\t * verification of their metadata.\n+\t */\n+\tODB_FSCK_FULL = (1 << 0),\n+};\n+\n+/* Options that shall be passed to `odb_fsck()`. */\n+struct odb_fsck_options {\n+\tenum odb_fsck_flags flags;\n+};\n+\n+/*\n+ * Run backend-specific integrity checks on all object sources. Each source\n+ * performs the checks appropriate to its type. Returns 0 on success, a\n+ * negative error code otherwise.\n+ */\n+int odb_fsck(struct object_database *odb, struct odb_fsck_options *opts);\n+\n /*\n  * Find source by its object directory path. Returns a `NULL` pointer in case\n  * the source could not be found.\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex bd4fdf3a6c..f6fb560d2e 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -893,6 +893,18 @@ static int odb_source_files_generate_pack(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n+static int odb_source_files_fsck(struct odb_source *source,\n+\t\t\t\t struct odb_fsck_options *opts)\n+{\n+\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\tint ret = 0;\n+\n+\tret |= odb_source_fsck(&files->loose->base, opts);\n+\tret |= odb_source_fsck(&files->packed->base, opts);\n+\n+\treturn ret;\n+}\n+\n struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \t\t\t\t\t      const char *path,\n \t\t\t\t\t      bool local)\n@@ -908,6 +920,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tfiles->base.close = odb_source_files_close;\n \tfiles->base.create_on_disk = odb_source_files_create_on_disk;\n \tfiles->base.prepare = odb_source_files_prepare;\n+\tfiles->base.fsck = odb_source_files_fsck;\n \tfiles->base.read_object_info = odb_source_files_read_object_info;\n \tfiles->base.read_object_stream = odb_source_files_read_object_stream;\n \tfiles->base.for_each_object = odb_source_files_for_each_object;\ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex 795672adf2..ba0f86da26 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"object-file.h\"\n #include \"odb.h\"\n+#include \"fsck.h\"\n #include \"odb/source-inmemory.h\"\n #include \"odb/streaming.h\"\n #include \"oidtree.h\"\n@@ -368,6 +369,12 @@ static void odb_source_inmemory_free(struct odb_source *source)\n \tfree(inmemory);\n }\n \n+static int odb_source_inmemory_fsck(struct odb_source *source UNUSED,\n+\t\t\t\t    struct odb_fsck_options *opts UNUSED)\n+{\n+\treturn 0;\n+}\n+\n struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n {\n \tstruct odb_source_inmemory *source;\n@@ -378,6 +385,7 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n \tsource->base.free = odb_source_inmemory_free;\n \tsource->base.close = odb_source_inmemory_close;\n \tsource->base.prepare = odb_source_inmemory_prepare;\n+\tsource->base.fsck = odb_source_inmemory_fsck;\n \tsource->base.read_object_info = odb_source_inmemory_read_object_info;\n \tsource->base.read_object_stream = odb_source_inmemory_read_object_stream;\n \tsource->base.for_each_object = odb_source_inmemory_for_each_object;\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex bb3455dfbd..f68d3c4d6c 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -1031,6 +1031,12 @@ static void odb_source_loose_free(struct odb_source *source)\n \tfree(loose);\n }\n \n+static int odb_source_loose_fsck(struct odb_source *source UNUSED,\n+\t\t\t\t struct odb_fsck_options *opts UNUSED)\n+{\n+\treturn 0;\n+}\n+\n struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \t\t\t\t\t      const char *path,\n \t\t\t\t\t      bool local)\n@@ -1043,6 +1049,7 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \tloose->base.free = odb_source_loose_free;\n \tloose->base.close = odb_source_loose_close;\n \tloose->base.prepare = odb_source_loose_prepare;\n+\tloose->base.fsck = odb_source_loose_fsck;\n \tloose->base.read_object_info = odb_source_loose_read_object_info;\n \tloose->base.read_object_stream = odb_source_loose_read_object_stream;\n \tloose->base.for_each_object = odb_source_loose_for_each_object;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 630d955585..7aacf4bc45 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -2,6 +2,7 @@\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n #include \"dir.h\"\n+#include \"fsck.h\"\n #include \"git-zlib.h\"\n #include \"list-objects-filter-options.h\"\n #include \"mergesort.h\"\n@@ -826,6 +827,12 @@ static void odb_source_packed_free(struct odb_source *source)\n \tfree(packed);\n }\n \n+static int odb_source_packed_fsck(struct odb_source *source UNUSED,\n+\t\t\t\t  struct odb_fsck_options *opts UNUSED)\n+{\n+\treturn 0;\n+}\n+\n struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \t\t\t\t\t\tconst char *path,\n \t\t\t\t\t\tbool local)\n@@ -839,6 +846,7 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \tpacked->base.free = odb_source_packed_free;\n \tpacked->base.close = odb_source_packed_close;\n \tpacked->base.prepare = odb_source_packed_prepare;\n+\tpacked->base.fsck = odb_source_packed_fsck;\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\ndiff --git a/odb/source.h b/odb/source.h\nindex 559e2ea2e9..10a5dd5194 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -320,6 +320,17 @@ struct odb_source {\n \tint (*generate_pack)(struct odb_source *source,\n \t\t\t     struct odb_pack_generator **out,\n \t\t\t     const struct odb_generate_pack_options *opts);\n+\n+\t/*\n+\t * This callback is expected to check the integrity of the object source\n+\t * and report any errors found via the fsck options. The checks performed\n+\t * are backend-specific.\n+\t *\n+\t * The callback is expected to return 0 on success, a negative error\n+\t * code otherwise.\n+\t */\n+\tint (*fsck)(struct odb_source *source,\n+\t\t    struct odb_fsck_options *options);\n };\n \n /*\n@@ -588,4 +599,14 @@ static inline int odb_source_generate_pack(struct odb_source *source,\n \treturn source->generate_pack(source, out, opts);\n }\n \n+/*\n+ * Check the integrity of the object database source. The checks performed\n+ * are backend-specific. Returns 0 on success, a negative error code otherwise.\n+ */\n+static inline int odb_source_fsck(struct odb_source *source,\n+\t\t\t\t  struct odb_fsck_options *opts)\n+{\n+\treturn source->fsck(source, opts);\n+}\n+\n #endif\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551188","messageId":"20260825-pks-odb-source-fsck-v1-6-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH 06/10] builtin/fsck: move packfile verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:08Z","receivedAt":"2026-08-25T14:30:29Z","isPatch":true,"body":"Move the packfile verification out of `cmd_fsck()` and into the \"packed\"\nsource. While doing so, thread the progress meter and object callback\nthrough the newly introduced `struct odb_fsck_options` so that the\ncaller's preferences are honoured without exposing those details at the\n\"builtin/fsck.c\" level.\n\nNote that the old code reported failures when verifying packfiles with\nthe `ERROR_PACK` bit, which gets returned to the caller via the exit\ncode. This bit is neither exercised in our test suite nor is it\ndocumented anywhere in our codebase. Furthermore, this bit is highly\nspecific to the object storage backend, which makes it a bad fit for the\nnew pluggable infrastructure. So instead of retaining these semantics,\nwe drop them and return the generic `ERROR_OBJECT` bit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c      | 33 ++++-----------------------------\n odb.h               |  7 +++++++\n odb/source-packed.c | 46 +++++++++++++++++++++++++++++++++++++++++++---\n 3 files changed, 54 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex adbe192e56..e504dae904 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -7,7 +7,6 @@\n #include \"blob.h\"\n #include \"tag.h\"\n #include \"refs.h\"\n-#include \"pack.h\"\n #include \"cache-tree.h\"\n #include \"fsck.h\"\n #include \"parse-options.h\"\n@@ -49,7 +48,6 @@ static int show_dangling = 1;\n static timestamp_t now;\n #define ERROR_OBJECT 01\n #define ERROR_REACHABLE 02\n-#define ERROR_PACK 04\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n #define ERROR_MULTI_PACK_INDEX 040\n@@ -967,6 +965,8 @@ int cmd_fsck(int argc,\n {\n \tstruct odb_fsck_options odb_fsck_opts = {\n \t\t.flags = ODB_FSCK_FULL,\n+\t\t.object_cb = fsck_obj_buffer,\n+\t\t.object_payload = repo,\n \t};\n \tint keep_cache_objects = 0;\n \tint name_objects = 0;\n@@ -1019,6 +1019,8 @@ int cmd_fsck(int argc,\n \t\tshow_progress = isatty(2);\n \tif (verbose)\n \t\tshow_progress = 0;\n+\tif (show_progress)\n+\t\todb_fsck_opts.flags |= ODB_FSCK_PROGRESS;\n \n \tif (write_lost_and_found) {\n \t\todb_fsck_opts.flags |= ODB_FSCK_FULL;\n@@ -1056,33 +1058,6 @@ int cmd_fsck(int argc,\n \t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n \t\t\terrors_found |= ERROR_OBJECT;\n \n-\t\tif (odb_fsck_opts.flags & ODB_FSCK_FULL) {\n-\t\t\tstruct packed_git *p;\n-\t\t\tuint32_t total = 0, count = 0;\n-\t\t\tstruct progress *progress = NULL;\n-\n-\t\t\tif (show_progress) {\n-\t\t\t\trepo_for_each_pack(repo, p) {\n-\t\t\t\t\tif (open_pack_index(p))\n-\t\t\t\t\t\tcontinue;\n-\t\t\t\t\ttotal += p->num_objects;\n-\t\t\t\t}\n-\n-\t\t\t\tprogress = start_progress(repo,\n-\t\t\t\t\t\t\t  _(\"Checking objects\"), total);\n-\t\t\t}\n-\n-\t\t\trepo_for_each_pack(repo, p) {\n-\t\t\t\t/* verify gives error messages itself */\n-\t\t\t\tif (verify_pack(repo,\n-\t\t\t\t\t\tp, fsck_obj_buffer, repo,\n-\t\t\t\t\t\tprogress, count))\n-\t\t\t\t\terrors_found |= ERROR_PACK;\n-\t\t\t\tcount += p->num_objects;\n-\t\t\t}\n-\t\t\tstop_progress(&progress);\n-\t\t}\n-\n \t\tif (fsck_finish(&fsck_obj_options))\n \t\t\terrors_found |= ERROR_OBJECT;\n \t}\ndiff --git a/odb.h b/odb.h\nindex 76c15e48f5..0bf6c8d7d2 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -215,11 +215,18 @@ enum odb_fsck_flags {\n \t * verification of their metadata.\n \t */\n \tODB_FSCK_FULL = (1 << 0),\n+\n+\t/* Display a progress meter, if sensible. */\n+\tODB_FSCK_PROGRESS = (1 << 1),\n };\n \n /* Options that shall be passed to `odb_fsck()`. */\n struct odb_fsck_options {\n \tenum odb_fsck_flags flags;\n+\n+\tint (*object_cb)(const struct object_id *oid, enum object_type type,\n+\t\t\t unsigned long size, void *buffer, int *eaten, void *cb_data);\n+\tvoid *object_payload;\n };\n \n /*\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 7aacf4bc45..0d3599f8fe 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -9,8 +9,10 @@\n #include \"midx.h\"\n #include \"odb/source-packed.h\"\n #include \"odb/streaming.h\"\n+#include \"pack.h\"\n #include \"packfile.h\"\n #include \"pack-bitmap.h\"\n+#include \"progress.h\"\n \n static int find_pack_entry(struct odb_source_packed *store,\n \t\t\t   const struct object_id *oid,\n@@ -827,10 +829,48 @@ static void odb_source_packed_free(struct odb_source *source)\n \tfree(packed);\n }\n \n-static int odb_source_packed_fsck(struct odb_source *source UNUSED,\n-\t\t\t\t  struct odb_fsck_options *opts UNUSED)\n+static int verify_packs(struct odb_source_packed *source,\n+\t\t\tstruct odb_fsck_options *opts)\n {\n-\treturn 0;\n+\tstruct progress *progress = NULL;\n+\tstruct packfile_list_entry *e;\n+\tuint32_t total = 0, count = 0;\n+\tint ret = 0;\n+\n+\tif (opts->flags & ODB_FSCK_PROGRESS) {\n+\t\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\t\tif (open_pack_index(e->pack))\n+\t\t\t\tcontinue;\n+\t\t\ttotal += e->pack->num_objects;\n+\t\t}\n+\n+\t\tprogress = start_progress(source->base.odb->repo,\n+\t\t\t\t\t  _(\"Checking objects\"), total);\n+\t}\n+\n+\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\t/* verify gives error messages itself */\n+\t\tif (verify_pack(source->base.odb->repo, e->pack,\n+\t\t\t\topts->object_cb, opts->object_payload,\n+\t\t\t\tprogress, count))\n+\t\t\tret = -1;\n+\t\tcount += e->pack->num_objects;\n+\t}\n+\tstop_progress(&progress);\n+\n+\treturn ret;\n+}\n+\n+static int odb_source_packed_fsck(struct odb_source *source,\n+\t\t\t\t  struct odb_fsck_options *opts)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tint ret = 0;\n+\n+\tif ((opts->flags & ODB_FSCK_FULL) && verify_packs(packed, opts) < 0)\n+\t\tret = -1;\n+\n+\treturn ret;\n }\n \n struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551189","messageId":"20260825-pks-odb-source-fsck-v1-7-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH 07/10] builtin/fsck: move reverse index verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:09Z","receivedAt":"2026-08-25T14:30:30Z","isPatch":true,"body":"The checks for reverse indexes live in `check_pack_rev_indexes()`, which\nis hosted in \"builtin/fsck.c\". These checks are obviously specific to\nthe \"packed\" backend.\n\nMove the logic into `odb_source_packed_fsck()`. As in the preceding\ncommit, drop the dedicated `ERROR_PACK_REV_INDEX` bit and instead use\nthe generic `ERROR_OBJECT` bit.\n\nNote that this changes behaviour in two ways:\n\n  - The checks are now skipped when \"--connectivity-only\" was passed.\n    This is because we don't even run `odb_fsck()` at all when that\n    flag has been passed by the user, and not verifying data structures\n    of the object database matches the documented intent of that flag,\n    which is to only check the connectivity of reachable objects.\n\n  - The checks are now skipped for non-local sources when \"--no-full\"\n    was passed. This is, again, in line with the documented intent of\n    that flag.\n\nAdd a test to cast these semantics into stone.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c           | 37 -------------------------------------\n odb/source-packed.c      | 39 +++++++++++++++++++++++++++++++++++++++\n t/t5325-reverse-index.sh |  8 ++++++++\n 3 files changed, 47 insertions(+), 37 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex e504dae904..06e72877f3 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -23,7 +23,6 @@\n #include \"run-command.h\"\n #include \"sparse-index.h\"\n #include \"worktree.h\"\n-#include \"pack-revindex.h\"\n #include \"pack-bitmap.h\"\n \n #define REACHABLE 0x0001\n@@ -51,7 +50,6 @@ static timestamp_t now;\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n #define ERROR_MULTI_PACK_INDEX 040\n-#define ERROR_PACK_REV_INDEX 0100\n #define ERROR_BITMAP 0200\n \n static const char *describe_object(const struct object_id *oid)\n@@ -890,40 +888,6 @@ static int mark_object_for_connectivity(const struct object_id *oid,\n \treturn 0;\n }\n \n-static int check_pack_rev_indexes(struct repository *r, int show_progress)\n-{\n-\tstruct progress *progress = NULL;\n-\tstruct packed_git *p;\n-\tuint32_t pack_count = 0;\n-\tint res = 0;\n-\n-\tif (show_progress) {\n-\t\trepo_for_each_pack(r, p)\n-\t\t\tpack_count++;\n-\t\tprogress = start_delayed_progress(r,\n-\t\t\t\t\t\t  \"Verifying reverse pack-indexes\", pack_count);\n-\t\tpack_count = 0;\n-\t}\n-\n-\trepo_for_each_pack(r, p) {\n-\t\tint load_error = load_pack_revindex_from_disk(p);\n-\n-\t\tif (load_error < 0) {\n-\t\t\terror(_(\"unable to load rev-index for pack '%s'\"), p->pack_name);\n-\t\t\tres = ERROR_PACK_REV_INDEX;\n-\t\t} else if (!load_error &&\n-\t\t\t   !load_pack_revindex(r, p) &&\n-\t\t\t   verify_pack_revindex(p)) {\n-\t\t\terror(_(\"invalid rev-index for pack '%s'\"), p->pack_name);\n-\t\t\tres = ERROR_PACK_REV_INDEX;\n-\t\t}\n-\t\tdisplay_progress(progress, ++pack_count);\n-\t}\n-\tstop_progress(&progress);\n-\n-\treturn res;\n-}\n-\n static void fsck_refs(struct repository *r)\n {\n \tstruct child_process refs_verify = CHILD_PROCESS_INIT;\n@@ -1104,7 +1068,6 @@ int cmd_fsck(int argc,\n \t\tfree_worktrees(worktrees);\n \t}\n \n-\terrors_found |= check_pack_rev_indexes(repo, show_progress);\n \tif (verify_bitmap_files(repo))\n \t\terrors_found |= ERROR_BITMAP;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 0d3599f8fe..e5e69636dd 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -10,6 +10,7 @@\n #include \"odb/source-packed.h\"\n #include \"odb/streaming.h\"\n #include \"pack.h\"\n+#include \"pack-revindex.h\"\n #include \"packfile.h\"\n #include \"pack-bitmap.h\"\n #include \"progress.h\"\n@@ -861,6 +862,41 @@ static int verify_packs(struct odb_source_packed *source,\n \treturn ret;\n }\n \n+static int verify_reverse_indices(struct odb_source_packed *source,\n+\t\t\t\t  struct odb_fsck_options *opts)\n+{\n+\tstruct progress *progress = NULL;\n+\tstruct packfile_list_entry *e;\n+\tuint32_t pack_count = 0;\n+\tint res = 0;\n+\n+\tif (opts->flags & ODB_FSCK_PROGRESS) {\n+\t\tfor (e = packfile_store_get_packs(source); e; e = e->next)\n+\t\t\tpack_count++;\n+\t\tprogress = start_delayed_progress(source->base.odb->repo,\n+\t\t\t\t\t\t  \"Verifying reverse pack-indexes\", pack_count);\n+\t\tpack_count = 0;\n+\t}\n+\n+\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\tint load_error = load_pack_revindex_from_disk(e->pack);\n+\n+\t\tif (load_error < 0) {\n+\t\t\terror(_(\"unable to load rev-index for pack '%s'\"), e->pack->pack_name);\n+\t\t\tres = -1;\n+\t\t} else if (!load_error &&\n+\t\t\t   !load_pack_revindex(source->base.odb->repo, e->pack) &&\n+\t\t\t   verify_pack_revindex(e->pack)) {\n+\t\t\terror(_(\"invalid rev-index for pack '%s'\"), e->pack->pack_name);\n+\t\t\tres = -1;\n+\t\t}\n+\t\tdisplay_progress(progress, ++pack_count);\n+\t}\n+\tstop_progress(&progress);\n+\n+\treturn res;\n+}\n+\n static int odb_source_packed_fsck(struct odb_source *source,\n \t\t\t\t  struct odb_fsck_options *opts)\n {\n@@ -870,6 +906,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n \tif ((opts->flags & ODB_FSCK_FULL) && verify_packs(packed, opts) < 0)\n \t\tret = -1;\n \n+\tif (verify_reverse_indices(packed, opts) < 0)\n+\t\tret = -1;\n+\n \treturn ret;\n }\n \ndiff --git a/t/t5325-reverse-index.sh b/t/t5325-reverse-index.sh\nindex 5493791938..6b81abf663 100755\n--- a/t/t5325-reverse-index.sh\n+++ b/t/t5325-reverse-index.sh\n@@ -204,4 +204,12 @@ test_expect_success 'fsck catches invalid header: hash function' '\n \t\t\"reverse-index file .* has unsupported hash id\"\n '\n \n+test_expect_success 'fsck --no-full checks rev-index, --connectivity-only does not' '\n+\ttest_must_fail git -C corrupt fsck --no-full 2>err &&\n+\ttest_grep \"has unsupported hash id\" err &&\n+\n+\tgit -C corrupt fsck --connectivity-only 2>err &&\n+\ttest_grep ! \"has unsupported hash id\" err\n+'\n+\n test_done\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551190","messageId":"20260825-pks-odb-source-fsck-v1-8-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH 08/10] builtin/fsck: move bitmap verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:10Z","receivedAt":"2026-08-25T14:30:33Z","isPatch":true,"body":"The checks for bitmaps live in `verify_bitmap_files()`, which is called\nby \"builtin/fsck.c\". These checks are obviously specific to the \"packed\"\nbackend.\n\nMove the logic into `odb_source_packed_fsck()`. As in preceding commits,\nthis means that we now properly honor both \"--connectivity-only\" and\n\"--no-full\". Furthermore, we drop the dedicated `ERROR_BITMAP` bit and\ninstead use the generic `ERROR_OBJECT` bit.\n\nNote that this change also adapts `verify_bitmap_files()` to be\nfocussed on a single \"packed\" source instead of verifying bitmaps from\nall sources. This change is required as we already know to loop around\nthe sources in `odb_fsck()` itself.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c                |  5 -----\n odb/source-packed.c           |  3 +++\n pack-bitmap.c                 | 26 ++++++++++----------------\n pack-bitmap.h                 |  2 +-\n t/t5326-multi-pack-bitmaps.sh | 10 +++++++++-\n 5 files changed, 23 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 06e72877f3..2f7d29aa56 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -23,7 +23,6 @@\n #include \"run-command.h\"\n #include \"sparse-index.h\"\n #include \"worktree.h\"\n-#include \"pack-bitmap.h\"\n \n #define REACHABLE 0x0001\n #define SEEN      0x0002\n@@ -50,7 +49,6 @@ static timestamp_t now;\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n #define ERROR_MULTI_PACK_INDEX 040\n-#define ERROR_BITMAP 0200\n \n static const char *describe_object(const struct object_id *oid)\n {\n@@ -1068,9 +1066,6 @@ int cmd_fsck(int argc,\n \t\tfree_worktrees(worktrees);\n \t}\n \n-\tif (verify_bitmap_files(repo))\n-\t\terrors_found |= ERROR_BITMAP;\n-\n \tcheck_connectivity(repo);\n \n \tif (repo->settings.core_commit_graph) {\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex e5e69636dd..2b5dc502f5 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -909,6 +909,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n \tif (verify_reverse_indices(packed, opts) < 0)\n \t\tret = -1;\n \n+\tif (verify_bitmap_files(packed))\n+\t\tret = -1;\n+\n \treturn ret;\n }\n \ndiff --git a/pack-bitmap.c b/pack-bitmap.c\nindex e0fb57d332..3de8e9590c 100644\n--- a/pack-bitmap.c\n+++ b/pack-bitmap.c\n@@ -3410,28 +3410,22 @@ static int verify_bitmap_file(const struct git_hash_algo *algop,\n \treturn res;\n }\n \n-int verify_bitmap_files(struct repository *r)\n+int verify_bitmap_files(struct odb_source_packed *source)\n {\n-\tstruct odb_source *source;\n-\tstruct packed_git *p;\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n \tint res = 0;\n \n-\tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n-\t\tchar *midx_bitmap_name;\n-\n-\t\tif (!m)\n-\t\t\tcontinue;\n-\n-\t\tmidx_bitmap_name = midx_bitmap_filename(m);\n-\t\tres |= verify_bitmap_file(r->hash_algo, midx_bitmap_name);\n+\tm = get_multi_pack_index(source);\n+\tif (m) {\n+\t\tchar *midx_bitmap_name = midx_bitmap_filename(m);\n+\t\tres |= verify_bitmap_file(source->base.odb->repo->hash_algo, midx_bitmap_name);\n \t\tfree(midx_bitmap_name);\n \t}\n \n-\trepo_for_each_pack(r, p) {\n-\t\tchar *pack_bitmap_name = pack_bitmap_filename(p);\n-\t\tres |= verify_bitmap_file(r->hash_algo, pack_bitmap_name);\n+\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\tchar *pack_bitmap_name = pack_bitmap_filename(e->pack);\n+\t\tres |= verify_bitmap_file(source->base.odb->repo->hash_algo, pack_bitmap_name);\n \t\tfree(pack_bitmap_name);\n \t}\n \ndiff --git a/pack-bitmap.h b/pack-bitmap.h\nindex 1385027c1f..847ad4762d 100644\n--- a/pack-bitmap.h\n+++ b/pack-bitmap.h\n@@ -205,7 +205,7 @@ int bitmap_is_midx(struct bitmap_index *bitmap_git);\n \n int bitmap_is_preferred_refname(struct repository *r, const char *refname);\n \n-int verify_bitmap_files(struct repository *r);\n+int verify_bitmap_files(struct odb_source_packed *source);\n \n struct ewah_bitmap *read_bitmap(const unsigned char *map,\n \t\t\t\tsize_t map_size, size_t *map_pos);\ndiff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh\nindex 86beab1dae..8047459b00 100755\n--- a/t/t5326-multi-pack-bitmaps.sh\n+++ b/t/t5326-multi-pack-bitmaps.sh\n@@ -498,7 +498,15 @@ test_expect_success 'git fsck correctly identifies good and bad bitmaps' '\n \tcorrupt_file \"$packbitmap\" &&\n \ttest_must_fail git fsck 2>err &&\n \ttest_grep \"bitmap file '\\''$midxbitmap'\\'' has invalid checksum\" err &&\n-\ttest_grep \"bitmap file '\\''$packbitmap'\\'' has invalid checksum\" err\n+\ttest_grep \"bitmap file '\\''$packbitmap'\\'' has invalid checksum\" err &&\n+\n+\t# The bitmap checks are performed with \"--no-full\", but not with\n+\t# \"--connectivity-only\".\n+\ttest_must_fail git fsck --no-full 2>err &&\n+\ttest_grep \"bitmap file '\\''$midxbitmap'\\'' has invalid checksum\" err &&\n+\ttest_grep \"bitmap file '\\''$packbitmap'\\'' has invalid checksum\" err &&\n+\tgit fsck --connectivity-only 2>err &&\n+\ttest_grep ! \"invalid checksum\" err\n '\n \n test_expect_success 'corrupt MIDX with bitmap causes fallback' '\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551191","messageId":"20260825-pks-odb-source-fsck-v1-9-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH 09/10] builtin/fsck: move multi-pack index verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:11Z","receivedAt":"2026-08-25T14:30:35Z","isPatch":true,"body":"The checks for multi-pack indexes are hosted in `cmd_fsck()` directly.\nThese checks are obviously specific to the \"packed\" backend.\n\nMove the logic into `odb_source_packed_fsck()`. As in preceding commits,\nthis means that we now properly honor both \"--connectivity-only\" and\n\"--no-full\". Furthermore, we drop the dedicated `ERROR_MULTI_PACK_INDEX`\nbit and instead use the generic `ERROR_OBJECT` bit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c              | 18 ------------------\n odb/source-packed.c         | 27 +++++++++++++++++++++++++++\n t/t5319-multi-pack-index.sh | 13 +++++++++++++\n 3 files changed, 40 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 2f7d29aa56..7eaea340b0 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -48,7 +48,6 @@ static timestamp_t now;\n #define ERROR_REACHABLE 02\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n-#define ERROR_MULTI_PACK_INDEX 040\n \n static const char *describe_object(const struct object_id *oid)\n {\n@@ -1085,23 +1084,6 @@ int cmd_fsck(int argc,\n \t\t}\n \t}\n \n-\tif (repo->settings.core_multi_pack_index) {\n-\t\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n-\n-\t\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\t\tchild_process_init(&midx_verify);\n-\t\t\tmidx_verify.git_cmd = 1;\n-\t\t\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n-\t\t\t\t     \"verify\", \"--object-dir\", source->path, NULL);\n-\t\t\tif (show_progress)\n-\t\t\t\tstrvec_push(&midx_verify.args, \"--progress\");\n-\t\t\telse\n-\t\t\t\tstrvec_push(&midx_verify.args, \"--no-progress\");\n-\t\t\tif (run_command(&midx_verify))\n-\t\t\t\terrors_found |= ERROR_MULTI_PACK_INDEX;\n-\t\t}\n-\t}\n-\n \tfree_snapshot_refs(&snap);\n \treturn errors_found;\n }\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 2b5dc502f5..9f42552377 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -14,6 +14,7 @@\n #include \"packfile.h\"\n #include \"pack-bitmap.h\"\n #include \"progress.h\"\n+#include \"run-command.h\"\n \n static int find_pack_entry(struct odb_source_packed *store,\n \t\t\t   const struct object_id *oid,\n@@ -897,6 +898,29 @@ static int verify_reverse_indices(struct odb_source_packed *source,\n \treturn res;\n }\n \n+static int verify_midx(struct odb_source_packed *source,\n+\t\t       struct odb_fsck_options *opts)\n+{\n+\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n+\tint ret = 0;\n+\n+\tif (!source->base.odb->repo->settings.core_multi_pack_index)\n+\t\treturn 0;\n+\n+\tchild_process_init(&midx_verify);\n+\tmidx_verify.git_cmd = 1;\n+\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n+\t\t     \"verify\", \"--object-dir\", source->base.path, NULL);\n+\tif (opts->flags & ODB_FSCK_PROGRESS)\n+\t\tstrvec_push(&midx_verify.args, \"--progress\");\n+\telse\n+\t\tstrvec_push(&midx_verify.args, \"--no-progress\");\n+\tif (run_command(&midx_verify))\n+\t\tret = -1;\n+\n+\treturn ret;\n+}\n+\n static int odb_source_packed_fsck(struct odb_source *source,\n \t\t\t\t  struct odb_fsck_options *opts)\n {\n@@ -912,6 +936,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n \tif (verify_bitmap_files(packed))\n \t\tret = -1;\n \n+\tif (verify_midx(packed, opts) < 0)\n+\t\tret = -1;\n+\n \treturn ret;\n }\n \ndiff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh\nindex 68143cb5b7..20b010c33b 100755\n--- a/t/t5319-multi-pack-index.sh\n+++ b/t/t5319-multi-pack-index.sh\n@@ -573,6 +573,19 @@ test_expect_success 'verify incorrect checksum' '\n \t\t$objdir \"incorrect checksum\"\n '\n \n+test_expect_success 'git fsck --no-full checks multi-pack-index, --connectivity-only does not' '\n+\tpos=$(($(wc -c <$objdir/pack/multi-pack-index) - 10)) &&\n+\tcorrupt_midx_and_verify $pos \\\n+\t\t\"\\377\\377\\377\\377\\377\\377\\377\\377\\377\\377\" \\\n+\t\t$objdir \"incorrect checksum\" &&\n+\n+\ttest_must_fail git fsck --no-full 2>err &&\n+\ttest_grep \"incorrect checksum\" err &&\n+\n+\tgit fsck --connectivity-only 2>err &&\n+\ttest_grep ! \"incorrect checksum\" err\n+'\n+\n test_expect_success 'setup for v1-specific fsck tests' '\n \tgit -c midx.version=1 multi-pack-index write\n '\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551192","messageId":"20260825-pks-odb-source-fsck-v1-10-b756de0bf24f@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH 10/10] builtin/fsck: move loose object verification into the loose source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:30:12Z","receivedAt":"2026-08-25T14:30:38Z","isPatch":true,"body":"The consistency checks for loose objects are hosted by \"builtin/fsck.c\".\nThese checks are obviously specific to the \"loose\" backend.\n\nMove the logic into `odb_source_loose_fsck()`. Introduce a new \"verbose\"\nflag so that we can properly retain semantics around whether or not we\nwant to print some status messages.\n\nNote that this fixes a bug as a side effect: the progress meter was\ncaptured in the callback data before `start_progress()` was even called,\nso the per-subdirectory progress updates always operated on a NULL\npointer and the meter jumped straight from 0 to 256 upon completion. The\nnew code only sets up the callback data's progress meter after it has\nbeen created, so the progress display now advances incrementally again.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c     | 91 ++----------------------------------------------------\n odb.h              |  3 ++\n odb/source-loose.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++--\n 3 files changed, 93 insertions(+), 90 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 7eaea340b0..4af1d874cc 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -12,7 +12,6 @@\n #include \"parse-options.h\"\n #include \"progress.h\"\n #include \"packfile.h\"\n-#include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n #include \"odb/streaming.h\"\n@@ -695,88 +694,6 @@ static void process_refs(struct repository *repo, struct snapshot *snap)\n \t}\n }\n \n-struct for_each_loose_cb {\n-\tstruct repository *repo;\n-\tstruct progress *progress;\n-};\n-\n-static int fsck_loose(const struct object_id *oid, const char *path,\n-\t\t      void *cb_data)\n-{\n-\tstruct for_each_loose_cb *data = cb_data;\n-\tenum object_type type = OBJ_NONE;\n-\tsize_t size;\n-\tvoid *contents = NULL;\n-\tint eaten;\n-\tstruct object_info oi = OBJECT_INFO_INIT;\n-\tstruct object_id real_oid = *null_oid(data->repo->hash_algo);\n-\tint err = 0;\n-\n-\toi.sizep = &size;\n-\toi.typep = &type;\n-\n-\tif (read_loose_object(data->repo, path, oid, &real_oid, &contents, &oi) < 0) {\n-\t\tif (contents && !oideq(&real_oid, oid))\n-\t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n-\t\t\t\t    oid_to_hex(&real_oid), path);\n-\t\telse\n-\t\t\terr = error(_(\"%s: object corrupt or missing: %s\"),\n-\t\t\t\t    oid_to_hex(oid), path);\n-\t}\n-\tif (err < 0) {\n-\t\terrors_found |= ERROR_OBJECT;\n-\t\tfree(contents);\n-\t\treturn 0; /* keep checking other objects */\n-\t}\n-\n-\tif (!contents && type != OBJ_BLOB)\n-\t\tBUG(\"read_loose_object streamed a non-blob\");\n-\n-\tif (fsck_obj_buffer(oid, type, size, contents, &eaten, data->repo))\n-\t\terrors_found |= ERROR_OBJECT;\n-\n-\tif (!eaten)\n-\t\tfree(contents);\n-\treturn 0; /* keep checking other objects, even if we saw an error */\n-}\n-\n-static int fsck_cruft(const char *basename, const char *path,\n-\t\t      void *data UNUSED)\n-{\n-\tif (!starts_with(basename, \"tmp_obj_\"))\n-\t\tfprintf_ln(stderr, _(\"bad sha1 file: %s\"), path);\n-\treturn 0;\n-}\n-\n-static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *data)\n-{\n-\tstruct for_each_loose_cb *cb_data = data;\n-\tstruct progress *progress = cb_data->progress;\n-\tdisplay_progress(progress, nr + 1);\n-\treturn 0;\n-}\n-\n-static void fsck_source(struct repository *repo, struct odb_source *source)\n-{\n-\tstruct progress *progress = NULL;\n-\tstruct for_each_loose_cb cb_data = {\n-\t\t.repo = source->odb->repo,\n-\t\t.progress = progress,\n-\t};\n-\n-\tif (verbose)\n-\t\tfprintf_ln(stderr, _(\"Checking object directory\"));\n-\n-\tif (show_progress)\n-\t\tprogress = start_progress(repo,\n-\t\t\t\t\t  _(\"Checking object directories\"), 256);\n-\n-\tfor_each_loose_file_in_source(source, fsck_loose,\n-\t\t\t\t      fsck_cruft, fsck_subdir, &cb_data);\n-\tdisplay_progress(progress, 256);\n-\tstop_progress(&progress);\n-}\n-\n static int fsck_cache_tree(struct repository *repo, struct cache_tree *it, const char *index_path)\n {\n \tint i;\n@@ -978,8 +895,10 @@ int cmd_fsck(int argc,\n \n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n-\tif (verbose)\n+\tif (verbose) {\n \t\tshow_progress = 0;\n+\t\todb_fsck_opts.flags |= ODB_FSCK_VERBOSE;\n+\t}\n \tif (show_progress)\n \t\todb_fsck_opts.flags |= ODB_FSCK_PROGRESS;\n \n@@ -1012,10 +931,6 @@ int cmd_fsck(int argc,\n \t\todb_for_each_object(repo->objects, NULL,\n \t\t\t\t    mark_object_for_connectivity, repo, 0);\n \t} else {\n-\t\tfor (source = repo->objects->sources; source; source = source->next)\n-\t\t\tif ((odb_fsck_opts.flags & ODB_FSCK_FULL) || source->local)\n-\t\t\t\tfsck_source(repo, source);\n-\n \t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n \t\t\terrors_found |= ERROR_OBJECT;\n \ndiff --git a/odb.h b/odb.h\nindex 0bf6c8d7d2..b87f281cbd 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -218,6 +218,9 @@ enum odb_fsck_flags {\n \n \t/* Display a progress meter, if sensible. */\n \tODB_FSCK_PROGRESS = (1 << 1),\n+\n+\t/* Be extra verbose when checking the database. */\n+\tODB_FSCK_VERBOSE = (1 << 2),\n };\n \n /* Options that shall be passed to `odb_fsck()`. */\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex f68d3c4d6c..efef9ca61f 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -12,6 +12,7 @@\n #include \"odb/streaming.h\"\n #include \"oidtree.h\"\n #include \"path.h\"\n+#include \"progress.h\"\n #include \"repository.h\"\n #include \"strbuf.h\"\n #include \"tempfile.h\"\n@@ -1031,12 +1032,96 @@ static void odb_source_loose_free(struct odb_source *source)\n \tfree(loose);\n }\n \n-static int odb_source_loose_fsck(struct odb_source *source UNUSED,\n-\t\t\t\t struct odb_fsck_options *opts UNUSED)\n+struct fsck_loose_data {\n+\tstruct odb_source_loose *source;\n+\tstruct odb_fsck_options *opts;\n+\tstruct progress *progress;\n+\tbool error_found;\n+};\n+\n+static int fsck_loose(const struct object_id *oid, const char *path,\n+\t\t      void *cb_data)\n {\n+\tstruct fsck_loose_data *data = cb_data;\n+\tenum object_type type = OBJ_NONE;\n+\tsize_t size;\n+\tvoid *contents = NULL;\n+\tint eaten = 0;\n+\tstruct object_info oi = OBJECT_INFO_INIT;\n+\tstruct object_id real_oid = *null_oid(data->source->base.odb->repo->hash_algo);\n+\tint err = 0;\n+\n+\toi.sizep = &size;\n+\toi.typep = &type;\n+\n+\tif (read_loose_object(data->source->base.odb->repo,\n+\t\t\t      path, oid, &real_oid, &contents, &oi) < 0) {\n+\t\tif (contents && !oideq(&real_oid, oid))\n+\t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n+\t\t\t\t    oid_to_hex(&real_oid), path);\n+\t\telse\n+\t\t\terr = error(_(\"%s: object corrupt or missing: %s\"),\n+\t\t\t\t    oid_to_hex(oid), path);\n+\t}\n+\tif (err < 0)\n+\t\tgoto out;\n+\n+\tif (!contents && type != OBJ_BLOB)\n+\t\tBUG(\"read_loose_object streamed a non-blob\");\n+\n+\tif (data->opts->object_cb(oid, type, size, contents, &eaten,\n+\t\t\t\t  data->opts->object_payload)) {\n+\t\terr = -1;\n+\t\tgoto out;\n+\t}\n+\n+out:\n+\tif (err)\n+\t\tdata->error_found = true;\n+\tif (!eaten)\n+\t\tfree(contents);\n+\treturn 0; /* keep checking other objects, even if we saw an error */\n+}\n+\n+static int fsck_cruft(const char *basename, const char *path,\n+\t\t      void *data UNUSED)\n+{\n+\tif (!starts_with(basename, \"tmp_obj_\"))\n+\t\tfprintf_ln(stderr, _(\"bad sha1 file: %s\"), path);\n+\treturn 0;\n+}\n+\n+static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *cb_data)\n+{\n+\tstruct fsck_loose_data *data = cb_data;\n+\tdisplay_progress(data->progress, nr + 1);\n \treturn 0;\n }\n \n+static int odb_source_loose_fsck(struct odb_source *source,\n+\t\t\t\t struct odb_fsck_options *opts)\n+{\n+\tstruct odb_source_loose *loose = odb_source_loose_downcast(source);\n+\tstruct fsck_loose_data data = {\n+\t\t.source = loose,\n+\t\t.opts = opts,\n+\t};\n+\n+\tif (opts->flags & ODB_FSCK_VERBOSE)\n+\t\tfprintf_ln(stderr, _(\"Checking object directory\"));\n+\n+\tif (opts->flags & ODB_FSCK_PROGRESS)\n+\t\tdata.progress = start_progress(source->odb->repo,\n+\t\t\t\t\t       _(\"Checking object directories\"), 256);\n+\n+\tfor_each_loose_file_in_source(source, fsck_loose,\n+\t\t\t\t      fsck_cruft, fsck_subdir, &data);\n+\tdisplay_progress(data.progress, 256);\n+\tstop_progress(&data.progress);\n+\n+\treturn data.error_found ? -1 : 0;\n+}\n+\n struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \t\t\t\t\t      const char *path,\n \t\t\t\t\t      bool local)\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551346","messageId":"CAOLa=ZQyGk65qhF2uRV6Q48c_F948XuTWOdHOTwenfGestLxWw@mail.gmail.com","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-1-b756de0bf24f@pks.im","subject":"Re: [PATCH 01/10] builtin/fsck: use `fsck_obj_buffer()` when checking loose objects","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-27T10:00:37Z","receivedAt":"2026-08-27T10:00:41Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> When checking loose objects we manually parse the object buffer we have\n> read from the on-disk file, mark the object and then call `fsck_obj()`.\n> Almost the exact same steps are also performed by `fsck_obj_buffer()`.\n>\n\nI was wondering what the difference was, there seems to be none, nit:\nperhaps we can drop 'Almost'.\n\n> Stop open-coding this logic and call `fsck_obj_buffer()` instead.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/fsck.c | 17 +----------------\n>  1 file changed, 1 insertion(+), 16 deletions(-)\n>\n> diff --git a/builtin/fsck.c b/builtin/fsck.c\n> index 892c5661d9..3c4127f4d8 100644\n> --- a/builtin/fsck.c\n> +++ b/builtin/fsck.c\n> @@ -722,7 +722,6 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n>  \t\t      void *cb_data)\n>  {\n>  \tstruct for_each_loose_cb *data = cb_data;\n> -\tstruct object *obj;\n>  \tenum object_type type = OBJ_NONE;\n>  \tsize_t size;\n>  \tvoid *contents = NULL;\n> @@ -751,21 +750,7 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n>  \tif (!contents && type != OBJ_BLOB)\n>  \t\tBUG(\"read_loose_object streamed a non-blob\");\n>\n> -\tobj = parse_object_buffer(data->repo, oid, type, size,\n> -\t\t\t\t  contents, &eaten);\n> -\n> -\tif (!obj) {\n> -\t\terrors_found |= ERROR_OBJECT;\n> -\t\terror(_(\"%s: object could not be parsed: %s\"),\n> -\t\t      oid_to_hex(oid), path);\n> -\t\tif (!eaten)\n> -\t\t\tfree(contents);\n\nThis is now moved to the bottom below fsck_obj_buffer() call. So that's\nokay.\n\n> -\t\treturn 0; /* keep checking other objects */\n> -\t}\n> -\n> -\tobj->flags &= ~(REACHABLE | SEEN);\n> -\tobj->flags |= HAS_OBJ;\n> -\tif (fsck_obj(data->repo, obj, contents, size))\n> +\tif (fsck_obj_buffer(oid, type, size, contents, &eaten, data->repo))\n>  \t\terrors_found |= ERROR_OBJECT;\n>\n\nI see `fsck_obj_buffer()` also sets adds the `ERROR_OBJECT` flag, but\nthat's okay.\n\n>  \tif (!eaten)\n>\n> --\n> 2.55.0.822.g20453c30eb.dirty\n"},{"id":"551347","messageId":"CAOLa=ZSmjfCD-5fPgJm4cbaKZOQa0S62wAf+vSxWFWLrrgczBA@mail.gmail.com","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-2-b756de0bf24f@pks.im","subject":"Re: [PATCH 02/10] builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-27T10:03:08Z","receivedAt":"2026-08-27T10:03:13Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The interfaces of the functions `fsck_obj()` and `fsck_obj_buffer()` are\n> somewhat similar to one another. The only difference between those two\n> is that `fsck_obj()` takes an already-parsed object as input, whereas\n> `fsck_obj_buffer()` parses the buffer and then calls `fsck_obj()`.\n>\n> Furthermore, `fsck_obj()` has no callers other than `fsck_obj_buffer()`.\n>\n> Refactor the code by merging those two functions. This makes it obvious\n> which function does what, and it allows us to get rid of the early in\n\ns/early/early return/ ?\n\n> `fsck_obj()` in case `SEEN` is set as the only caller unconditionally\n> clears that bit before calling it anyway.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/fsck.c | 47 ++++++++++++++++++++---------------------------\n>  1 file changed, 20 insertions(+), 27 deletions(-)\n>\n\nThe patch looks good!\n"},{"id":"551348","messageId":"CAOLa=ZQoJotCEXWxbOz9sHs0+Xen=7NwW-3OcrNXFL461VEz1Q@mail.gmail.com","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-3-b756de0bf24f@pks.im","subject":"Re: [PATCH 03/10] builtin/fsck: de-globalize option handling","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-27T10:05:14Z","receivedAt":"2026-08-27T10:05:24Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> In subsequent commits we're about to rework some of the option handling\n> in git-fsck(1) a bit. It is currently a bit of a mess though due to lots\n> of global state that makes it hard to see which flags are used where\n> exactly.\n>\n> Refactor the code by moving the fsck options into `cmd_fsck()`. This\n> allows us to convert some of the options into function-local variables.\n>\n\nNice. I was wondering how much work this would involve, but it seems\nlike these variables are only used in `cmd_fsck()` anyway, so they\ndidn't even have to be global. Good spotting.\n\n[snip]\n"},{"id":"551349","messageId":"CAOLa=ZThFKpbmifU-zScXqiT1QSQqCTppHWbgSqmpRft8K+0XQ@mail.gmail.com","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-4-b756de0bf24f@pks.im","subject":"Re: [PATCH 04/10] builtin/fsck: don't check alternates with \"--no-full\"","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-27T10:12:13Z","receivedAt":"2026-08-27T10:12:16Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> According to git-fsck(1), the \"--full\" option behaves in the following\n> way:\n>\n>   Check not just objects in GIT_OBJECT_DIRECTORY ($GIT_DIR/objects), but\n>   also the ones found in alternate object pools listed in\n>   GIT_ALTERNATE_OBJECT_DIRECTORIES or $GIT_DIR/objects/info/alternates,\n>   and in packed Git archives found in $GIT_DIR/objects/pack and\n>   corresponding pack subdirectories in alternate object pools.\n>\n> So ultimately, it is supposed to control two things: (1) whether we only\n> check the main object directory, and (2) whether we check packfiles.\n>\n> In its current state though, the flag only controls whether we check\n> packfiles or not, and if so we verify packfiles of all attached sources.\n> But we also have checks for loose objects in git-fsck(1), and here we\n> unconditionally check them in all sources.\n>\n\nTo reiterate,\n\nwithout '--full': Check local loose + alternate loose. No packed objects\n\nwith '--full': Check local loose + alternate loose. local packed +\nalternates packed.\n\nAnd we want to only do local loose in the latter. Makes sense.\n\n>\n> The flag is arguably conflating two unrelated concerns with one another,\n> and it really should be split up into two flags: one that controls how\n> thorough we want to check individual sources, and one that controls\n> which sources we want to check in the first place. So ideally, we would\n> have:\n>\n>   - \"--include-alternates\": check all sources, not only the local one.\n>\n>   - \"--include-optimized-objects\": check not only loose objects, but\n>     also those that have been packed. Note that we explicitly don't say\n>     \"--include-packed-objects\" here to be more backend-agnostic.\n>\n>   - \"--full\": implies both of the above flags.\n>\n> This feels out of scope for this series though. So for now, simply fix\n> the code by honoring locality of the sources for loose objects.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/fsck.c  | 3 ++-\n>  t/t1450-fsck.sh | 5 +++++\n>  2 files changed, 7 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/fsck.c b/builtin/fsck.c\n> index 5132ff0f15..3f6056535f 100644\n> --- a/builtin/fsck.c\n> +++ b/builtin/fsck.c\n> @@ -1047,7 +1047,8 @@ int cmd_fsck(int argc,\n>  \t\t\t\t    mark_object_for_connectivity, repo, 0);\n>  \t} else {\n>  \t\tfor (source = repo->objects->sources; source; source = source->next)\n> -\t\t\tfsck_source(repo, source);\n> +\t\t\tif (check_full || source->local)\n> +\t\t\t\tfsck_source(repo, source);\n>\n\nSo we check the local bit and only fsck that source. Looks good.\n\n>  \t\tif (check_full) {\n>  \t\t\tstruct packed_git *p;\n> diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh\n> index 77cd96de78..1b4074304c 100755\n> --- a/t/t1450-fsck.sh\n> +++ b/t/t1450-fsck.sh\n> @@ -844,6 +844,11 @@ test_expect_success 'alternate objects are correctly blamed' '\n>  \techo \"../../alt.git/objects\" >.git/objects/info/alternates &&\n>  \tmkdir alt.git/objects/$(dirname $path) &&\n>  \t>alt.git/objects/$(dirname $path)/$(basename $path) &&\n> +\n> +\t# Without \"--full\", only the local object source is checked.\n> +\tgit fsck --no-full >out 2>&1 &&\n> +\ttest_must_be_empty out &&\n> +\n>  \ttest_must_fail git fsck >out 2>&1 &&\n>  \ttest_grep alt.git out\n>  '\n>\n> --\n> 2.55.0.822.g20453c30eb.dirty\n"},{"id":"551350","messageId":"CAOLa=ZQaetcmzOWzba=peCadW6i_JqhMth5cmQOZ32xz-E-zoQ@mail.gmail.com","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-5-b756de0bf24f@pks.im","subject":"Re: [PATCH 05/10] odb: provide infrastructure for pluggable fsck checks","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-27T10:49:38Z","receivedAt":"2026-08-27T10:49:40Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The on-disk consistency checks in git-fsck(1) are conceptually\n> backend-specific: while connectivity checks and object-level parsing\n> checks are generic, verifying the physical integrity of packfiles and\n> loose objects is meaningful only to backends that use these formats:\n> Having these checks live in \"builtin/fsck.c\" violates that layering,\n> because it forces the command to reach directly into format-specific\n> internals.\n>\n> Provide new infrastructure to make these format-specific checks\n> pluggable and implement stubs for the different source types we already\n> have. In subsequent commits we'll move functionality over piece by\n> piece.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/fsck.c        | 16 +++++++++++-----\n>  odb.c                 |  9 +++++++++\n>  odb.h                 | 23 +++++++++++++++++++++++\n>  odb/source-files.c    | 13 +++++++++++++\n>  odb/source-inmemory.c |  8 ++++++++\n>  odb/source-loose.c    |  7 +++++++\n>  odb/source-packed.c   |  8 ++++++++\n>  odb/source.h          | 21 +++++++++++++++++++++\n>  8 files changed, 100 insertions(+), 5 deletions(-)\n>\n> diff --git a/builtin/fsck.c b/builtin/fsck.c\n> index 3f6056535f..adbe192e56 100644\n> --- a/builtin/fsck.c\n> +++ b/builtin/fsck.c\n> @@ -965,7 +965,9 @@ int cmd_fsck(int argc,\n>  \t     const char *prefix,\n>  \t     struct repository *repo)\n>  {\n> -\tint check_full = 1;\n> +\tstruct odb_fsck_options odb_fsck_opts = {\n> +\t\t.flags = ODB_FSCK_FULL,\n> +\t};\n>  \tint keep_cache_objects = 0;\n>  \tint name_objects = 0;\n>  \tint check_references = 1;\n> @@ -977,7 +979,8 @@ int cmd_fsck(int argc,\n>  \t\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n>  \t\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n>  \t\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n> -\t\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n\nQuestion: OPT_BOOL sets 'check_full' to 0 when using '--no-full', does\nOPT_BIT provide similar functionality?\n\n> +\t\tOPT_BIT(0, \"full\", &odb_fsck_opts.flags,\n> +\t\t\tN_(\"also consider packs and alternate objects\"), ODB_FSCK_FULL),\n>  \t\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n>  \t\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n>  \t\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n> @@ -1018,7 +1021,7 @@ int cmd_fsck(int argc,\n>  \t\tshow_progress = 0;\n>\n>  \tif (write_lost_and_found) {\n> -\t\tcheck_full = 1;\n> +\t\todb_fsck_opts.flags |= ODB_FSCK_FULL;\n>  \t\tinclude_reflogs = 0;\n>  \t}\n>\n> @@ -1047,10 +1050,13 @@ int cmd_fsck(int argc,\n>  \t\t\t\t    mark_object_for_connectivity, repo, 0);\n>  \t} else {\n>  \t\tfor (source = repo->objects->sources; source; source = source->next)\n> -\t\t\tif (check_full || source->local)\n> +\t\t\tif ((odb_fsck_opts.flags & ODB_FSCK_FULL) || source->local)\n>  \t\t\t\tfsck_source(repo, source);\n>\n> -\t\tif (check_full) {\n> +\t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n> +\t\t\terrors_found |= ERROR_OBJECT;\n> +\n\nSo most of the functionality will move into this and we'll cleanup\naround in the following commits.\n\n> +\t\tif (odb_fsck_opts.flags & ODB_FSCK_FULL) {\n>  \t\t\tstruct packed_git *p;\n>  \t\t\tuint32_t total = 0, count = 0;\n>  \t\t\tstruct progress *progress = NULL;\n> diff --git a/odb.c b/odb.c\n> index 1fe20808eb..766043b685 100644\n> --- a/odb.c\n> +++ b/odb.c\n> @@ -1177,3 +1177,12 @@ void odb_reprepare(struct object_database *o)\n>  {\n>  \todb_prepare(o, ODB_PREPARE_FLUSH_CACHES);\n>  }\n> +\n> +int odb_fsck(struct object_database *odb, struct odb_fsck_options *options)\n> +{\n> +\tint ret = 0;\n> +\tfor (struct odb_source *source = odb->sources; source; source = source->next)\n> +\t\tif ((options->flags & ODB_FSCK_FULL) || source->local)\n> +\t\t\tret |= odb_source_fsck(source, options);\n> +\treturn ret;\n> +}\n\nThe odb iterates over all the sources and does a consistency check,\nlooks good.\n\n[snip]\n\nThe changes in this commit look to be in order.\n"},{"id":"551351","messageId":"CAOLa=ZQwhpPMrgeLW8W0pezH8VFrqDiiAfet3G_jDRQDu_KQUg@mail.gmail.com","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-8-b756de0bf24f@pks.im","subject":"Re: [PATCH 08/10] builtin/fsck: move bitmap verification into the packed source","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-27T10:54:51Z","receivedAt":"2026-08-27T10:54:55Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The checks for bitmaps live in `verify_bitmap_files()`, which is called\n> by \"builtin/fsck.c\". These checks are obviously specific to the \"packed\"\n> backend.\n>\n> Move the logic into `odb_source_packed_fsck()`. As in preceding commits,\n> this means that we now properly honor both \"--connectivity-only\" and\n> \"--no-full\". Furthermore, we drop the dedicated `ERROR_BITMAP` bit and\n> instead use the generic `ERROR_OBJECT` bit.\n>\n> Note that this change also adapts `verify_bitmap_files()` to be\n> focussed on a single \"packed\" source instead of verifying bitmaps from\n\nnit: s/focussed/focused\n\n> all sources. This change is required as we already know to loop around\n> the sources in `odb_fsck()` itself.\n>\n\n[snip]\n\nchanges look good\n"},{"id":"551352","messageId":"CAOLa=ZRYSje476JJDr5b8hHozwa5=4bw4E30i3AfLX176=12ZA@mail.gmail.com","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"Re: [PATCH 00/10] odb: make consistency checks pluggable","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-27T10:58:23Z","receivedAt":"2026-08-27T10:58:25Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> this patch series makes object database consistency checks pluggable.\n>\n> This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\n> with the following two dependencsie merged into it:\n>\n>   - ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n>     `alternates_db` field, 2026-08-17)\n>\n>   - ps/odb-pluggable-pack-generation at 5176dd3d05 (bundle: generate\n>     packfiles via the object database, 2026-08-21)\n>\n> Thanks!\n>\n> Patrick\n>\n\nHi,\n\nI reviewed the series and only have some small nits/questions. Rest\nlooks good :)\n"},{"id":"551494","messageId":"apUYbzzzBULal_op@pks.im","threadId":"66217","inReplyTo":"CAOLa=ZSmjfCD-5fPgJm4cbaKZOQa0S62wAf+vSxWFWLrrgczBA@mail.gmail.com","subject":"Re: [PATCH 02/10] builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:00:15Z","receivedAt":"2026-08-31T06:00:25Z","isPatch":true,"body":"On Thu, Aug 27, 2026 at 06:03:08AM -0400, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The interfaces of the functions `fsck_obj()` and `fsck_obj_buffer()` are\n> > somewhat similar to one another. The only difference between those two\n> > is that `fsck_obj()` takes an already-parsed object as input, whereas\n> > `fsck_obj_buffer()` parses the buffer and then calls `fsck_obj()`.\n> >\n> > Furthermore, `fsck_obj()` has no callers other than `fsck_obj_buffer()`.\n> >\n> > Refactor the code by merging those two functions. This makes it obvious\n> > which function does what, and it allows us to get rid of the early in\n> \n> s/early/early return/ ?\n\nIndeed. Will fix.\n\nPatrick\n"},{"id":"551495","messageId":"apUYeK7IvIfaxdtf@pks.im","threadId":"66217","inReplyTo":"CAOLa=ZQyGk65qhF2uRV6Q48c_F948XuTWOdHOTwenfGestLxWw@mail.gmail.com","subject":"Re: [PATCH 01/10] builtin/fsck: use `fsck_obj_buffer()` when checking loose objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:00:24Z","receivedAt":"2026-08-31T06:00:34Z","isPatch":true,"body":"On Thu, Aug 27, 2026 at 06:00:37AM -0400, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > When checking loose objects we manually parse the object buffer we have\n> > read from the on-disk file, mark the object and then call `fsck_obj()`.\n> > Almost the exact same steps are also performed by `fsck_obj_buffer()`.\n> >\n> \n> I was wondering what the difference was, there seems to be none, nit:\n> perhaps we can drop 'Almost'.\n\nI actually didn't have the \"almost\" initially but added it later, but\nthere isn't really much of a reason why specifically I did so. I'll drop\nit again.\n\n> > diff --git a/builtin/fsck.c b/builtin/fsck.c\n> > index 892c5661d9..3c4127f4d8 100644\n> > --- a/builtin/fsck.c\n> > +++ b/builtin/fsck.c\n> > @@ -751,21 +750,7 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n> >  \tif (!contents && type != OBJ_BLOB)\n> >  \t\tBUG(\"read_loose_object streamed a non-blob\");\n> >\n> > -\tobj = parse_object_buffer(data->repo, oid, type, size,\n> > -\t\t\t\t  contents, &eaten);\n> > -\n> > -\tif (!obj) {\n> > -\t\terrors_found |= ERROR_OBJECT;\n> > -\t\terror(_(\"%s: object could not be parsed: %s\"),\n> > -\t\t      oid_to_hex(oid), path);\n> > -\t\tif (!eaten)\n> > -\t\t\tfree(contents);\n> \n> This is now moved to the bottom below fsck_obj_buffer() call. So that's\n> okay.\n> \n> > -\t\treturn 0; /* keep checking other objects */\n> > -\t}\n> > -\n> > -\tobj->flags &= ~(REACHABLE | SEEN);\n> > -\tobj->flags |= HAS_OBJ;\n> > -\tif (fsck_obj(data->repo, obj, contents, size))\n> > +\tif (fsck_obj_buffer(oid, type, size, contents, &eaten, data->repo))\n> >  \t\terrors_found |= ERROR_OBJECT;\n> >\n> \n> I see `fsck_obj_buffer()` also sets adds the `ERROR_OBJECT` flag, but\n> that's okay.\n\nYeah. We could just drop this, but then it'd feel a tiny bit weird as we\ncall the function without checking its return value at all. So I decided\nto just keep this as-is.\n\nPatrick\n"},{"id":"551496","messageId":"apUYgMoYbTWoe2W9@pks.im","threadId":"66217","inReplyTo":"CAOLa=ZThFKpbmifU-zScXqiT1QSQqCTppHWbgSqmpRft8K+0XQ@mail.gmail.com","subject":"Re: [PATCH 04/10] builtin/fsck: don't check alternates with \"--no-full\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:00:32Z","receivedAt":"2026-08-31T06:00:38Z","isPatch":true,"body":"On Thu, Aug 27, 2026 at 06:12:13AM -0400, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > According to git-fsck(1), the \"--full\" option behaves in the following\n> > way:\n> >\n> >   Check not just objects in GIT_OBJECT_DIRECTORY ($GIT_DIR/objects), but\n> >   also the ones found in alternate object pools listed in\n> >   GIT_ALTERNATE_OBJECT_DIRECTORIES or $GIT_DIR/objects/info/alternates,\n> >   and in packed Git archives found in $GIT_DIR/objects/pack and\n> >   corresponding pack subdirectories in alternate object pools.\n> >\n> > So ultimately, it is supposed to control two things: (1) whether we only\n> > check the main object directory, and (2) whether we check packfiles.\n> >\n> > In its current state though, the flag only controls whether we check\n> > packfiles or not, and if so we verify packfiles of all attached sources.\n> > But we also have checks for loose objects in git-fsck(1), and here we\n> > unconditionally check them in all sources.\n> >\n> \n> To reiterate,\n> \n> without '--full': Check local loose + alternate loose. No packed objects\n> \n> with '--full': Check local loose + alternate loose. local packed +\n> alternates packed.\n> \n> And we want to only do local loose in the latter. Makes sense.\n\ns/latter/former/, but other than this: yes.\n\nPatrick\n"},{"id":"551497","messageId":"apUYhh-Uz-ZVrsFh@pks.im","threadId":"66217","inReplyTo":"CAOLa=ZQaetcmzOWzba=peCadW6i_JqhMth5cmQOZ32xz-E-zoQ@mail.gmail.com","subject":"Re: [PATCH 05/10] odb: provide infrastructure for pluggable fsck checks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:00:38Z","receivedAt":"2026-08-31T06:00:44Z","isPatch":true,"body":"On Thu, Aug 27, 2026 at 06:49:38AM -0400, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/builtin/fsck.c b/builtin/fsck.c\n> > index 3f6056535f..adbe192e56 100644\n> > --- a/builtin/fsck.c\n> > +++ b/builtin/fsck.c\n> > @@ -977,7 +979,8 @@ int cmd_fsck(int argc,\n> >  \t\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n> >  \t\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n> >  \t\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n> > -\t\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n> \n> Question: OPT_BOOL sets 'check_full' to 0 when using '--no-full', does\n> OPT_BIT provide similar functionality?\n\nYes, it does.\n\n> > @@ -1047,10 +1050,13 @@ int cmd_fsck(int argc,\n> >  \t\t\t\t    mark_object_for_connectivity, repo, 0);\n> >  \t} else {\n> >  \t\tfor (source = repo->objects->sources; source; source = source->next)\n> > -\t\t\tif (check_full || source->local)\n> > +\t\t\tif ((odb_fsck_opts.flags & ODB_FSCK_FULL) || source->local)\n> >  \t\t\t\tfsck_source(repo, source);\n> >\n> > -\t\tif (check_full) {\n> > +\t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n> > +\t\t\terrors_found |= ERROR_OBJECT;\n> > +\n> \n> So most of the functionality will move into this and we'll cleanup\n> around in the following commits.\n\nYup, exactly.\n\nPatrick\n"},{"id":"551498","messageId":"apUYiv36xvWe-oj7@pks.im","threadId":"66217","inReplyTo":"CAOLa=ZQwhpPMrgeLW8W0pezH8VFrqDiiAfet3G_jDRQDu_KQUg@mail.gmail.com","subject":"Re: [PATCH 08/10] builtin/fsck: move bitmap verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:00:42Z","receivedAt":"2026-08-31T06:00:50Z","isPatch":true,"body":"On Thu, Aug 27, 2026 at 06:54:51AM -0400, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The checks for bitmaps live in `verify_bitmap_files()`, which is called\n> > by \"builtin/fsck.c\". These checks are obviously specific to the \"packed\"\n> > backend.\n> >\n> > Move the logic into `odb_source_packed_fsck()`. As in preceding commits,\n> > this means that we now properly honor both \"--connectivity-only\" and\n> > \"--no-full\". Furthermore, we drop the dedicated `ERROR_BITMAP` bit and\n> > instead use the generic `ERROR_OBJECT` bit.\n> >\n> > Note that this change also adapts `verify_bitmap_files()` to be\n> > focussed on a single \"packed\" source instead of verifying bitmaps from\n> \n> nit: s/focussed/focused\n\nYou can actually use both spellings [1], where \"focussed\" is more\ncommonly used in the UK. Anyway, I'll change this to help our American\nfriends out there.\n\nPatrick\n\n[1]: https://en.wiktionary.org/wiki/focussed\n"},{"id":"551504","messageId":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH v2 00/10] odb: make consistency checks pluggable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:14Z","receivedAt":"2026-08-31T06:46:23Z","isPatch":true,"body":"Hi,\n\nthis patch series makes object database consistency checks pluggable.\n\nThis series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\nwith the following two dependencsie merged into it:\n\n  - ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n    `alternates_db` field, 2026-08-17)\n\n  - ps/odb-pluggable-pack-generation at 5176dd3d05 (bundle: generate\n    packfiles via the object database, 2026-08-21)\n\nChanges in v2:\n  - Some commit message improvements.\n  - Link to v1: https://patch.msgid.link/20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (10):\n      builtin/fsck: use `fsck_obj_buffer()` when checking loose objects\n      builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`\n      builtin/fsck: de-globalize option handling\n      builtin/fsck: don't check alternates with \"--no-full\"\n      odb: provide infrastructure for pluggable fsck checks\n      builtin/fsck: move packfile verification into the packed source\n      builtin/fsck: move reverse index verification into the packed source\n      builtin/fsck: move bitmap verification into the packed source\n      builtin/fsck: move multi-pack index verification into the packed source\n      builtin/fsck: move loose object verification into the loose source\n\n builtin/fsck.c                | 296 ++++++++----------------------------------\n odb.c                         |   9 ++\n odb.h                         |  33 +++++\n odb/source-files.c            |  13 ++\n odb/source-inmemory.c         |   8 ++\n odb/source-loose.c            |  92 +++++++++++++\n odb/source-packed.c           | 117 +++++++++++++++++\n odb/source.h                  |  21 +++\n pack-bitmap.c                 |  26 ++--\n pack-bitmap.h                 |   2 +-\n t/t1450-fsck.sh               |   5 +\n t/t5319-multi-pack-index.sh   |  13 ++\n t/t5325-reverse-index.sh      |   8 ++\n t/t5326-multi-pack-bitmaps.sh |  10 +-\n 14 files changed, 394 insertions(+), 259 deletions(-)\n\nRange-diff versus v1:\n\n 1:  cf49376600 !  1:  1aec903546 builtin/fsck: use `fsck_obj_buffer()` when checking loose objects\n    @@ Commit message\n     \n         When checking loose objects we manually parse the object buffer we have\n         read from the on-disk file, mark the object and then call `fsck_obj()`.\n    -    Almost the exact same steps are also performed by `fsck_obj_buffer()`.\n    +    The exact same steps are also performed by `fsck_obj_buffer()`.\n     \n         Stop open-coding this logic and call `fsck_obj_buffer()` instead.\n     \n 2:  da2ca27041 !  2:  3804f0339e builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`\n    @@ Commit message\n         Furthermore, `fsck_obj()` has no callers other than `fsck_obj_buffer()`.\n     \n         Refactor the code by merging those two functions. This makes it obvious\n    -    which function does what, and it allows us to get rid of the early in\n    -    `fsck_obj()` in case `SEEN` is set as the only caller unconditionally\n    -    clears that bit before calling it anyway.\n    +    which function does what, and it allows us to get rid of the early\n    +    return in `fsck_obj()` in case `SEEN` is set as the only caller\n    +    unconditionally clears that bit before calling it anyway.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n 3:  a24506f55e =  3:  b2cb9032cf builtin/fsck: de-globalize option handling\n 4:  f6a407efd0 =  4:  10ee3b8baf builtin/fsck: don't check alternates with \"--no-full\"\n 5:  31841a1f05 =  5:  1e65eec60e odb: provide infrastructure for pluggable fsck checks\n 6:  2cd6d71983 =  6:  0b8cf751aa builtin/fsck: move packfile verification into the packed source\n 7:  c0559f1820 =  7:  3a38a75549 builtin/fsck: move reverse index verification into the packed source\n 8:  96ae1ce3c6 !  8:  dd3a4c6cea builtin/fsck: move bitmap verification into the packed source\n    @@ Commit message\n         instead use the generic `ERROR_OBJECT` bit.\n     \n         Note that this change also adapts `verify_bitmap_files()` to be\n    -    focussed on a single \"packed\" source instead of verifying bitmaps from\n    +    focused on a single \"packed\" source instead of verifying bitmaps from\n         all sources. This change is required as we already know to loop around\n         the sources in `odb_fsck()` itself.\n     \n 9:  4721f4b4ba =  9:  90ada56b7f builtin/fsck: move multi-pack index verification into the packed source\n10:  0b36829fd9 = 10:  b0f6fccae8 builtin/fsck: move loose object verification into the loose source\n\n---\nbase-commit: 6b08999fb1b3ad0bad04d492dc206ad42839e274\nchange-id: 20260810-pks-odb-source-fsck-e64772c7ee5f\n\n"},{"id":"551505","messageId":"20260831-pks-odb-source-fsck-v2-1-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"[PATCH v2 01/10] builtin/fsck: use `fsck_obj_buffer()` when checking loose objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:15Z","receivedAt":"2026-08-31T06:46:25Z","isPatch":true,"body":"When checking loose objects we manually parse the object buffer we have\nread from the on-disk file, mark the object and then call `fsck_obj()`.\nThe exact same steps are also performed by `fsck_obj_buffer()`.\n\nStop open-coding this logic and call `fsck_obj_buffer()` instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 17 +----------------\n 1 file changed, 1 insertion(+), 16 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 892c5661d9..3c4127f4d8 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -722,7 +722,6 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n \t\t      void *cb_data)\n {\n \tstruct for_each_loose_cb *data = cb_data;\n-\tstruct object *obj;\n \tenum object_type type = OBJ_NONE;\n \tsize_t size;\n \tvoid *contents = NULL;\n@@ -751,21 +750,7 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n \tif (!contents && type != OBJ_BLOB)\n \t\tBUG(\"read_loose_object streamed a non-blob\");\n \n-\tobj = parse_object_buffer(data->repo, oid, type, size,\n-\t\t\t\t  contents, &eaten);\n-\n-\tif (!obj) {\n-\t\terrors_found |= ERROR_OBJECT;\n-\t\terror(_(\"%s: object could not be parsed: %s\"),\n-\t\t      oid_to_hex(oid), path);\n-\t\tif (!eaten)\n-\t\t\tfree(contents);\n-\t\treturn 0; /* keep checking other objects */\n-\t}\n-\n-\tobj->flags &= ~(REACHABLE | SEEN);\n-\tobj->flags |= HAS_OBJ;\n-\tif (fsck_obj(data->repo, obj, contents, size))\n+\tif (fsck_obj_buffer(oid, type, size, contents, &eaten, data->repo))\n \t\terrors_found |= ERROR_OBJECT;\n \n \tif (!eaten)\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551506","messageId":"20260831-pks-odb-source-fsck-v2-2-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"[PATCH v2 02/10] builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:16Z","receivedAt":"2026-08-31T06:46:27Z","isPatch":true,"body":"The interfaces of the functions `fsck_obj()` and `fsck_obj_buffer()` are\nsomewhat similar to one another. The only difference between those two\nis that `fsck_obj()` takes an already-parsed object as input, whereas\n`fsck_obj_buffer()` parses the buffer and then calls `fsck_obj()`.\n\nFurthermore, `fsck_obj()` has no callers other than `fsck_obj_buffer()`.\n\nRefactor the code by merging those two functions. This makes it obvious\nwhich function does what, and it allows us to get rid of the early\nreturn in `fsck_obj()` in case `SEEN` is set as the only caller\nunconditionally clears that bit before calling it anyway.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 47 ++++++++++++++++++++---------------------------\n 1 file changed, 20 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 3c4127f4d8..bed8481893 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -401,14 +401,27 @@ static void check_connectivity(struct repository *repo)\n \t}\n }\n \n-static int fsck_obj(struct repository *repo,\n-\t\t    struct object *obj, void *buffer, unsigned long size)\n+static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,\n+\t\t\t   unsigned long size, void *buffer, int *eaten, void *cb_data)\n {\n+\tstruct repository *repo = cb_data;\n+\tstruct object *obj;\n \tint err;\n \n-\tif (obj->flags & SEEN)\n-\t\treturn 0;\n-\tobj->flags |= SEEN;\n+\t/*\n+\t * Note, buffer may be NULL if type is OBJ_BLOB. See\n+\t * verify_packfile(), data_valid variable for details.\n+\t */\n+\tobj = parse_object_buffer(repo, oid, type, size, buffer, eaten);\n+\tif (!obj) {\n+\t\terrors_found |= ERROR_OBJECT;\n+\t\terr = error(_(\"%s: object corrupt or missing\"),\n+\t\t\t    oid_to_hex(oid));\n+\t\tgoto out;\n+\t}\n+\n+\tobj->flags &= ~REACHABLE;\n+\tobj->flags |= HAS_OBJ | SEEN;\n \n \tif (verbose)\n \t\tfprintf_ln(stderr, _(\"Checking %s %s\"),\n@@ -417,6 +430,7 @@ static int fsck_obj(struct repository *repo,\n \n \tif (fsck_walk(obj, NULL, &fsck_obj_options))\n \t\tobjerror(repo, obj, _(\"broken links\"));\n+\n \terr = fsck_object(obj, buffer, size, &fsck_obj_options);\n \tif (err)\n \t\tgoto out;\n@@ -442,32 +456,11 @@ static int fsck_obj(struct repository *repo,\n \t}\n \n out:\n-\tif (obj->type == OBJ_TREE)\n+\tif (obj && obj->type == OBJ_TREE)\n \t\tfree_tree_buffer((struct tree *)obj);\n \treturn err;\n }\n \n-static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,\n-\t\t\t   unsigned long size, void *buffer, int *eaten, void *cb_data)\n-{\n-\tstruct repository *repo = cb_data;\n-\tstruct object *obj;\n-\n-\t/*\n-\t * Note, buffer may be NULL if type is OBJ_BLOB. See\n-\t * verify_packfile(), data_valid variable for details.\n-\t */\n-\tobj = parse_object_buffer(repo, oid, type, size, buffer, eaten);\n-\tif (!obj) {\n-\t\terrors_found |= ERROR_OBJECT;\n-\t\treturn error(_(\"%s: object corrupt or missing\"),\n-\t\t\t     oid_to_hex(oid));\n-\t}\n-\tobj->flags &= ~(REACHABLE | SEEN);\n-\tobj->flags |= HAS_OBJ;\n-\treturn fsck_obj(repo, obj, buffer, size);\n-}\n-\n static int default_refs;\n \n static void fsck_handle_reflog_oid(struct repository *repo,\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551507","messageId":"20260831-pks-odb-source-fsck-v2-3-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"[PATCH v2 03/10] builtin/fsck: de-globalize option handling","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:17Z","receivedAt":"2026-08-31T06:46:30Z","isPatch":true,"body":"In subsequent commits we're about to rework some of the option handling\nin git-fsck(1) a bit. It is currently a bit of a mess though due to lots\nof global state that makes it hard to see which flags are used where\nexactly.\n\nRefactor the code by moving the fsck options into `cmd_fsck()`. This\nallows us to convert some of the options into function-local variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 45 ++++++++++++++++++++++-----------------------\n 1 file changed, 22 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex bed8481893..5132ff0f15 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -37,10 +37,8 @@ static int show_root;\n static int show_tags;\n static int show_unreachable;\n static int include_reflogs = 1;\n-static int check_full = 1;\n static int connectivity_only;\n static int check_strict;\n-static int keep_cache_objects;\n static struct fsck_options fsck_walk_options;\n static struct fsck_options fsck_obj_options;\n static int errors_found;\n@@ -48,8 +46,6 @@ static int write_lost_and_found;\n static int verbose;\n static int show_progress = -1;\n static int show_dangling = 1;\n-static int name_objects;\n-static int check_references = 1;\n static timestamp_t now;\n #define ERROR_OBJECT 01\n #define ERROR_REACHABLE 02\n@@ -964,30 +960,33 @@ static char const * const fsck_usage[] = {\n \tNULL\n };\n \n-static struct option fsck_opts[] = {\n-\tOPT__VERBOSE(&verbose, N_(\"be verbose\")),\n-\tOPT_BOOL(0, \"unreachable\", &show_unreachable, N_(\"show unreachable objects\")),\n-\tOPT_BOOL(0, \"dangling\", &show_dangling, N_(\"show dangling objects\")),\n-\tOPT_BOOL(0, \"tags\", &show_tags, N_(\"report tags\")),\n-\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n-\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n-\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n-\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n-\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n-\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n-\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n-\t\t\t\tN_(\"write dangling objects in .git/lost-found\")),\n-\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n-\tOPT_BOOL(0, \"name-objects\", &name_objects, N_(\"show verbose names for reachable objects\")),\n-\tOPT_BOOL(0, \"references\", &check_references, N_(\"check reference database consistency\")),\n-\tOPT_END(),\n-};\n-\n int cmd_fsck(int argc,\n \t     const char **argv,\n \t     const char *prefix,\n \t     struct repository *repo)\n {\n+\tint check_full = 1;\n+\tint keep_cache_objects = 0;\n+\tint name_objects = 0;\n+\tint check_references = 1;\n+\tstruct option fsck_opts[] = {\n+\t\tOPT__VERBOSE(&verbose, N_(\"be verbose\")),\n+\t\tOPT_BOOL(0, \"unreachable\", &show_unreachable, N_(\"show unreachable objects\")),\n+\t\tOPT_BOOL(0, \"dangling\", &show_dangling, N_(\"show dangling objects\")),\n+\t\tOPT_BOOL(0, \"tags\", &show_tags, N_(\"report tags\")),\n+\t\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n+\t\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n+\t\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n+\t\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n+\t\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n+\t\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n+\t\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n+\t\t\t\t\tN_(\"write dangling objects in .git/lost-found\")),\n+\t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"name-objects\", &name_objects, N_(\"show verbose names for reachable objects\")),\n+\t\tOPT_BOOL(0, \"references\", &check_references, N_(\"check reference database consistency\")),\n+\t\tOPT_END(),\n+\t};\n \tstruct odb_source *source;\n \tstruct snapshot snap = {\n \t\t.nr = 0,\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551508","messageId":"20260831-pks-odb-source-fsck-v2-4-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"[PATCH v2 04/10] builtin/fsck: don't check alternates with \"--no-full\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:18Z","receivedAt":"2026-08-31T06:46:32Z","isPatch":true,"body":"According to git-fsck(1), the \"--full\" option behaves in the following\nway:\n\n  Check not just objects in GIT_OBJECT_DIRECTORY ($GIT_DIR/objects), but\n  also the ones found in alternate object pools listed in\n  GIT_ALTERNATE_OBJECT_DIRECTORIES or $GIT_DIR/objects/info/alternates,\n  and in packed Git archives found in $GIT_DIR/objects/pack and\n  corresponding pack subdirectories in alternate object pools.\n\nSo ultimately, it is supposed to control two things: (1) whether we only\ncheck the main object directory, and (2) whether we check packfiles.\n\nIn its current state though, the flag only controls whether we check\npackfiles or not, and if so we verify packfiles of all attached sources.\nBut we also have checks for loose objects in git-fsck(1), and here we\nunconditionally check them in all sources.\n\nThe flag is arguably conflating two unrelated concerns with one another,\nand it really should be split up into two flags: one that controls how\nthorough we want to check individual sources, and one that controls\nwhich sources we want to check in the first place. So ideally, we would\nhave:\n\n  - \"--include-alternates\": check all sources, not only the local one.\n\n  - \"--include-optimized-objects\": check not only loose objects, but\n    also those that have been packed. Note that we explicitly don't say\n    \"--include-packed-objects\" here to be more backend-agnostic.\n\n  - \"--full\": implies both of the above flags.\n\nThis feels out of scope for this series though. So for now, simply fix\nthe code by honoring locality of the sources for loose objects.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c  | 3 ++-\n t/t1450-fsck.sh | 5 +++++\n 2 files changed, 7 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 5132ff0f15..3f6056535f 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -1047,7 +1047,8 @@ int cmd_fsck(int argc,\n \t\t\t\t    mark_object_for_connectivity, repo, 0);\n \t} else {\n \t\tfor (source = repo->objects->sources; source; source = source->next)\n-\t\t\tfsck_source(repo, source);\n+\t\t\tif (check_full || source->local)\n+\t\t\t\tfsck_source(repo, source);\n \n \t\tif (check_full) {\n \t\t\tstruct packed_git *p;\ndiff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh\nindex 77cd96de78..1b4074304c 100755\n--- a/t/t1450-fsck.sh\n+++ b/t/t1450-fsck.sh\n@@ -844,6 +844,11 @@ test_expect_success 'alternate objects are correctly blamed' '\n \techo \"../../alt.git/objects\" >.git/objects/info/alternates &&\n \tmkdir alt.git/objects/$(dirname $path) &&\n \t>alt.git/objects/$(dirname $path)/$(basename $path) &&\n+\n+\t# Without \"--full\", only the local object source is checked.\n+\tgit fsck --no-full >out 2>&1 &&\n+\ttest_must_be_empty out &&\n+\n \ttest_must_fail git fsck >out 2>&1 &&\n \ttest_grep alt.git out\n '\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551509","messageId":"20260831-pks-odb-source-fsck-v2-5-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"[PATCH v2 05/10] odb: provide infrastructure for pluggable fsck checks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:19Z","receivedAt":"2026-08-31T06:46:34Z","isPatch":true,"body":"The on-disk consistency checks in git-fsck(1) are conceptually\nbackend-specific: while connectivity checks and object-level parsing\nchecks are generic, verifying the physical integrity of packfiles and\nloose objects is meaningful only to backends that use these formats:\nHaving these checks live in \"builtin/fsck.c\" violates that layering,\nbecause it forces the command to reach directly into format-specific\ninternals.\n\nProvide new infrastructure to make these format-specific checks\npluggable and implement stubs for the different source types we already\nhave. In subsequent commits we'll move functionality over piece by\npiece.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c        | 16 +++++++++++-----\n odb.c                 |  9 +++++++++\n odb.h                 | 23 +++++++++++++++++++++++\n odb/source-files.c    | 13 +++++++++++++\n odb/source-inmemory.c |  8 ++++++++\n odb/source-loose.c    |  7 +++++++\n odb/source-packed.c   |  8 ++++++++\n odb/source.h          | 21 +++++++++++++++++++++\n 8 files changed, 100 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 3f6056535f..adbe192e56 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -965,7 +965,9 @@ int cmd_fsck(int argc,\n \t     const char *prefix,\n \t     struct repository *repo)\n {\n-\tint check_full = 1;\n+\tstruct odb_fsck_options odb_fsck_opts = {\n+\t\t.flags = ODB_FSCK_FULL,\n+\t};\n \tint keep_cache_objects = 0;\n \tint name_objects = 0;\n \tint check_references = 1;\n@@ -977,7 +979,8 @@ int cmd_fsck(int argc,\n \t\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n \t\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n \t\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n-\t\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n+\t\tOPT_BIT(0, \"full\", &odb_fsck_opts.flags,\n+\t\t\tN_(\"also consider packs and alternate objects\"), ODB_FSCK_FULL),\n \t\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n \t\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n \t\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n@@ -1018,7 +1021,7 @@ int cmd_fsck(int argc,\n \t\tshow_progress = 0;\n \n \tif (write_lost_and_found) {\n-\t\tcheck_full = 1;\n+\t\todb_fsck_opts.flags |= ODB_FSCK_FULL;\n \t\tinclude_reflogs = 0;\n \t}\n \n@@ -1047,10 +1050,13 @@ int cmd_fsck(int argc,\n \t\t\t\t    mark_object_for_connectivity, repo, 0);\n \t} else {\n \t\tfor (source = repo->objects->sources; source; source = source->next)\n-\t\t\tif (check_full || source->local)\n+\t\t\tif ((odb_fsck_opts.flags & ODB_FSCK_FULL) || source->local)\n \t\t\t\tfsck_source(repo, source);\n \n-\t\tif (check_full) {\n+\t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n+\t\t\terrors_found |= ERROR_OBJECT;\n+\n+\t\tif (odb_fsck_opts.flags & ODB_FSCK_FULL) {\n \t\t\tstruct packed_git *p;\n \t\t\tuint32_t total = 0, count = 0;\n \t\t\tstruct progress *progress = NULL;\ndiff --git a/odb.c b/odb.c\nindex 1fe20808eb..766043b685 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -1177,3 +1177,12 @@ void odb_reprepare(struct object_database *o)\n {\n \todb_prepare(o, ODB_PREPARE_FLUSH_CACHES);\n }\n+\n+int odb_fsck(struct object_database *odb, struct odb_fsck_options *options)\n+{\n+\tint ret = 0;\n+\tfor (struct odb_source *source = odb->sources; source; source = source->next)\n+\t\tif ((options->flags & ODB_FSCK_FULL) || source->local)\n+\t\t\tret |= odb_source_fsck(source, options);\n+\treturn ret;\n+}\ndiff --git a/odb.h b/odb.h\nindex e60174070f..76c15e48f5 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -206,6 +206,29 @@ void odb_prepare(struct object_database *o, enum odb_prepare_flags flags);\n /* Equivalent to `odb_prepare(o, ODB_PREPARE_FLUSH_CACHES)`. */\n void odb_reprepare(struct object_database *o);\n \n+enum odb_fsck_flags {\n+\t/*\n+\t * If set, perform a full consistency check for the full object\n+\t * database, including all of its sources and the contents of their\n+\t * optimized formats. Otherwise, only check the local source, and\n+\t * restrict checks of its optimized formats to cheap structural\n+\t * verification of their metadata.\n+\t */\n+\tODB_FSCK_FULL = (1 << 0),\n+};\n+\n+/* Options that shall be passed to `odb_fsck()`. */\n+struct odb_fsck_options {\n+\tenum odb_fsck_flags flags;\n+};\n+\n+/*\n+ * Run backend-specific integrity checks on all object sources. Each source\n+ * performs the checks appropriate to its type. Returns 0 on success, a\n+ * negative error code otherwise.\n+ */\n+int odb_fsck(struct object_database *odb, struct odb_fsck_options *opts);\n+\n /*\n  * Find source by its object directory path. Returns a `NULL` pointer in case\n  * the source could not be found.\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex bd4fdf3a6c..f6fb560d2e 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -893,6 +893,18 @@ static int odb_source_files_generate_pack(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n+static int odb_source_files_fsck(struct odb_source *source,\n+\t\t\t\t struct odb_fsck_options *opts)\n+{\n+\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\tint ret = 0;\n+\n+\tret |= odb_source_fsck(&files->loose->base, opts);\n+\tret |= odb_source_fsck(&files->packed->base, opts);\n+\n+\treturn ret;\n+}\n+\n struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \t\t\t\t\t      const char *path,\n \t\t\t\t\t      bool local)\n@@ -908,6 +920,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tfiles->base.close = odb_source_files_close;\n \tfiles->base.create_on_disk = odb_source_files_create_on_disk;\n \tfiles->base.prepare = odb_source_files_prepare;\n+\tfiles->base.fsck = odb_source_files_fsck;\n \tfiles->base.read_object_info = odb_source_files_read_object_info;\n \tfiles->base.read_object_stream = odb_source_files_read_object_stream;\n \tfiles->base.for_each_object = odb_source_files_for_each_object;\ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex 795672adf2..ba0f86da26 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"object-file.h\"\n #include \"odb.h\"\n+#include \"fsck.h\"\n #include \"odb/source-inmemory.h\"\n #include \"odb/streaming.h\"\n #include \"oidtree.h\"\n@@ -368,6 +369,12 @@ static void odb_source_inmemory_free(struct odb_source *source)\n \tfree(inmemory);\n }\n \n+static int odb_source_inmemory_fsck(struct odb_source *source UNUSED,\n+\t\t\t\t    struct odb_fsck_options *opts UNUSED)\n+{\n+\treturn 0;\n+}\n+\n struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n {\n \tstruct odb_source_inmemory *source;\n@@ -378,6 +385,7 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n \tsource->base.free = odb_source_inmemory_free;\n \tsource->base.close = odb_source_inmemory_close;\n \tsource->base.prepare = odb_source_inmemory_prepare;\n+\tsource->base.fsck = odb_source_inmemory_fsck;\n \tsource->base.read_object_info = odb_source_inmemory_read_object_info;\n \tsource->base.read_object_stream = odb_source_inmemory_read_object_stream;\n \tsource->base.for_each_object = odb_source_inmemory_for_each_object;\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex bb3455dfbd..f68d3c4d6c 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -1031,6 +1031,12 @@ static void odb_source_loose_free(struct odb_source *source)\n \tfree(loose);\n }\n \n+static int odb_source_loose_fsck(struct odb_source *source UNUSED,\n+\t\t\t\t struct odb_fsck_options *opts UNUSED)\n+{\n+\treturn 0;\n+}\n+\n struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \t\t\t\t\t      const char *path,\n \t\t\t\t\t      bool local)\n@@ -1043,6 +1049,7 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \tloose->base.free = odb_source_loose_free;\n \tloose->base.close = odb_source_loose_close;\n \tloose->base.prepare = odb_source_loose_prepare;\n+\tloose->base.fsck = odb_source_loose_fsck;\n \tloose->base.read_object_info = odb_source_loose_read_object_info;\n \tloose->base.read_object_stream = odb_source_loose_read_object_stream;\n \tloose->base.for_each_object = odb_source_loose_for_each_object;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 630d955585..7aacf4bc45 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -2,6 +2,7 @@\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n #include \"dir.h\"\n+#include \"fsck.h\"\n #include \"git-zlib.h\"\n #include \"list-objects-filter-options.h\"\n #include \"mergesort.h\"\n@@ -826,6 +827,12 @@ static void odb_source_packed_free(struct odb_source *source)\n \tfree(packed);\n }\n \n+static int odb_source_packed_fsck(struct odb_source *source UNUSED,\n+\t\t\t\t  struct odb_fsck_options *opts UNUSED)\n+{\n+\treturn 0;\n+}\n+\n struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \t\t\t\t\t\tconst char *path,\n \t\t\t\t\t\tbool local)\n@@ -839,6 +846,7 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \tpacked->base.free = odb_source_packed_free;\n \tpacked->base.close = odb_source_packed_close;\n \tpacked->base.prepare = odb_source_packed_prepare;\n+\tpacked->base.fsck = odb_source_packed_fsck;\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\ndiff --git a/odb/source.h b/odb/source.h\nindex 559e2ea2e9..10a5dd5194 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -320,6 +320,17 @@ struct odb_source {\n \tint (*generate_pack)(struct odb_source *source,\n \t\t\t     struct odb_pack_generator **out,\n \t\t\t     const struct odb_generate_pack_options *opts);\n+\n+\t/*\n+\t * This callback is expected to check the integrity of the object source\n+\t * and report any errors found via the fsck options. The checks performed\n+\t * are backend-specific.\n+\t *\n+\t * The callback is expected to return 0 on success, a negative error\n+\t * code otherwise.\n+\t */\n+\tint (*fsck)(struct odb_source *source,\n+\t\t    struct odb_fsck_options *options);\n };\n \n /*\n@@ -588,4 +599,14 @@ static inline int odb_source_generate_pack(struct odb_source *source,\n \treturn source->generate_pack(source, out, opts);\n }\n \n+/*\n+ * Check the integrity of the object database source. The checks performed\n+ * are backend-specific. Returns 0 on success, a negative error code otherwise.\n+ */\n+static inline int odb_source_fsck(struct odb_source *source,\n+\t\t\t\t  struct odb_fsck_options *opts)\n+{\n+\treturn source->fsck(source, opts);\n+}\n+\n #endif\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551510","messageId":"20260831-pks-odb-source-fsck-v2-6-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"[PATCH v2 06/10] builtin/fsck: move packfile verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:20Z","receivedAt":"2026-08-31T06:46:36Z","isPatch":true,"body":"Move the packfile verification out of `cmd_fsck()` and into the \"packed\"\nsource. While doing so, thread the progress meter and object callback\nthrough the newly introduced `struct odb_fsck_options` so that the\ncaller's preferences are honoured without exposing those details at the\n\"builtin/fsck.c\" level.\n\nNote that the old code reported failures when verifying packfiles with\nthe `ERROR_PACK` bit, which gets returned to the caller via the exit\ncode. This bit is neither exercised in our test suite nor is it\ndocumented anywhere in our codebase. Furthermore, this bit is highly\nspecific to the object storage backend, which makes it a bad fit for the\nnew pluggable infrastructure. So instead of retaining these semantics,\nwe drop them and return the generic `ERROR_OBJECT` bit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c      | 33 ++++-----------------------------\n odb.h               |  7 +++++++\n odb/source-packed.c | 46 +++++++++++++++++++++++++++++++++++++++++++---\n 3 files changed, 54 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex adbe192e56..e504dae904 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -7,7 +7,6 @@\n #include \"blob.h\"\n #include \"tag.h\"\n #include \"refs.h\"\n-#include \"pack.h\"\n #include \"cache-tree.h\"\n #include \"fsck.h\"\n #include \"parse-options.h\"\n@@ -49,7 +48,6 @@ static int show_dangling = 1;\n static timestamp_t now;\n #define ERROR_OBJECT 01\n #define ERROR_REACHABLE 02\n-#define ERROR_PACK 04\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n #define ERROR_MULTI_PACK_INDEX 040\n@@ -967,6 +965,8 @@ int cmd_fsck(int argc,\n {\n \tstruct odb_fsck_options odb_fsck_opts = {\n \t\t.flags = ODB_FSCK_FULL,\n+\t\t.object_cb = fsck_obj_buffer,\n+\t\t.object_payload = repo,\n \t};\n \tint keep_cache_objects = 0;\n \tint name_objects = 0;\n@@ -1019,6 +1019,8 @@ int cmd_fsck(int argc,\n \t\tshow_progress = isatty(2);\n \tif (verbose)\n \t\tshow_progress = 0;\n+\tif (show_progress)\n+\t\todb_fsck_opts.flags |= ODB_FSCK_PROGRESS;\n \n \tif (write_lost_and_found) {\n \t\todb_fsck_opts.flags |= ODB_FSCK_FULL;\n@@ -1056,33 +1058,6 @@ int cmd_fsck(int argc,\n \t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n \t\t\terrors_found |= ERROR_OBJECT;\n \n-\t\tif (odb_fsck_opts.flags & ODB_FSCK_FULL) {\n-\t\t\tstruct packed_git *p;\n-\t\t\tuint32_t total = 0, count = 0;\n-\t\t\tstruct progress *progress = NULL;\n-\n-\t\t\tif (show_progress) {\n-\t\t\t\trepo_for_each_pack(repo, p) {\n-\t\t\t\t\tif (open_pack_index(p))\n-\t\t\t\t\t\tcontinue;\n-\t\t\t\t\ttotal += p->num_objects;\n-\t\t\t\t}\n-\n-\t\t\t\tprogress = start_progress(repo,\n-\t\t\t\t\t\t\t  _(\"Checking objects\"), total);\n-\t\t\t}\n-\n-\t\t\trepo_for_each_pack(repo, p) {\n-\t\t\t\t/* verify gives error messages itself */\n-\t\t\t\tif (verify_pack(repo,\n-\t\t\t\t\t\tp, fsck_obj_buffer, repo,\n-\t\t\t\t\t\tprogress, count))\n-\t\t\t\t\terrors_found |= ERROR_PACK;\n-\t\t\t\tcount += p->num_objects;\n-\t\t\t}\n-\t\t\tstop_progress(&progress);\n-\t\t}\n-\n \t\tif (fsck_finish(&fsck_obj_options))\n \t\t\terrors_found |= ERROR_OBJECT;\n \t}\ndiff --git a/odb.h b/odb.h\nindex 76c15e48f5..0bf6c8d7d2 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -215,11 +215,18 @@ enum odb_fsck_flags {\n \t * verification of their metadata.\n \t */\n \tODB_FSCK_FULL = (1 << 0),\n+\n+\t/* Display a progress meter, if sensible. */\n+\tODB_FSCK_PROGRESS = (1 << 1),\n };\n \n /* Options that shall be passed to `odb_fsck()`. */\n struct odb_fsck_options {\n \tenum odb_fsck_flags flags;\n+\n+\tint (*object_cb)(const struct object_id *oid, enum object_type type,\n+\t\t\t unsigned long size, void *buffer, int *eaten, void *cb_data);\n+\tvoid *object_payload;\n };\n \n /*\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 7aacf4bc45..0d3599f8fe 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -9,8 +9,10 @@\n #include \"midx.h\"\n #include \"odb/source-packed.h\"\n #include \"odb/streaming.h\"\n+#include \"pack.h\"\n #include \"packfile.h\"\n #include \"pack-bitmap.h\"\n+#include \"progress.h\"\n \n static int find_pack_entry(struct odb_source_packed *store,\n \t\t\t   const struct object_id *oid,\n@@ -827,10 +829,48 @@ static void odb_source_packed_free(struct odb_source *source)\n \tfree(packed);\n }\n \n-static int odb_source_packed_fsck(struct odb_source *source UNUSED,\n-\t\t\t\t  struct odb_fsck_options *opts UNUSED)\n+static int verify_packs(struct odb_source_packed *source,\n+\t\t\tstruct odb_fsck_options *opts)\n {\n-\treturn 0;\n+\tstruct progress *progress = NULL;\n+\tstruct packfile_list_entry *e;\n+\tuint32_t total = 0, count = 0;\n+\tint ret = 0;\n+\n+\tif (opts->flags & ODB_FSCK_PROGRESS) {\n+\t\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\t\tif (open_pack_index(e->pack))\n+\t\t\t\tcontinue;\n+\t\t\ttotal += e->pack->num_objects;\n+\t\t}\n+\n+\t\tprogress = start_progress(source->base.odb->repo,\n+\t\t\t\t\t  _(\"Checking objects\"), total);\n+\t}\n+\n+\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\t/* verify gives error messages itself */\n+\t\tif (verify_pack(source->base.odb->repo, e->pack,\n+\t\t\t\topts->object_cb, opts->object_payload,\n+\t\t\t\tprogress, count))\n+\t\t\tret = -1;\n+\t\tcount += e->pack->num_objects;\n+\t}\n+\tstop_progress(&progress);\n+\n+\treturn ret;\n+}\n+\n+static int odb_source_packed_fsck(struct odb_source *source,\n+\t\t\t\t  struct odb_fsck_options *opts)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tint ret = 0;\n+\n+\tif ((opts->flags & ODB_FSCK_FULL) && verify_packs(packed, opts) < 0)\n+\t\tret = -1;\n+\n+\treturn ret;\n }\n \n struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551511","messageId":"20260831-pks-odb-source-fsck-v2-7-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"[PATCH v2 07/10] builtin/fsck: move reverse index verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:21Z","receivedAt":"2026-08-31T06:46:39Z","isPatch":true,"body":"The checks for reverse indexes live in `check_pack_rev_indexes()`, which\nis hosted in \"builtin/fsck.c\". These checks are obviously specific to\nthe \"packed\" backend.\n\nMove the logic into `odb_source_packed_fsck()`. As in the preceding\ncommit, drop the dedicated `ERROR_PACK_REV_INDEX` bit and instead use\nthe generic `ERROR_OBJECT` bit.\n\nNote that this changes behaviour in two ways:\n\n  - The checks are now skipped when \"--connectivity-only\" was passed.\n    This is because we don't even run `odb_fsck()` at all when that\n    flag has been passed by the user, and not verifying data structures\n    of the object database matches the documented intent of that flag,\n    which is to only check the connectivity of reachable objects.\n\n  - The checks are now skipped for non-local sources when \"--no-full\"\n    was passed. This is, again, in line with the documented intent of\n    that flag.\n\nAdd a test to cast these semantics into stone.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c           | 37 -------------------------------------\n odb/source-packed.c      | 39 +++++++++++++++++++++++++++++++++++++++\n t/t5325-reverse-index.sh |  8 ++++++++\n 3 files changed, 47 insertions(+), 37 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex e504dae904..06e72877f3 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -23,7 +23,6 @@\n #include \"run-command.h\"\n #include \"sparse-index.h\"\n #include \"worktree.h\"\n-#include \"pack-revindex.h\"\n #include \"pack-bitmap.h\"\n \n #define REACHABLE 0x0001\n@@ -51,7 +50,6 @@ static timestamp_t now;\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n #define ERROR_MULTI_PACK_INDEX 040\n-#define ERROR_PACK_REV_INDEX 0100\n #define ERROR_BITMAP 0200\n \n static const char *describe_object(const struct object_id *oid)\n@@ -890,40 +888,6 @@ static int mark_object_for_connectivity(const struct object_id *oid,\n \treturn 0;\n }\n \n-static int check_pack_rev_indexes(struct repository *r, int show_progress)\n-{\n-\tstruct progress *progress = NULL;\n-\tstruct packed_git *p;\n-\tuint32_t pack_count = 0;\n-\tint res = 0;\n-\n-\tif (show_progress) {\n-\t\trepo_for_each_pack(r, p)\n-\t\t\tpack_count++;\n-\t\tprogress = start_delayed_progress(r,\n-\t\t\t\t\t\t  \"Verifying reverse pack-indexes\", pack_count);\n-\t\tpack_count = 0;\n-\t}\n-\n-\trepo_for_each_pack(r, p) {\n-\t\tint load_error = load_pack_revindex_from_disk(p);\n-\n-\t\tif (load_error < 0) {\n-\t\t\terror(_(\"unable to load rev-index for pack '%s'\"), p->pack_name);\n-\t\t\tres = ERROR_PACK_REV_INDEX;\n-\t\t} else if (!load_error &&\n-\t\t\t   !load_pack_revindex(r, p) &&\n-\t\t\t   verify_pack_revindex(p)) {\n-\t\t\terror(_(\"invalid rev-index for pack '%s'\"), p->pack_name);\n-\t\t\tres = ERROR_PACK_REV_INDEX;\n-\t\t}\n-\t\tdisplay_progress(progress, ++pack_count);\n-\t}\n-\tstop_progress(&progress);\n-\n-\treturn res;\n-}\n-\n static void fsck_refs(struct repository *r)\n {\n \tstruct child_process refs_verify = CHILD_PROCESS_INIT;\n@@ -1104,7 +1068,6 @@ int cmd_fsck(int argc,\n \t\tfree_worktrees(worktrees);\n \t}\n \n-\terrors_found |= check_pack_rev_indexes(repo, show_progress);\n \tif (verify_bitmap_files(repo))\n \t\terrors_found |= ERROR_BITMAP;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 0d3599f8fe..e5e69636dd 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -10,6 +10,7 @@\n #include \"odb/source-packed.h\"\n #include \"odb/streaming.h\"\n #include \"pack.h\"\n+#include \"pack-revindex.h\"\n #include \"packfile.h\"\n #include \"pack-bitmap.h\"\n #include \"progress.h\"\n@@ -861,6 +862,41 @@ static int verify_packs(struct odb_source_packed *source,\n \treturn ret;\n }\n \n+static int verify_reverse_indices(struct odb_source_packed *source,\n+\t\t\t\t  struct odb_fsck_options *opts)\n+{\n+\tstruct progress *progress = NULL;\n+\tstruct packfile_list_entry *e;\n+\tuint32_t pack_count = 0;\n+\tint res = 0;\n+\n+\tif (opts->flags & ODB_FSCK_PROGRESS) {\n+\t\tfor (e = packfile_store_get_packs(source); e; e = e->next)\n+\t\t\tpack_count++;\n+\t\tprogress = start_delayed_progress(source->base.odb->repo,\n+\t\t\t\t\t\t  \"Verifying reverse pack-indexes\", pack_count);\n+\t\tpack_count = 0;\n+\t}\n+\n+\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\tint load_error = load_pack_revindex_from_disk(e->pack);\n+\n+\t\tif (load_error < 0) {\n+\t\t\terror(_(\"unable to load rev-index for pack '%s'\"), e->pack->pack_name);\n+\t\t\tres = -1;\n+\t\t} else if (!load_error &&\n+\t\t\t   !load_pack_revindex(source->base.odb->repo, e->pack) &&\n+\t\t\t   verify_pack_revindex(e->pack)) {\n+\t\t\terror(_(\"invalid rev-index for pack '%s'\"), e->pack->pack_name);\n+\t\t\tres = -1;\n+\t\t}\n+\t\tdisplay_progress(progress, ++pack_count);\n+\t}\n+\tstop_progress(&progress);\n+\n+\treturn res;\n+}\n+\n static int odb_source_packed_fsck(struct odb_source *source,\n \t\t\t\t  struct odb_fsck_options *opts)\n {\n@@ -870,6 +906,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n \tif ((opts->flags & ODB_FSCK_FULL) && verify_packs(packed, opts) < 0)\n \t\tret = -1;\n \n+\tif (verify_reverse_indices(packed, opts) < 0)\n+\t\tret = -1;\n+\n \treturn ret;\n }\n \ndiff --git a/t/t5325-reverse-index.sh b/t/t5325-reverse-index.sh\nindex 5493791938..6b81abf663 100755\n--- a/t/t5325-reverse-index.sh\n+++ b/t/t5325-reverse-index.sh\n@@ -204,4 +204,12 @@ test_expect_success 'fsck catches invalid header: hash function' '\n \t\t\"reverse-index file .* has unsupported hash id\"\n '\n \n+test_expect_success 'fsck --no-full checks rev-index, --connectivity-only does not' '\n+\ttest_must_fail git -C corrupt fsck --no-full 2>err &&\n+\ttest_grep \"has unsupported hash id\" err &&\n+\n+\tgit -C corrupt fsck --connectivity-only 2>err &&\n+\ttest_grep ! \"has unsupported hash id\" err\n+'\n+\n test_done\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551512","messageId":"20260831-pks-odb-source-fsck-v2-8-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"[PATCH v2 08/10] builtin/fsck: move bitmap verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:22Z","receivedAt":"2026-08-31T06:46:42Z","isPatch":true,"body":"The checks for bitmaps live in `verify_bitmap_files()`, which is called\nby \"builtin/fsck.c\". These checks are obviously specific to the \"packed\"\nbackend.\n\nMove the logic into `odb_source_packed_fsck()`. As in preceding commits,\nthis means that we now properly honor both \"--connectivity-only\" and\n\"--no-full\". Furthermore, we drop the dedicated `ERROR_BITMAP` bit and\ninstead use the generic `ERROR_OBJECT` bit.\n\nNote that this change also adapts `verify_bitmap_files()` to be\nfocused on a single \"packed\" source instead of verifying bitmaps from\nall sources. This change is required as we already know to loop around\nthe sources in `odb_fsck()` itself.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c                |  5 -----\n odb/source-packed.c           |  3 +++\n pack-bitmap.c                 | 26 ++++++++++----------------\n pack-bitmap.h                 |  2 +-\n t/t5326-multi-pack-bitmaps.sh | 10 +++++++++-\n 5 files changed, 23 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 06e72877f3..2f7d29aa56 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -23,7 +23,6 @@\n #include \"run-command.h\"\n #include \"sparse-index.h\"\n #include \"worktree.h\"\n-#include \"pack-bitmap.h\"\n \n #define REACHABLE 0x0001\n #define SEEN      0x0002\n@@ -50,7 +49,6 @@ static timestamp_t now;\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n #define ERROR_MULTI_PACK_INDEX 040\n-#define ERROR_BITMAP 0200\n \n static const char *describe_object(const struct object_id *oid)\n {\n@@ -1068,9 +1066,6 @@ int cmd_fsck(int argc,\n \t\tfree_worktrees(worktrees);\n \t}\n \n-\tif (verify_bitmap_files(repo))\n-\t\terrors_found |= ERROR_BITMAP;\n-\n \tcheck_connectivity(repo);\n \n \tif (repo->settings.core_commit_graph) {\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex e5e69636dd..2b5dc502f5 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -909,6 +909,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n \tif (verify_reverse_indices(packed, opts) < 0)\n \t\tret = -1;\n \n+\tif (verify_bitmap_files(packed))\n+\t\tret = -1;\n+\n \treturn ret;\n }\n \ndiff --git a/pack-bitmap.c b/pack-bitmap.c\nindex e0fb57d332..3de8e9590c 100644\n--- a/pack-bitmap.c\n+++ b/pack-bitmap.c\n@@ -3410,28 +3410,22 @@ static int verify_bitmap_file(const struct git_hash_algo *algop,\n \treturn res;\n }\n \n-int verify_bitmap_files(struct repository *r)\n+int verify_bitmap_files(struct odb_source_packed *source)\n {\n-\tstruct odb_source *source;\n-\tstruct packed_git *p;\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n \tint res = 0;\n \n-\tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n-\t\tchar *midx_bitmap_name;\n-\n-\t\tif (!m)\n-\t\t\tcontinue;\n-\n-\t\tmidx_bitmap_name = midx_bitmap_filename(m);\n-\t\tres |= verify_bitmap_file(r->hash_algo, midx_bitmap_name);\n+\tm = get_multi_pack_index(source);\n+\tif (m) {\n+\t\tchar *midx_bitmap_name = midx_bitmap_filename(m);\n+\t\tres |= verify_bitmap_file(source->base.odb->repo->hash_algo, midx_bitmap_name);\n \t\tfree(midx_bitmap_name);\n \t}\n \n-\trepo_for_each_pack(r, p) {\n-\t\tchar *pack_bitmap_name = pack_bitmap_filename(p);\n-\t\tres |= verify_bitmap_file(r->hash_algo, pack_bitmap_name);\n+\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\tchar *pack_bitmap_name = pack_bitmap_filename(e->pack);\n+\t\tres |= verify_bitmap_file(source->base.odb->repo->hash_algo, pack_bitmap_name);\n \t\tfree(pack_bitmap_name);\n \t}\n \ndiff --git a/pack-bitmap.h b/pack-bitmap.h\nindex 1385027c1f..847ad4762d 100644\n--- a/pack-bitmap.h\n+++ b/pack-bitmap.h\n@@ -205,7 +205,7 @@ int bitmap_is_midx(struct bitmap_index *bitmap_git);\n \n int bitmap_is_preferred_refname(struct repository *r, const char *refname);\n \n-int verify_bitmap_files(struct repository *r);\n+int verify_bitmap_files(struct odb_source_packed *source);\n \n struct ewah_bitmap *read_bitmap(const unsigned char *map,\n \t\t\t\tsize_t map_size, size_t *map_pos);\ndiff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh\nindex 86beab1dae..8047459b00 100755\n--- a/t/t5326-multi-pack-bitmaps.sh\n+++ b/t/t5326-multi-pack-bitmaps.sh\n@@ -498,7 +498,15 @@ test_expect_success 'git fsck correctly identifies good and bad bitmaps' '\n \tcorrupt_file \"$packbitmap\" &&\n \ttest_must_fail git fsck 2>err &&\n \ttest_grep \"bitmap file '\\''$midxbitmap'\\'' has invalid checksum\" err &&\n-\ttest_grep \"bitmap file '\\''$packbitmap'\\'' has invalid checksum\" err\n+\ttest_grep \"bitmap file '\\''$packbitmap'\\'' has invalid checksum\" err &&\n+\n+\t# The bitmap checks are performed with \"--no-full\", but not with\n+\t# \"--connectivity-only\".\n+\ttest_must_fail git fsck --no-full 2>err &&\n+\ttest_grep \"bitmap file '\\''$midxbitmap'\\'' has invalid checksum\" err &&\n+\ttest_grep \"bitmap file '\\''$packbitmap'\\'' has invalid checksum\" err &&\n+\tgit fsck --connectivity-only 2>err &&\n+\ttest_grep ! \"invalid checksum\" err\n '\n \n test_expect_success 'corrupt MIDX with bitmap causes fallback' '\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551513","messageId":"20260831-pks-odb-source-fsck-v2-9-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"[PATCH v2 09/10] builtin/fsck: move multi-pack index verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:23Z","receivedAt":"2026-08-31T06:46:44Z","isPatch":true,"body":"The checks for multi-pack indexes are hosted in `cmd_fsck()` directly.\nThese checks are obviously specific to the \"packed\" backend.\n\nMove the logic into `odb_source_packed_fsck()`. As in preceding commits,\nthis means that we now properly honor both \"--connectivity-only\" and\n\"--no-full\". Furthermore, we drop the dedicated `ERROR_MULTI_PACK_INDEX`\nbit and instead use the generic `ERROR_OBJECT` bit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c              | 18 ------------------\n odb/source-packed.c         | 27 +++++++++++++++++++++++++++\n t/t5319-multi-pack-index.sh | 13 +++++++++++++\n 3 files changed, 40 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 2f7d29aa56..7eaea340b0 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -48,7 +48,6 @@ static timestamp_t now;\n #define ERROR_REACHABLE 02\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n-#define ERROR_MULTI_PACK_INDEX 040\n \n static const char *describe_object(const struct object_id *oid)\n {\n@@ -1085,23 +1084,6 @@ int cmd_fsck(int argc,\n \t\t}\n \t}\n \n-\tif (repo->settings.core_multi_pack_index) {\n-\t\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n-\n-\t\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\t\tchild_process_init(&midx_verify);\n-\t\t\tmidx_verify.git_cmd = 1;\n-\t\t\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n-\t\t\t\t     \"verify\", \"--object-dir\", source->path, NULL);\n-\t\t\tif (show_progress)\n-\t\t\t\tstrvec_push(&midx_verify.args, \"--progress\");\n-\t\t\telse\n-\t\t\t\tstrvec_push(&midx_verify.args, \"--no-progress\");\n-\t\t\tif (run_command(&midx_verify))\n-\t\t\t\terrors_found |= ERROR_MULTI_PACK_INDEX;\n-\t\t}\n-\t}\n-\n \tfree_snapshot_refs(&snap);\n \treturn errors_found;\n }\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 2b5dc502f5..9f42552377 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -14,6 +14,7 @@\n #include \"packfile.h\"\n #include \"pack-bitmap.h\"\n #include \"progress.h\"\n+#include \"run-command.h\"\n \n static int find_pack_entry(struct odb_source_packed *store,\n \t\t\t   const struct object_id *oid,\n@@ -897,6 +898,29 @@ static int verify_reverse_indices(struct odb_source_packed *source,\n \treturn res;\n }\n \n+static int verify_midx(struct odb_source_packed *source,\n+\t\t       struct odb_fsck_options *opts)\n+{\n+\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n+\tint ret = 0;\n+\n+\tif (!source->base.odb->repo->settings.core_multi_pack_index)\n+\t\treturn 0;\n+\n+\tchild_process_init(&midx_verify);\n+\tmidx_verify.git_cmd = 1;\n+\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n+\t\t     \"verify\", \"--object-dir\", source->base.path, NULL);\n+\tif (opts->flags & ODB_FSCK_PROGRESS)\n+\t\tstrvec_push(&midx_verify.args, \"--progress\");\n+\telse\n+\t\tstrvec_push(&midx_verify.args, \"--no-progress\");\n+\tif (run_command(&midx_verify))\n+\t\tret = -1;\n+\n+\treturn ret;\n+}\n+\n static int odb_source_packed_fsck(struct odb_source *source,\n \t\t\t\t  struct odb_fsck_options *opts)\n {\n@@ -912,6 +936,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n \tif (verify_bitmap_files(packed))\n \t\tret = -1;\n \n+\tif (verify_midx(packed, opts) < 0)\n+\t\tret = -1;\n+\n \treturn ret;\n }\n \ndiff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh\nindex 68143cb5b7..20b010c33b 100755\n--- a/t/t5319-multi-pack-index.sh\n+++ b/t/t5319-multi-pack-index.sh\n@@ -573,6 +573,19 @@ test_expect_success 'verify incorrect checksum' '\n \t\t$objdir \"incorrect checksum\"\n '\n \n+test_expect_success 'git fsck --no-full checks multi-pack-index, --connectivity-only does not' '\n+\tpos=$(($(wc -c <$objdir/pack/multi-pack-index) - 10)) &&\n+\tcorrupt_midx_and_verify $pos \\\n+\t\t\"\\377\\377\\377\\377\\377\\377\\377\\377\\377\\377\" \\\n+\t\t$objdir \"incorrect checksum\" &&\n+\n+\ttest_must_fail git fsck --no-full 2>err &&\n+\ttest_grep \"incorrect checksum\" err &&\n+\n+\tgit fsck --connectivity-only 2>err &&\n+\ttest_grep ! \"incorrect checksum\" err\n+'\n+\n test_expect_success 'setup for v1-specific fsck tests' '\n \tgit -c midx.version=1 multi-pack-index write\n '\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551514","messageId":"20260831-pks-odb-source-fsck-v2-10-f9b16ef4957b@pks.im","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"[PATCH v2 10/10] builtin/fsck: move loose object verification into the loose source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:46:24Z","receivedAt":"2026-08-31T06:46:46Z","isPatch":true,"body":"The consistency checks for loose objects are hosted by \"builtin/fsck.c\".\nThese checks are obviously specific to the \"loose\" backend.\n\nMove the logic into `odb_source_loose_fsck()`. Introduce a new \"verbose\"\nflag so that we can properly retain semantics around whether or not we\nwant to print some status messages.\n\nNote that this fixes a bug as a side effect: the progress meter was\ncaptured in the callback data before `start_progress()` was even called,\nso the per-subdirectory progress updates always operated on a NULL\npointer and the meter jumped straight from 0 to 256 upon completion. The\nnew code only sets up the callback data's progress meter after it has\nbeen created, so the progress display now advances incrementally again.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c     | 91 ++----------------------------------------------------\n odb.h              |  3 ++\n odb/source-loose.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++--\n 3 files changed, 93 insertions(+), 90 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 7eaea340b0..4af1d874cc 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -12,7 +12,6 @@\n #include \"parse-options.h\"\n #include \"progress.h\"\n #include \"packfile.h\"\n-#include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n #include \"odb/streaming.h\"\n@@ -695,88 +694,6 @@ static void process_refs(struct repository *repo, struct snapshot *snap)\n \t}\n }\n \n-struct for_each_loose_cb {\n-\tstruct repository *repo;\n-\tstruct progress *progress;\n-};\n-\n-static int fsck_loose(const struct object_id *oid, const char *path,\n-\t\t      void *cb_data)\n-{\n-\tstruct for_each_loose_cb *data = cb_data;\n-\tenum object_type type = OBJ_NONE;\n-\tsize_t size;\n-\tvoid *contents = NULL;\n-\tint eaten;\n-\tstruct object_info oi = OBJECT_INFO_INIT;\n-\tstruct object_id real_oid = *null_oid(data->repo->hash_algo);\n-\tint err = 0;\n-\n-\toi.sizep = &size;\n-\toi.typep = &type;\n-\n-\tif (read_loose_object(data->repo, path, oid, &real_oid, &contents, &oi) < 0) {\n-\t\tif (contents && !oideq(&real_oid, oid))\n-\t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n-\t\t\t\t    oid_to_hex(&real_oid), path);\n-\t\telse\n-\t\t\terr = error(_(\"%s: object corrupt or missing: %s\"),\n-\t\t\t\t    oid_to_hex(oid), path);\n-\t}\n-\tif (err < 0) {\n-\t\terrors_found |= ERROR_OBJECT;\n-\t\tfree(contents);\n-\t\treturn 0; /* keep checking other objects */\n-\t}\n-\n-\tif (!contents && type != OBJ_BLOB)\n-\t\tBUG(\"read_loose_object streamed a non-blob\");\n-\n-\tif (fsck_obj_buffer(oid, type, size, contents, &eaten, data->repo))\n-\t\terrors_found |= ERROR_OBJECT;\n-\n-\tif (!eaten)\n-\t\tfree(contents);\n-\treturn 0; /* keep checking other objects, even if we saw an error */\n-}\n-\n-static int fsck_cruft(const char *basename, const char *path,\n-\t\t      void *data UNUSED)\n-{\n-\tif (!starts_with(basename, \"tmp_obj_\"))\n-\t\tfprintf_ln(stderr, _(\"bad sha1 file: %s\"), path);\n-\treturn 0;\n-}\n-\n-static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *data)\n-{\n-\tstruct for_each_loose_cb *cb_data = data;\n-\tstruct progress *progress = cb_data->progress;\n-\tdisplay_progress(progress, nr + 1);\n-\treturn 0;\n-}\n-\n-static void fsck_source(struct repository *repo, struct odb_source *source)\n-{\n-\tstruct progress *progress = NULL;\n-\tstruct for_each_loose_cb cb_data = {\n-\t\t.repo = source->odb->repo,\n-\t\t.progress = progress,\n-\t};\n-\n-\tif (verbose)\n-\t\tfprintf_ln(stderr, _(\"Checking object directory\"));\n-\n-\tif (show_progress)\n-\t\tprogress = start_progress(repo,\n-\t\t\t\t\t  _(\"Checking object directories\"), 256);\n-\n-\tfor_each_loose_file_in_source(source, fsck_loose,\n-\t\t\t\t      fsck_cruft, fsck_subdir, &cb_data);\n-\tdisplay_progress(progress, 256);\n-\tstop_progress(&progress);\n-}\n-\n static int fsck_cache_tree(struct repository *repo, struct cache_tree *it, const char *index_path)\n {\n \tint i;\n@@ -978,8 +895,10 @@ int cmd_fsck(int argc,\n \n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n-\tif (verbose)\n+\tif (verbose) {\n \t\tshow_progress = 0;\n+\t\todb_fsck_opts.flags |= ODB_FSCK_VERBOSE;\n+\t}\n \tif (show_progress)\n \t\todb_fsck_opts.flags |= ODB_FSCK_PROGRESS;\n \n@@ -1012,10 +931,6 @@ int cmd_fsck(int argc,\n \t\todb_for_each_object(repo->objects, NULL,\n \t\t\t\t    mark_object_for_connectivity, repo, 0);\n \t} else {\n-\t\tfor (source = repo->objects->sources; source; source = source->next)\n-\t\t\tif ((odb_fsck_opts.flags & ODB_FSCK_FULL) || source->local)\n-\t\t\t\tfsck_source(repo, source);\n-\n \t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n \t\t\terrors_found |= ERROR_OBJECT;\n \ndiff --git a/odb.h b/odb.h\nindex 0bf6c8d7d2..b87f281cbd 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -218,6 +218,9 @@ enum odb_fsck_flags {\n \n \t/* Display a progress meter, if sensible. */\n \tODB_FSCK_PROGRESS = (1 << 1),\n+\n+\t/* Be extra verbose when checking the database. */\n+\tODB_FSCK_VERBOSE = (1 << 2),\n };\n \n /* Options that shall be passed to `odb_fsck()`. */\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex f68d3c4d6c..efef9ca61f 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -12,6 +12,7 @@\n #include \"odb/streaming.h\"\n #include \"oidtree.h\"\n #include \"path.h\"\n+#include \"progress.h\"\n #include \"repository.h\"\n #include \"strbuf.h\"\n #include \"tempfile.h\"\n@@ -1031,12 +1032,96 @@ static void odb_source_loose_free(struct odb_source *source)\n \tfree(loose);\n }\n \n-static int odb_source_loose_fsck(struct odb_source *source UNUSED,\n-\t\t\t\t struct odb_fsck_options *opts UNUSED)\n+struct fsck_loose_data {\n+\tstruct odb_source_loose *source;\n+\tstruct odb_fsck_options *opts;\n+\tstruct progress *progress;\n+\tbool error_found;\n+};\n+\n+static int fsck_loose(const struct object_id *oid, const char *path,\n+\t\t      void *cb_data)\n {\n+\tstruct fsck_loose_data *data = cb_data;\n+\tenum object_type type = OBJ_NONE;\n+\tsize_t size;\n+\tvoid *contents = NULL;\n+\tint eaten = 0;\n+\tstruct object_info oi = OBJECT_INFO_INIT;\n+\tstruct object_id real_oid = *null_oid(data->source->base.odb->repo->hash_algo);\n+\tint err = 0;\n+\n+\toi.sizep = &size;\n+\toi.typep = &type;\n+\n+\tif (read_loose_object(data->source->base.odb->repo,\n+\t\t\t      path, oid, &real_oid, &contents, &oi) < 0) {\n+\t\tif (contents && !oideq(&real_oid, oid))\n+\t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n+\t\t\t\t    oid_to_hex(&real_oid), path);\n+\t\telse\n+\t\t\terr = error(_(\"%s: object corrupt or missing: %s\"),\n+\t\t\t\t    oid_to_hex(oid), path);\n+\t}\n+\tif (err < 0)\n+\t\tgoto out;\n+\n+\tif (!contents && type != OBJ_BLOB)\n+\t\tBUG(\"read_loose_object streamed a non-blob\");\n+\n+\tif (data->opts->object_cb(oid, type, size, contents, &eaten,\n+\t\t\t\t  data->opts->object_payload)) {\n+\t\terr = -1;\n+\t\tgoto out;\n+\t}\n+\n+out:\n+\tif (err)\n+\t\tdata->error_found = true;\n+\tif (!eaten)\n+\t\tfree(contents);\n+\treturn 0; /* keep checking other objects, even if we saw an error */\n+}\n+\n+static int fsck_cruft(const char *basename, const char *path,\n+\t\t      void *data UNUSED)\n+{\n+\tif (!starts_with(basename, \"tmp_obj_\"))\n+\t\tfprintf_ln(stderr, _(\"bad sha1 file: %s\"), path);\n+\treturn 0;\n+}\n+\n+static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *cb_data)\n+{\n+\tstruct fsck_loose_data *data = cb_data;\n+\tdisplay_progress(data->progress, nr + 1);\n \treturn 0;\n }\n \n+static int odb_source_loose_fsck(struct odb_source *source,\n+\t\t\t\t struct odb_fsck_options *opts)\n+{\n+\tstruct odb_source_loose *loose = odb_source_loose_downcast(source);\n+\tstruct fsck_loose_data data = {\n+\t\t.source = loose,\n+\t\t.opts = opts,\n+\t};\n+\n+\tif (opts->flags & ODB_FSCK_VERBOSE)\n+\t\tfprintf_ln(stderr, _(\"Checking object directory\"));\n+\n+\tif (opts->flags & ODB_FSCK_PROGRESS)\n+\t\tdata.progress = start_progress(source->odb->repo,\n+\t\t\t\t\t       _(\"Checking object directories\"), 256);\n+\n+\tfor_each_loose_file_in_source(source, fsck_loose,\n+\t\t\t\t      fsck_cruft, fsck_subdir, &data);\n+\tdisplay_progress(data.progress, 256);\n+\tstop_progress(&data.progress);\n+\n+\treturn data.error_found ? -1 : 0;\n+}\n+\n struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \t\t\t\t\t      const char *path,\n \t\t\t\t\t      bool local)\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551525","messageId":"CAOLa=ZSVe2okfJZL-xt1PkADF67z8JZrtcFce+mecsoMaseKuA@mail.gmail.com","threadId":"66217","inReplyTo":"apUYiv36xvWe-oj7@pks.im","subject":"Re: [PATCH 08/10] builtin/fsck: move bitmap verification into the packed source","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-31T09:26:16Z","receivedAt":"2026-08-31T09:26:18Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Thu, Aug 27, 2026 at 06:54:51AM -0400, Karthik Nayak wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>> > The checks for bitmaps live in `verify_bitmap_files()`, which is called\n>> > by \"builtin/fsck.c\". These checks are obviously specific to the \"packed\"\n>> > backend.\n>> >\n>> > Move the logic into `odb_source_packed_fsck()`. As in preceding commits,\n>> > this means that we now properly honor both \"--connectivity-only\" and\n>> > \"--no-full\". Furthermore, we drop the dedicated `ERROR_BITMAP` bit and\n>> > instead use the generic `ERROR_OBJECT` bit.\n>> >\n>> > Note that this change also adapts `verify_bitmap_files()` to be\n>> > focussed on a single \"packed\" source instead of verifying bitmaps from\n>>\n>> nit: s/focussed/focused\n>\n> You can actually use both spellings [1], where \"focussed\" is more\n> commonly used in the UK. Anyway, I'll change this to help our American\n> friends out there.\n>\n> Patrick\n>\n> [1]: https://en.wiktionary.org/wiki/focussed\n\nI usually follow the UK spellings, I didn't know the focussed <> focused\nvariability.\n"},{"id":"551526","messageId":"CAOLa=ZSi1TiTZ=i=SQp+pmjTOm2_wY-NiCotx66+M6VDKx=ZXg@mail.gmail.com","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im","subject":"Re: [PATCH v2 00/10] odb: make consistency checks pluggable","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-08-31T09:26:57Z","receivedAt":"2026-08-31T09:27:00Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> this patch series makes object database consistency checks pluggable.\n>\n> This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\n> with the following two dependencsie merged into it:\n>\n>   - ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n>     `alternates_db` field, 2026-08-17)\n>\n>   - ps/odb-pluggable-pack-generation at 5176dd3d05 (bundle: generate\n>     packfiles via the object database, 2026-08-21)\n>\n> Changes in v2:\n>   - Some commit message improvements.\n>   - Link to v1: https://patch.msgid.link/20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im\n>\n> Thanks!\n>\n> Patrick\n>\n> ---\n> Patrick Steinhardt (10):\n>       builtin/fsck: use `fsck_obj_buffer()` when checking loose objects\n>       builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`\n>       builtin/fsck: de-globalize option handling\n>       builtin/fsck: don't check alternates with \"--no-full\"\n>       odb: provide infrastructure for pluggable fsck checks\n>       builtin/fsck: move packfile verification into the packed source\n>       builtin/fsck: move reverse index verification into the packed source\n>       builtin/fsck: move bitmap verification into the packed source\n>       builtin/fsck: move multi-pack index verification into the packed source\n>       builtin/fsck: move loose object verification into the loose source\n>\n>  builtin/fsck.c                | 296 ++++++++----------------------------------\n>  odb.c                         |   9 ++\n>  odb.h                         |  33 +++++\n>  odb/source-files.c            |  13 ++\n>  odb/source-inmemory.c         |   8 ++\n>  odb/source-loose.c            |  92 +++++++++++++\n>  odb/source-packed.c           | 117 +++++++++++++++++\n>  odb/source.h                  |  21 +++\n>  pack-bitmap.c                 |  26 ++--\n>  pack-bitmap.h                 |   2 +-\n>  t/t1450-fsck.sh               |   5 +\n>  t/t5319-multi-pack-index.sh   |  13 ++\n>  t/t5325-reverse-index.sh      |   8 ++\n>  t/t5326-multi-pack-bitmaps.sh |  10 +-\n>  14 files changed, 394 insertions(+), 259 deletions(-)\n>\n> Range-diff versus v1:\n>\n>  1:  cf49376600 !  1:  1aec903546 builtin/fsck: use `fsck_obj_buffer()` when checking loose objects\n>     @@ Commit message\n>\n>          When checking loose objects we manually parse the object buffer we have\n>          read from the on-disk file, mark the object and then call `fsck_obj()`.\n>     -    Almost the exact same steps are also performed by `fsck_obj_buffer()`.\n>     +    The exact same steps are also performed by `fsck_obj_buffer()`.\n>\n>          Stop open-coding this logic and call `fsck_obj_buffer()` instead.\n>\n>  2:  da2ca27041 !  2:  3804f0339e builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`\n>     @@ Commit message\n>          Furthermore, `fsck_obj()` has no callers other than `fsck_obj_buffer()`.\n>\n>          Refactor the code by merging those two functions. This makes it obvious\n>     -    which function does what, and it allows us to get rid of the early in\n>     -    `fsck_obj()` in case `SEEN` is set as the only caller unconditionally\n>     -    clears that bit before calling it anyway.\n>     +    which function does what, and it allows us to get rid of the early\n>     +    return in `fsck_obj()` in case `SEEN` is set as the only caller\n>     +    unconditionally clears that bit before calling it anyway.\n>\n>          Signed-off-by: Patrick Steinhardt <ps@pks.im>\n>\n>  3:  a24506f55e =  3:  b2cb9032cf builtin/fsck: de-globalize option handling\n>  4:  f6a407efd0 =  4:  10ee3b8baf builtin/fsck: don't check alternates with \"--no-full\"\n>  5:  31841a1f05 =  5:  1e65eec60e odb: provide infrastructure for pluggable fsck checks\n>  6:  2cd6d71983 =  6:  0b8cf751aa builtin/fsck: move packfile verification into the packed source\n>  7:  c0559f1820 =  7:  3a38a75549 builtin/fsck: move reverse index verification into the packed source\n>  8:  96ae1ce3c6 !  8:  dd3a4c6cea builtin/fsck: move bitmap verification into the packed source\n>     @@ Commit message\n>          instead use the generic `ERROR_OBJECT` bit.\n>\n>          Note that this change also adapts `verify_bitmap_files()` to be\n>     -    focussed on a single \"packed\" source instead of verifying bitmaps from\n>     +    focused on a single \"packed\" source instead of verifying bitmaps from\n>          all sources. This change is required as we already know to loop around\n>          the sources in `odb_fsck()` itself.\n>\n>  9:  4721f4b4ba =  9:  90ada56b7f builtin/fsck: move multi-pack index verification into the packed source\n> 10:  0b36829fd9 = 10:  b0f6fccae8 builtin/fsck: move loose object verification into the loose source\n>\n> ---\n> base-commit: 6b08999fb1b3ad0bad04d492dc206ad42839e274\n> change-id: 20260810-pks-odb-source-fsck-e64772c7ee5f\n\nThe changes here look good to me, thanks!\n"},{"id":"552526","messageId":"87a4ponipw.fsf@emacs.iotcl.com","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-5-f9b16ef4957b@pks.im","subject":"Re: [PATCH v2 05/10] odb: provide infrastructure for pluggable fsck checks","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-09-11T11:14:03Z","receivedAt":"2026-09-11T11:14:24Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The on-disk consistency checks in git-fsck(1) are conceptually\n> backend-specific: while connectivity checks and object-level parsing\n> checks are generic, verifying the physical integrity of packfiles and\n> loose objects is meaningful only to backends that use these formats:\n> Having these checks live in \"builtin/fsck.c\" violates that layering,\n> because it forces the command to reach directly into format-specific\n> internals.\n>\n> Provide new infrastructure to make these format-specific checks\n> pluggable and implement stubs for the different source types we already\n> have. In subsequent commits we'll move functionality over piece by\n> piece.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/fsck.c        | 16 +++++++++++-----\n>  odb.c                 |  9 +++++++++\n>  odb.h                 | 23 +++++++++++++++++++++++\n>  odb/source-files.c    | 13 +++++++++++++\n>  odb/source-inmemory.c |  8 ++++++++\n>  odb/source-loose.c    |  7 +++++++\n>  odb/source-packed.c   |  8 ++++++++\n>  odb/source.h          | 21 +++++++++++++++++++++\n>  8 files changed, 100 insertions(+), 5 deletions(-)\n>\n> diff --git a/builtin/fsck.c b/builtin/fsck.c\n> index 3f6056535f..adbe192e56 100644\n> --- a/builtin/fsck.c\n> +++ b/builtin/fsck.c\n> @@ -965,7 +965,9 @@ int cmd_fsck(int argc,\n>  \t     const char *prefix,\n>  \t     struct repository *repo)\n>  {\n> -\tint check_full = 1;\n> +\tstruct odb_fsck_options odb_fsck_opts = {\n> +\t\t.flags = ODB_FSCK_FULL,\n> +\t};\n>  \tint keep_cache_objects = 0;\n>  \tint name_objects = 0;\n>  \tint check_references = 1;\n> @@ -977,7 +979,8 @@ int cmd_fsck(int argc,\n>  \t\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n>  \t\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n>  \t\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n> -\t\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n> +\t\tOPT_BIT(0, \"full\", &odb_fsck_opts.flags,\n> +\t\t\tN_(\"also consider packs and alternate objects\"), ODB_FSCK_FULL),\n>  \t\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n>  \t\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n>  \t\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n> @@ -1018,7 +1021,7 @@ int cmd_fsck(int argc,\n>  \t\tshow_progress = 0;\n>  \n>  \tif (write_lost_and_found) {\n> -\t\tcheck_full = 1;\n> +\t\todb_fsck_opts.flags |= ODB_FSCK_FULL;\n>  \t\tinclude_reflogs = 0;\n>  \t}\n>  \n> @@ -1047,10 +1050,13 @@ int cmd_fsck(int argc,\n>  \t\t\t\t    mark_object_for_connectivity, repo, 0);\n>  \t} else {\n>  \t\tfor (source = repo->objects->sources; source; source = source->next)\n> -\t\t\tif (check_full || source->local)\n> +\t\t\tif ((odb_fsck_opts.flags & ODB_FSCK_FULL) || source->local)\n>  \t\t\t\tfsck_source(repo, source);\n>  \n> -\t\tif (check_full) {\n> +\t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n> +\t\t\terrors_found |= ERROR_OBJECT;\n> +\n> +\t\tif (odb_fsck_opts.flags & ODB_FSCK_FULL) {\n>  \t\t\tstruct packed_git *p;\n>  \t\t\tuint32_t total = 0, count = 0;\n>  \t\t\tstruct progress *progress = NULL;\n> diff --git a/odb.c b/odb.c\n> index 1fe20808eb..766043b685 100644\n> --- a/odb.c\n> +++ b/odb.c\n> @@ -1177,3 +1177,12 @@ void odb_reprepare(struct object_database *o)\n>  {\n>  \todb_prepare(o, ODB_PREPARE_FLUSH_CACHES);\n>  }\n> +\n> +int odb_fsck(struct object_database *odb, struct odb_fsck_options *options)\n> +{\n> +\tint ret = 0;\n> +\tfor (struct odb_source *source = odb->sources; source; source = source->next)\n> +\t\tif ((options->flags & ODB_FSCK_FULL) || source->local)\n> +\t\t\tret |= odb_source_fsck(source, options);\n\nShouldn't it be the responsibility of the source to determine whether it\nshould be included due to the `--full` flag? In the future there might\nbe other types of sources which have possibly a different meaning for\n\"local\". So would it make sense to have them check for ODB_FSCK_FULL\nthemselves.\n\n-- \nLaters,\nToon\n"},{"id":"552527","messageId":"878q58nip1.fsf@emacs.iotcl.com","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-7-f9b16ef4957b@pks.im","subject":"Re: [PATCH v2 07/10] builtin/fsck: move reverse index verification into the packed source","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-09-11T11:14:34Z","receivedAt":"2026-09-11T11:14:39Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The checks for reverse indexes live in `check_pack_rev_indexes()`, which\n> is hosted in \"builtin/fsck.c\". These checks are obviously specific to\n> the \"packed\" backend.\n>\n> Move the logic into `odb_source_packed_fsck()`. As in the preceding\n> commit, drop the dedicated `ERROR_PACK_REV_INDEX` bit and instead use\n> the generic `ERROR_OBJECT` bit.\n\nIt wasn't immediately obvious to me, but the check is moved to\nodb_source_packed_fsck() which is the callback for `.fsck` which is\ncalled by odb_fsck() in odb/odb.c. In builtin/fsck.c a negative return\nvalue is converted to ERROR_OBJECT.\n\nBecause it's part of the ODB, that makes sense to me.\n\n-- \nLaters,\nToon\n"},{"id":"552528","messageId":"877bksnior.fsf@emacs.iotcl.com","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-9-f9b16ef4957b@pks.im","subject":"Re: [PATCH v2 09/10] builtin/fsck: move multi-pack index verification into the packed source","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-09-11T11:14:44Z","receivedAt":"2026-09-11T11:14:48Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The checks for multi-pack indexes are hosted in `cmd_fsck()` directly.\n> These checks are obviously specific to the \"packed\" backend.\n>\n> Move the logic into `odb_source_packed_fsck()`. As in preceding commits,\n> this means that we now properly honor both \"--connectivity-only\" and\n> \"--no-full\". Furthermore, we drop the dedicated `ERROR_MULTI_PACK_INDEX`\n> bit and instead use the generic `ERROR_OBJECT` bit.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/fsck.c              | 18 ------------------\n>  odb/source-packed.c         | 27 +++++++++++++++++++++++++++\n>  t/t5319-multi-pack-index.sh | 13 +++++++++++++\n>  3 files changed, 40 insertions(+), 18 deletions(-)\n>\n> diff --git a/builtin/fsck.c b/builtin/fsck.c\n> index 2f7d29aa56..7eaea340b0 100644\n> --- a/builtin/fsck.c\n> +++ b/builtin/fsck.c\n> @@ -48,7 +48,6 @@ static timestamp_t now;\n>  #define ERROR_REACHABLE 02\n>  #define ERROR_REFS 010\n>  #define ERROR_COMMIT_GRAPH 020\n> -#define ERROR_MULTI_PACK_INDEX 040\n>  \n>  static const char *describe_object(const struct object_id *oid)\n>  {\n> @@ -1085,23 +1084,6 @@ int cmd_fsck(int argc,\n>  \t\t}\n>  \t}\n>  \n> -\tif (repo->settings.core_multi_pack_index) {\n> -\t\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n> -\n> -\t\tfor (source = repo->objects->sources; source; source = source->next) {\n> -\t\t\tchild_process_init(&midx_verify);\n> -\t\t\tmidx_verify.git_cmd = 1;\n> -\t\t\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n> -\t\t\t\t     \"verify\", \"--object-dir\", source->path, NULL);\n> -\t\t\tif (show_progress)\n> -\t\t\t\tstrvec_push(&midx_verify.args, \"--progress\");\n> -\t\t\telse\n> -\t\t\t\tstrvec_push(&midx_verify.args, \"--no-progress\");\n> -\t\t\tif (run_command(&midx_verify))\n> -\t\t\t\terrors_found |= ERROR_MULTI_PACK_INDEX;\n> -\t\t}\n> -\t}\n> -\n>  \tfree_snapshot_refs(&snap);\n>  \treturn errors_found;\n>  }\n> diff --git a/odb/source-packed.c b/odb/source-packed.c\n> index 2b5dc502f5..9f42552377 100644\n> --- a/odb/source-packed.c\n> +++ b/odb/source-packed.c\n> @@ -14,6 +14,7 @@\n>  #include \"packfile.h\"\n>  #include \"pack-bitmap.h\"\n>  #include \"progress.h\"\n> +#include \"run-command.h\"\n>  \n>  static int find_pack_entry(struct odb_source_packed *store,\n>  \t\t\t   const struct object_id *oid,\n> @@ -897,6 +898,29 @@ static int verify_reverse_indices(struct odb_source_packed *source,\n>  \treturn res;\n>  }\n>  \n> +static int verify_midx(struct odb_source_packed *source,\n> +\t\t       struct odb_fsck_options *opts)\n> +{\n> +\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n> +\tint ret = 0;\n\nI don't see much reason to use a `ret` value instead of using early\nreturns instead.\n\n> +\n> +\tif (!source->base.odb->repo->settings.core_multi_pack_index)\n\nBecause we cannot ensure where this function was called from, shall we\nBUG() if (!settings.initialized)?\n\n> +\t\treturn 0;\n> +\n> +\tchild_process_init(&midx_verify);\n> +\tmidx_verify.git_cmd = 1;\n> +\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n> +\t\t     \"verify\", \"--object-dir\", source->base.path, NULL);\n> +\tif (opts->flags & ODB_FSCK_PROGRESS)\n> +\t\tstrvec_push(&midx_verify.args, \"--progress\");\n> +\telse\n> +\t\tstrvec_push(&midx_verify.args, \"--no-progress\");\n> +\tif (run_command(&midx_verify))\n> +\t\tret = -1;\n> +\n> +\treturn ret;\n> +}\n> +\n>  static int odb_source_packed_fsck(struct odb_source *source,\n>  \t\t\t\t  struct odb_fsck_options *opts)\n>  {\n> @@ -912,6 +936,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n>  \tif (verify_bitmap_files(packed))\n>  \t\tret = -1;\n>  \n> +\tif (verify_midx(packed, opts) < 0)\n\nAny reason why you're checking negative value here and not in the if\nabove?\n\n> +\t\tret = -1;\n> +\n>  \treturn ret;\n>  }\n>  \n> diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh\n> index 68143cb5b7..20b010c33b 100755\n> --- a/t/t5319-multi-pack-index.sh\n> +++ b/t/t5319-multi-pack-index.sh\n> @@ -573,6 +573,19 @@ test_expect_success 'verify incorrect checksum' '\n>  \t\t$objdir \"incorrect checksum\"\n>  '\n>  \n> +test_expect_success 'git fsck --no-full checks multi-pack-index, --connectivity-only does not' '\n> +\tpos=$(($(wc -c <$objdir/pack/multi-pack-index) - 10)) &&\n> +\tcorrupt_midx_and_verify $pos \\\n> +\t\t\"\\377\\377\\377\\377\\377\\377\\377\\377\\377\\377\" \\\n> +\t\t$objdir \"incorrect checksum\" &&\n> +\n> +\ttest_must_fail git fsck --no-full 2>err &&\n> +\ttest_grep \"incorrect checksum\" err &&\n> +\n> +\tgit fsck --connectivity-only 2>err &&\n> +\ttest_grep ! \"incorrect checksum\" err\n> +'\n> +\n>  test_expect_success 'setup for v1-specific fsck tests' '\n>  \tgit -c midx.version=1 multi-pack-index write\n>  '\n>\n> -- \n> 2.55.0.979.g7e5102b832.dirty\n>\n>\n\n-- \nLaters,\nToon\n"},{"id":"552529","messageId":"875x0cnio5.fsf@emacs.iotcl.com","threadId":"66217","inReplyTo":"20260831-pks-odb-source-fsck-v2-10-f9b16ef4957b@pks.im","subject":"Re: [PATCH v2 10/10] builtin/fsck: move loose object verification into the loose source","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-09-11T11:15:06Z","receivedAt":"2026-09-11T11:15:26Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The consistency checks for loose objects are hosted by \"builtin/fsck.c\".\n> These checks are obviously specific to the \"loose\" backend.\n>\n> Move the logic into `odb_source_loose_fsck()`. Introduce a new \"verbose\"\n> flag so that we can properly retain semantics around whether or not we\n> want to print some status messages.\n>\n> Note that this fixes a bug as a side effect: the progress meter was\n> captured in the callback data before `start_progress()` was even called,\n> so the per-subdirectory progress updates always operated on a NULL\n> pointer and the meter jumped straight from 0 to 256 upon completion. The\n> new code only sets up the callback data's progress meter after it has\n> been created, so the progress display now advances incrementally again.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/fsck.c     | 91 ++----------------------------------------------------\n>  odb.h              |  3 ++\n>  odb/source-loose.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++--\n>  3 files changed, 93 insertions(+), 90 deletions(-)\n>\n> diff --git a/builtin/fsck.c b/builtin/fsck.c\n> index 7eaea340b0..4af1d874cc 100644\n> --- a/builtin/fsck.c\n> +++ b/builtin/fsck.c\n> @@ -12,7 +12,6 @@\n>  #include \"parse-options.h\"\n>  #include \"progress.h\"\n>  #include \"packfile.h\"\n> -#include \"object-file.h\"\n>  #include \"object-name.h\"\n>  #include \"odb.h\"\n>  #include \"odb/streaming.h\"\n> @@ -695,88 +694,6 @@ static void process_refs(struct repository *repo, struct snapshot *snap)\n>  \t}\n>  }\n>  \n> -struct for_each_loose_cb {\n> -\tstruct repository *repo;\n> -\tstruct progress *progress;\n> -};\n> -\n> -static int fsck_loose(const struct object_id *oid, const char *path,\n> -\t\t      void *cb_data)\n> -{\n> -\tstruct for_each_loose_cb *data = cb_data;\n> -\tenum object_type type = OBJ_NONE;\n> -\tsize_t size;\n> -\tvoid *contents = NULL;\n> -\tint eaten;\n> -\tstruct object_info oi = OBJECT_INFO_INIT;\n> -\tstruct object_id real_oid = *null_oid(data->repo->hash_algo);\n> -\tint err = 0;\n> -\n> -\toi.sizep = &size;\n> -\toi.typep = &type;\n> -\n> -\tif (read_loose_object(data->repo, path, oid, &real_oid, &contents, &oi) < 0) {\n> -\t\tif (contents && !oideq(&real_oid, oid))\n> -\t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n> -\t\t\t\t    oid_to_hex(&real_oid), path);\n> -\t\telse\n> -\t\t\terr = error(_(\"%s: object corrupt or missing: %s\"),\n> -\t\t\t\t    oid_to_hex(oid), path);\n> -\t}\n> -\tif (err < 0) {\n> -\t\terrors_found |= ERROR_OBJECT;\n> -\t\tfree(contents);\n> -\t\treturn 0; /* keep checking other objects */\n> -\t}\n> -\n> -\tif (!contents && type != OBJ_BLOB)\n> -\t\tBUG(\"read_loose_object streamed a non-blob\");\n> -\n> -\tif (fsck_obj_buffer(oid, type, size, contents, &eaten, data->repo))\n> -\t\terrors_found |= ERROR_OBJECT;\n> -\n> -\tif (!eaten)\n> -\t\tfree(contents);\n> -\treturn 0; /* keep checking other objects, even if we saw an error */\n> -}\n> -\n> -static int fsck_cruft(const char *basename, const char *path,\n> -\t\t      void *data UNUSED)\n> -{\n> -\tif (!starts_with(basename, \"tmp_obj_\"))\n> -\t\tfprintf_ln(stderr, _(\"bad sha1 file: %s\"), path);\n> -\treturn 0;\n> -}\n> -\n> -static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *data)\n> -{\n> -\tstruct for_each_loose_cb *cb_data = data;\n> -\tstruct progress *progress = cb_data->progress;\n> -\tdisplay_progress(progress, nr + 1);\n> -\treturn 0;\n> -}\n> -\n> -static void fsck_source(struct repository *repo, struct odb_source *source)\n> -{\n> -\tstruct progress *progress = NULL;\n> -\tstruct for_each_loose_cb cb_data = {\n> -\t\t.repo = source->odb->repo,\n> -\t\t.progress = progress,\n> -\t};\n> -\n> -\tif (verbose)\n> -\t\tfprintf_ln(stderr, _(\"Checking object directory\"));\n> -\n> -\tif (show_progress)\n> -\t\tprogress = start_progress(repo,\n> -\t\t\t\t\t  _(\"Checking object directories\"), 256);\n> -\n> -\tfor_each_loose_file_in_source(source, fsck_loose,\n> -\t\t\t\t      fsck_cruft, fsck_subdir, &cb_data);\n> -\tdisplay_progress(progress, 256);\n> -\tstop_progress(&progress);\n> -}\n> -\n>  static int fsck_cache_tree(struct repository *repo, struct cache_tree *it, const char *index_path)\n>  {\n>  \tint i;\n> @@ -978,8 +895,10 @@ int cmd_fsck(int argc,\n>  \n>  \tif (show_progress == -1)\n>  \t\tshow_progress = isatty(2);\n> -\tif (verbose)\n> +\tif (verbose) {\n>  \t\tshow_progress = 0;\n> +\t\todb_fsck_opts.flags |= ODB_FSCK_VERBOSE;\n> +\t}\n>  \tif (show_progress)\n>  \t\todb_fsck_opts.flags |= ODB_FSCK_PROGRESS;\n>  \n> @@ -1012,10 +931,6 @@ int cmd_fsck(int argc,\n>  \t\todb_for_each_object(repo->objects, NULL,\n>  \t\t\t\t    mark_object_for_connectivity, repo, 0);\n>  \t} else {\n> -\t\tfor (source = repo->objects->sources; source; source = source->next)\n> -\t\t\tif ((odb_fsck_opts.flags & ODB_FSCK_FULL) || source->local)\n> -\t\t\t\tfsck_source(repo, source);\n> -\n>  \t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n>  \t\t\terrors_found |= ERROR_OBJECT;\n>  \n> diff --git a/odb.h b/odb.h\n> index 0bf6c8d7d2..b87f281cbd 100644\n> --- a/odb.h\n> +++ b/odb.h\n> @@ -218,6 +218,9 @@ enum odb_fsck_flags {\n>  \n>  \t/* Display a progress meter, if sensible. */\n>  \tODB_FSCK_PROGRESS = (1 << 1),\n> +\n> +\t/* Be extra verbose when checking the database. */\n> +\tODB_FSCK_VERBOSE = (1 << 2),\n\nShall we document this one is mutually exclusive with ODB_FSCK_PROGRESS?\n\n>  };\n>  \n>  /* Options that shall be passed to `odb_fsck()`. */\n> diff --git a/odb/source-loose.c b/odb/source-loose.c\n> index f68d3c4d6c..efef9ca61f 100644\n> --- a/odb/source-loose.c\n> +++ b/odb/source-loose.c\n> @@ -12,6 +12,7 @@\n>  #include \"odb/streaming.h\"\n>  #include \"oidtree.h\"\n>  #include \"path.h\"\n> +#include \"progress.h\"\n>  #include \"repository.h\"\n>  #include \"strbuf.h\"\n>  #include \"tempfile.h\"\n> @@ -1031,12 +1032,96 @@ static void odb_source_loose_free(struct odb_source *source)\n>  \tfree(loose);\n>  }\n>  \n> -static int odb_source_loose_fsck(struct odb_source *source UNUSED,\n> -\t\t\t\t struct odb_fsck_options *opts UNUSED)\n> +struct fsck_loose_data {\n> +\tstruct odb_source_loose *source;\n> +\tstruct odb_fsck_options *opts;\n> +\tstruct progress *progress;\n> +\tbool error_found;\n> +};\n> +\n> +static int fsck_loose(const struct object_id *oid, const char *path,\n> +\t\t      void *cb_data)\n>  {\n> +\tstruct fsck_loose_data *data = cb_data;\n> +\tenum object_type type = OBJ_NONE;\n> +\tsize_t size;\n> +\tvoid *contents = NULL;\n> +\tint eaten = 0;\n> +\tstruct object_info oi = OBJECT_INFO_INIT;\n> +\tstruct object_id real_oid = *null_oid(data->source->base.odb->repo->hash_algo);\n> +\tint err = 0;\n> +\n> +\toi.sizep = &size;\n> +\toi.typep = &type;\n> +\n> +\tif (read_loose_object(data->source->base.odb->repo,\n> +\t\t\t      path, oid, &real_oid, &contents, &oi) < 0) {\n> +\t\tif (contents && !oideq(&real_oid, oid))\n> +\t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n> +\t\t\t\t    oid_to_hex(&real_oid), path);\n> +\t\telse\n> +\t\t\terr = error(_(\"%s: object corrupt or missing: %s\"),\n> +\t\t\t\t    oid_to_hex(oid), path);\n> +\t}\n> +\tif (err < 0)\n> +\t\tgoto out;\n> +\n> +\tif (!contents && type != OBJ_BLOB)\n> +\t\tBUG(\"read_loose_object streamed a non-blob\");\n> +\n> +\tif (data->opts->object_cb(oid, type, size, contents, &eaten,\n> +\t\t\t\t  data->opts->object_payload)) {\n\nShould we guard data->opts->object_cb being NULL?\n\n> +\t\terr = -1;\n> +\t\tgoto out;\n> +\t}\n> +\n> +out:\n> +\tif (err)\n> +\t\tdata->error_found = true;\n> +\tif (!eaten)\n> +\t\tfree(contents);\n> +\treturn 0; /* keep checking other objects, even if we saw an error */\n\nOkay, this function is called by for_each_loose_file_in_source() so we\nneed to return 0 to keep that loop going. I'm not a huge fan of the\nerror_found construct, but that isn't changed, so it's fine.\n\n> +}\n> +\n> +static int fsck_cruft(const char *basename, const char *path,\n> +\t\t      void *data UNUSED)\n> +{\n> +\tif (!starts_with(basename, \"tmp_obj_\"))\n> +\t\tfprintf_ln(stderr, _(\"bad sha1 file: %s\"), path);\n> +\treturn 0;\n> +}\n> +\n> +static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *cb_data)\n> +{\n> +\tstruct fsck_loose_data *data = cb_data;\n> +\tdisplay_progress(data->progress, nr + 1);\n>  \treturn 0;\n>  }\n>  \n> +static int odb_source_loose_fsck(struct odb_source *source,\n> +\t\t\t\t struct odb_fsck_options *opts)\n> +{\n> +\tstruct odb_source_loose *loose = odb_source_loose_downcast(source);\n> +\tstruct fsck_loose_data data = {\n> +\t\t.source = loose,\n> +\t\t.opts = opts,\n> +\t};\n> +\n> +\tif (opts->flags & ODB_FSCK_VERBOSE)\n> +\t\tfprintf_ln(stderr, _(\"Checking object directory\"));\n> +\n> +\tif (opts->flags & ODB_FSCK_PROGRESS)\n> +\t\tdata.progress = start_progress(source->odb->repo,\n> +\t\t\t\t\t       _(\"Checking object directories\"), 256);\n> +\n> +\tfor_each_loose_file_in_source(source, fsck_loose,\n> +\t\t\t\t      fsck_cruft, fsck_subdir, &data);\n> +\tdisplay_progress(data.progress, 256);\n> +\tstop_progress(&data.progress);\n> +\n> +\treturn data.error_found ? -1 : 0;\n> +}\n> +\n>  struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n>  \t\t\t\t\t      const char *path,\n>  \t\t\t\t\t      bool local)\n>\n> -- \n> 2.55.0.979.g7e5102b832.dirty\n>\n>\n\n-- \nLaters,\nToon\n"},{"id":"552530","messageId":"aqPyqg0no7k-VHty@pks.im","threadId":"66217","inReplyTo":"87a4ponipw.fsf@emacs.iotcl.com","subject":"Re: [PATCH v2 05/10] odb: provide infrastructure for pluggable fsck checks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T12:23:06Z","receivedAt":"2026-09-11T12:23:16Z","isPatch":true,"body":"On Fri, Sep 11, 2026 at 01:14:03PM +0200, Toon Claes wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/odb.c b/odb.c\n> > index 1fe20808eb..766043b685 100644\n> > --- a/odb.c\n> > +++ b/odb.c\n> > @@ -1177,3 +1177,12 @@ void odb_reprepare(struct object_database *o)\n> >  {\n> >  \todb_prepare(o, ODB_PREPARE_FLUSH_CACHES);\n> >  }\n> > +\n> > +int odb_fsck(struct object_database *odb, struct odb_fsck_options *options)\n> > +{\n> > +\tint ret = 0;\n> > +\tfor (struct odb_source *source = odb->sources; source; source = source->next)\n> > +\t\tif ((options->flags & ODB_FSCK_FULL) || source->local)\n> > +\t\t\tret |= odb_source_fsck(source, options);\n> \n> Shouldn't it be the responsibility of the source to determine whether it\n> should be included due to the `--full` flag? In the future there might\n> be other types of sources which have possibly a different meaning for\n> \"local\". So would it make sense to have them check for ODB_FSCK_FULL\n> themselves.\n\nThat's actually a sensible idea, doubly so because we're going to move\nhandling of alternates into the source itself. So at that point, we\nwould be forced to move it into the \"files\" backend anyway. Will adapt.\n\nPatrick\n"},{"id":"552531","messageId":"aqPyw2mHJ9kt-xna@pks.im","threadId":"66217","inReplyTo":"877bksnior.fsf@emacs.iotcl.com","subject":"Re: [PATCH v2 09/10] builtin/fsck: move multi-pack index verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T12:23:31Z","receivedAt":"2026-09-11T12:23:38Z","isPatch":true,"body":"On Fri, Sep 11, 2026 at 01:14:44PM +0200, Toon Claes wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/odb/source-packed.c b/odb/source-packed.c\n> > index 2b5dc502f5..9f42552377 100644\n> > --- a/odb/source-packed.c\n> > +++ b/odb/source-packed.c\n> > @@ -14,6 +14,7 @@\n> >  #include \"packfile.h\"\n> >  #include \"pack-bitmap.h\"\n> >  #include \"progress.h\"\n> > +#include \"run-command.h\"\n> >  \n> >  static int find_pack_entry(struct odb_source_packed *store,\n> >  \t\t\t   const struct object_id *oid,\n> > @@ -897,6 +898,29 @@ static int verify_reverse_indices(struct odb_source_packed *source,\n> >  \treturn res;\n> >  }\n> >  \n> > +static int verify_midx(struct odb_source_packed *source,\n> > +\t\t       struct odb_fsck_options *opts)\n> > +{\n> > +\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n> > +\tint ret = 0;\n> \n> I don't see much reason to use a `ret` value instead of using early\n> returns instead.\n\nFair enough.\n\n> > +\n> > +\tif (!source->base.odb->repo->settings.core_multi_pack_index)\n> \n> Because we cannot ensure where this function was called from, shall we\n> BUG() if (!settings.initialized)?\n\nGood point, but I think it's preferable to call\n`prepare_repo_settings()` instead.\n\n> > @@ -912,6 +936,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n> >  \tif (verify_bitmap_files(packed))\n> >  \t\tret = -1;\n> >  \n> > +\tif (verify_midx(packed, opts) < 0)\n> \n> Any reason why you're checking negative value here and not in the if\n> above?\n\nNot specifically, and in theory both could check for `< 0`. But I\nrefrained from doing so when moving around `verify_bitmap_file()`\nbecause in the preimage we didn't check for a negative value, either,\nand it would have thus caused more questions.\n\nSo I think I'd leave this part as-is.\n\nPatrick\n"},{"id":"552532","messageId":"aqPy0q0LJJCcBgZY@pks.im","threadId":"66217","inReplyTo":"875x0cnio5.fsf@emacs.iotcl.com","subject":"Re: [PATCH v2 10/10] builtin/fsck: move loose object verification into the loose source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T12:23:46Z","receivedAt":"2026-09-11T12:23:52Z","isPatch":true,"body":"On Fri, Sep 11, 2026 at 01:15:06PM +0200, Toon Claes wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/odb.h b/odb.h\n> > index 0bf6c8d7d2..b87f281cbd 100644\n> > --- a/odb.h\n> > +++ b/odb.h\n> > @@ -218,6 +218,9 @@ enum odb_fsck_flags {\n> >  \n> >  \t/* Display a progress meter, if sensible. */\n> >  \tODB_FSCK_PROGRESS = (1 << 1),\n> > +\n> > +\t/* Be extra verbose when checking the database. */\n> > +\tODB_FSCK_VERBOSE = (1 << 2),\n> \n> Shall we document this one is mutually exclusive with ODB_FSCK_PROGRESS?\n\nBut is it really? Sure, we'll potentially have interleaving output where\nwe print log messages followed by progress output. But as far as I can\nsee, we have nothing where we fully interleave so that the progress\noutput would be mangled.\n\n> > diff --git a/odb/source-loose.c b/odb/source-loose.c\n> > index f68d3c4d6c..efef9ca61f 100644\n> > --- a/odb/source-loose.c\n> > +++ b/odb/source-loose.c\n> > @@ -1031,12 +1032,96 @@ static void odb_source_loose_free(struct odb_source *source)\n> >  \tfree(loose);\n> >  }\n> >  \n> > -static int odb_source_loose_fsck(struct odb_source *source UNUSED,\n> > -\t\t\t\t struct odb_fsck_options *opts UNUSED)\n> > +struct fsck_loose_data {\n> > +\tstruct odb_source_loose *source;\n> > +\tstruct odb_fsck_options *opts;\n> > +\tstruct progress *progress;\n> > +\tbool error_found;\n> > +};\n> > +\n> > +static int fsck_loose(const struct object_id *oid, const char *path,\n> > +\t\t      void *cb_data)\n> >  {\n> > +\tstruct fsck_loose_data *data = cb_data;\n> > +\tenum object_type type = OBJ_NONE;\n> > +\tsize_t size;\n> > +\tvoid *contents = NULL;\n> > +\tint eaten = 0;\n> > +\tstruct object_info oi = OBJECT_INFO_INIT;\n> > +\tstruct object_id real_oid = *null_oid(data->source->base.odb->repo->hash_algo);\n> > +\tint err = 0;\n> > +\n> > +\toi.sizep = &size;\n> > +\toi.typep = &type;\n> > +\n> > +\tif (read_loose_object(data->source->base.odb->repo,\n> > +\t\t\t      path, oid, &real_oid, &contents, &oi) < 0) {\n> > +\t\tif (contents && !oideq(&real_oid, oid))\n> > +\t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n> > +\t\t\t\t    oid_to_hex(&real_oid), path);\n> > +\t\telse\n> > +\t\t\terr = error(_(\"%s: object corrupt or missing: %s\"),\n> > +\t\t\t\t    oid_to_hex(oid), path);\n> > +\t}\n> > +\tif (err < 0)\n> > +\t\tgoto out;\n> > +\n> > +\tif (!contents && type != OBJ_BLOB)\n> > +\t\tBUG(\"read_loose_object streamed a non-blob\");\n> > +\n> > +\tif (data->opts->object_cb(oid, type, size, contents, &eaten,\n> > +\t\t\t\t  data->opts->object_payload)) {\n> \n> Should we guard data->opts->object_cb being NULL?\n\nI don't see a reason for that -- we don't currently have any callers\nthat do, and we can still introduce this check if we ever grow one.\n\nThanks!\n\nPatrick\n"},{"id":"552540","messageId":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im","subject":"[PATCH v3 00/10] odb: make consistency checks pluggable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:24Z","receivedAt":"2026-09-11T13:27:33Z","isPatch":true,"body":"Hi,\n\nthis patch series makes object database consistency checks pluggable.\n\nThis series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\nwith the following two dependencsie merged into it:\n\n  - ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n    `alternates_db` field, 2026-08-17)\n\n  - ps/odb-pluggable-pack-generation at 5176dd3d05 (bundle: generate\n    packfiles via the object database, 2026-08-21)\n\nChanges in v3:\n  - Move check for `ODB_FSCK_FULL || local` into the \"files\" backend.\n  - Ensure that repo settings are prepared.\n  - Drop a mostly-useless `ret` variable.\n  - Link to v2: https://patch.msgid.link/20260831-pks-odb-source-fsck-v2-0-f9b16ef4957b@pks.im\n\nChanges in v2:\n  - Some commit message improvements.\n  - Link to v1: https://patch.msgid.link/20260825-pks-odb-source-fsck-v1-0-b756de0bf24f@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (10):\n      builtin/fsck: use `fsck_obj_buffer()` when checking loose objects\n      builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`\n      builtin/fsck: de-globalize option handling\n      builtin/fsck: don't check alternates with \"--no-full\"\n      odb: provide infrastructure for pluggable fsck checks\n      builtin/fsck: move packfile verification into the packed source\n      builtin/fsck: move reverse index verification into the packed source\n      builtin/fsck: move bitmap verification into the packed source\n      builtin/fsck: move multi-pack index verification into the packed source\n      builtin/fsck: move loose object verification into the loose source\n\n builtin/fsck.c                | 296 ++++++++----------------------------------\n odb.c                         |   8 ++\n odb.h                         |  33 +++++\n odb/source-files.c            |  16 +++\n odb/source-inmemory.c         |   8 ++\n odb/source-loose.c            |  92 +++++++++++++\n odb/source-packed.c           | 117 +++++++++++++++++\n odb/source.h                  |  21 +++\n pack-bitmap.c                 |  26 ++--\n pack-bitmap.h                 |   2 +-\n t/t1450-fsck.sh               |   5 +\n t/t5319-multi-pack-index.sh   |  13 ++\n t/t5325-reverse-index.sh      |   8 ++\n t/t5326-multi-pack-bitmaps.sh |  10 +-\n 14 files changed, 396 insertions(+), 259 deletions(-)\n\nRange-diff versus v2:\n\n 1:  099ad8ddcd =  1:  575a49f5a1 builtin/fsck: use `fsck_obj_buffer()` when checking loose objects\n 2:  4e223cfab6 =  2:  71040c9b34 builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`\n 3:  108b522c55 =  3:  adb2e035ee builtin/fsck: de-globalize option handling\n 4:  c686809406 =  4:  68d143fc9f builtin/fsck: don't check alternates with \"--no-full\"\n 5:  a82a1a8ed2 !  5:  bb785f4f00 odb: provide infrastructure for pluggable fsck checks\n    @@ odb.c: void odb_reprepare(struct object_database *o)\n     +{\n     +\tint ret = 0;\n     +\tfor (struct odb_source *source = odb->sources; source; source = source->next)\n    -+\t\tif ((options->flags & ODB_FSCK_FULL) || source->local)\n    -+\t\t\tret |= odb_source_fsck(source, options);\n    ++\t\tret |= odb_source_fsck(source, options);\n     +\treturn ret;\n     +}\n     \n    @@ odb/source-files.c: static int odb_source_files_generate_pack(struct odb_source\n     +\tstruct odb_source_files *files = odb_source_files_downcast(source);\n     +\tint ret = 0;\n     +\n    ++\tif (!(opts->flags & ODB_FSCK_FULL) && !source->local)\n    ++\t\treturn 0;\n    ++\n     +\tret |= odb_source_fsck(&files->loose->base, opts);\n     +\tret |= odb_source_fsck(&files->packed->base, opts);\n     +\n 6:  166d9ad073 =  6:  9df4ebd53e builtin/fsck: move packfile verification into the packed source\n 7:  83f1b18308 =  7:  b7b28d2ab4 builtin/fsck: move reverse index verification into the packed source\n 8:  a760738e7a =  8:  e0efbab606 builtin/fsck: move bitmap verification into the packed source\n 9:  f0acd3bdff !  9:  5bebd3fded builtin/fsck: move multi-pack index verification into the packed source\n    @@ odb/source-packed.c: static int verify_reverse_indices(struct odb_source_packed\n     +\t\t       struct odb_fsck_options *opts)\n     +{\n     +\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n    -+\tint ret = 0;\n     +\n    ++\tprepare_repo_settings(source->base.odb->repo);\n     +\tif (!source->base.odb->repo->settings.core_multi_pack_index)\n     +\t\treturn 0;\n     +\n    @@ odb/source-packed.c: static int verify_reverse_indices(struct odb_source_packed\n     +\telse\n     +\t\tstrvec_push(&midx_verify.args, \"--no-progress\");\n     +\tif (run_command(&midx_verify))\n    -+\t\tret = -1;\n    ++\t\treturn -1;\n     +\n    -+\treturn ret;\n    ++\treturn 0;\n     +}\n     +\n      static int odb_source_packed_fsck(struct odb_source *source,\n10:  d140d15980 = 10:  f0f00e573a builtin/fsck: move loose object verification into the loose source\n\n---\nbase-commit: 6b08999fb1b3ad0bad04d492dc206ad42839e274\nchange-id: 20260810-pks-odb-source-fsck-e64772c7ee5f\n\n"},{"id":"552541","messageId":"20260911-pks-odb-source-fsck-v3-1-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","subject":"[PATCH v3 01/10] builtin/fsck: use `fsck_obj_buffer()` when checking loose objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:25Z","receivedAt":"2026-09-11T13:27:34Z","isPatch":true,"body":"When checking loose objects we manually parse the object buffer we have\nread from the on-disk file, mark the object and then call `fsck_obj()`.\nThe exact same steps are also performed by `fsck_obj_buffer()`.\n\nStop open-coding this logic and call `fsck_obj_buffer()` instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 17 +----------------\n 1 file changed, 1 insertion(+), 16 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 892c5661d9..3c4127f4d8 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -722,7 +722,6 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n \t\t      void *cb_data)\n {\n \tstruct for_each_loose_cb *data = cb_data;\n-\tstruct object *obj;\n \tenum object_type type = OBJ_NONE;\n \tsize_t size;\n \tvoid *contents = NULL;\n@@ -751,21 +750,7 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n \tif (!contents && type != OBJ_BLOB)\n \t\tBUG(\"read_loose_object streamed a non-blob\");\n \n-\tobj = parse_object_buffer(data->repo, oid, type, size,\n-\t\t\t\t  contents, &eaten);\n-\n-\tif (!obj) {\n-\t\terrors_found |= ERROR_OBJECT;\n-\t\terror(_(\"%s: object could not be parsed: %s\"),\n-\t\t      oid_to_hex(oid), path);\n-\t\tif (!eaten)\n-\t\t\tfree(contents);\n-\t\treturn 0; /* keep checking other objects */\n-\t}\n-\n-\tobj->flags &= ~(REACHABLE | SEEN);\n-\tobj->flags |= HAS_OBJ;\n-\tif (fsck_obj(data->repo, obj, contents, size))\n+\tif (fsck_obj_buffer(oid, type, size, contents, &eaten, data->repo))\n \t\terrors_found |= ERROR_OBJECT;\n \n \tif (!eaten)\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552542","messageId":"20260911-pks-odb-source-fsck-v3-2-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","subject":"[PATCH v3 02/10] builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:26Z","receivedAt":"2026-09-11T13:27:36Z","isPatch":true,"body":"The interfaces of the functions `fsck_obj()` and `fsck_obj_buffer()` are\nsomewhat similar to one another. The only difference between those two\nis that `fsck_obj()` takes an already-parsed object as input, whereas\n`fsck_obj_buffer()` parses the buffer and then calls `fsck_obj()`.\n\nFurthermore, `fsck_obj()` has no callers other than `fsck_obj_buffer()`.\n\nRefactor the code by merging those two functions. This makes it obvious\nwhich function does what, and it allows us to get rid of the early\nreturn in `fsck_obj()` in case `SEEN` is set as the only caller\nunconditionally clears that bit before calling it anyway.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 47 ++++++++++++++++++++---------------------------\n 1 file changed, 20 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 3c4127f4d8..bed8481893 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -401,14 +401,27 @@ static void check_connectivity(struct repository *repo)\n \t}\n }\n \n-static int fsck_obj(struct repository *repo,\n-\t\t    struct object *obj, void *buffer, unsigned long size)\n+static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,\n+\t\t\t   unsigned long size, void *buffer, int *eaten, void *cb_data)\n {\n+\tstruct repository *repo = cb_data;\n+\tstruct object *obj;\n \tint err;\n \n-\tif (obj->flags & SEEN)\n-\t\treturn 0;\n-\tobj->flags |= SEEN;\n+\t/*\n+\t * Note, buffer may be NULL if type is OBJ_BLOB. See\n+\t * verify_packfile(), data_valid variable for details.\n+\t */\n+\tobj = parse_object_buffer(repo, oid, type, size, buffer, eaten);\n+\tif (!obj) {\n+\t\terrors_found |= ERROR_OBJECT;\n+\t\terr = error(_(\"%s: object corrupt or missing\"),\n+\t\t\t    oid_to_hex(oid));\n+\t\tgoto out;\n+\t}\n+\n+\tobj->flags &= ~REACHABLE;\n+\tobj->flags |= HAS_OBJ | SEEN;\n \n \tif (verbose)\n \t\tfprintf_ln(stderr, _(\"Checking %s %s\"),\n@@ -417,6 +430,7 @@ static int fsck_obj(struct repository *repo,\n \n \tif (fsck_walk(obj, NULL, &fsck_obj_options))\n \t\tobjerror(repo, obj, _(\"broken links\"));\n+\n \terr = fsck_object(obj, buffer, size, &fsck_obj_options);\n \tif (err)\n \t\tgoto out;\n@@ -442,32 +456,11 @@ static int fsck_obj(struct repository *repo,\n \t}\n \n out:\n-\tif (obj->type == OBJ_TREE)\n+\tif (obj && obj->type == OBJ_TREE)\n \t\tfree_tree_buffer((struct tree *)obj);\n \treturn err;\n }\n \n-static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,\n-\t\t\t   unsigned long size, void *buffer, int *eaten, void *cb_data)\n-{\n-\tstruct repository *repo = cb_data;\n-\tstruct object *obj;\n-\n-\t/*\n-\t * Note, buffer may be NULL if type is OBJ_BLOB. See\n-\t * verify_packfile(), data_valid variable for details.\n-\t */\n-\tobj = parse_object_buffer(repo, oid, type, size, buffer, eaten);\n-\tif (!obj) {\n-\t\terrors_found |= ERROR_OBJECT;\n-\t\treturn error(_(\"%s: object corrupt or missing\"),\n-\t\t\t     oid_to_hex(oid));\n-\t}\n-\tobj->flags &= ~(REACHABLE | SEEN);\n-\tobj->flags |= HAS_OBJ;\n-\treturn fsck_obj(repo, obj, buffer, size);\n-}\n-\n static int default_refs;\n \n static void fsck_handle_reflog_oid(struct repository *repo,\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552543","messageId":"20260911-pks-odb-source-fsck-v3-3-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","subject":"[PATCH v3 03/10] builtin/fsck: de-globalize option handling","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:27Z","receivedAt":"2026-09-11T13:27:38Z","isPatch":true,"body":"In subsequent commits we're about to rework some of the option handling\nin git-fsck(1) a bit. It is currently a bit of a mess though due to lots\nof global state that makes it hard to see which flags are used where\nexactly.\n\nRefactor the code by moving the fsck options into `cmd_fsck()`. This\nallows us to convert some of the options into function-local variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 45 ++++++++++++++++++++++-----------------------\n 1 file changed, 22 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex bed8481893..5132ff0f15 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -37,10 +37,8 @@ static int show_root;\n static int show_tags;\n static int show_unreachable;\n static int include_reflogs = 1;\n-static int check_full = 1;\n static int connectivity_only;\n static int check_strict;\n-static int keep_cache_objects;\n static struct fsck_options fsck_walk_options;\n static struct fsck_options fsck_obj_options;\n static int errors_found;\n@@ -48,8 +46,6 @@ static int write_lost_and_found;\n static int verbose;\n static int show_progress = -1;\n static int show_dangling = 1;\n-static int name_objects;\n-static int check_references = 1;\n static timestamp_t now;\n #define ERROR_OBJECT 01\n #define ERROR_REACHABLE 02\n@@ -964,30 +960,33 @@ static char const * const fsck_usage[] = {\n \tNULL\n };\n \n-static struct option fsck_opts[] = {\n-\tOPT__VERBOSE(&verbose, N_(\"be verbose\")),\n-\tOPT_BOOL(0, \"unreachable\", &show_unreachable, N_(\"show unreachable objects\")),\n-\tOPT_BOOL(0, \"dangling\", &show_dangling, N_(\"show dangling objects\")),\n-\tOPT_BOOL(0, \"tags\", &show_tags, N_(\"report tags\")),\n-\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n-\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n-\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n-\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n-\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n-\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n-\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n-\t\t\t\tN_(\"write dangling objects in .git/lost-found\")),\n-\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n-\tOPT_BOOL(0, \"name-objects\", &name_objects, N_(\"show verbose names for reachable objects\")),\n-\tOPT_BOOL(0, \"references\", &check_references, N_(\"check reference database consistency\")),\n-\tOPT_END(),\n-};\n-\n int cmd_fsck(int argc,\n \t     const char **argv,\n \t     const char *prefix,\n \t     struct repository *repo)\n {\n+\tint check_full = 1;\n+\tint keep_cache_objects = 0;\n+\tint name_objects = 0;\n+\tint check_references = 1;\n+\tstruct option fsck_opts[] = {\n+\t\tOPT__VERBOSE(&verbose, N_(\"be verbose\")),\n+\t\tOPT_BOOL(0, \"unreachable\", &show_unreachable, N_(\"show unreachable objects\")),\n+\t\tOPT_BOOL(0, \"dangling\", &show_dangling, N_(\"show dangling objects\")),\n+\t\tOPT_BOOL(0, \"tags\", &show_tags, N_(\"report tags\")),\n+\t\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n+\t\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n+\t\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n+\t\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n+\t\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n+\t\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n+\t\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n+\t\t\t\t\tN_(\"write dangling objects in .git/lost-found\")),\n+\t\tOPT_BOOL(0, \"progress\", &show_progress, N_(\"show progress\")),\n+\t\tOPT_BOOL(0, \"name-objects\", &name_objects, N_(\"show verbose names for reachable objects\")),\n+\t\tOPT_BOOL(0, \"references\", &check_references, N_(\"check reference database consistency\")),\n+\t\tOPT_END(),\n+\t};\n \tstruct odb_source *source;\n \tstruct snapshot snap = {\n \t\t.nr = 0,\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552544","messageId":"20260911-pks-odb-source-fsck-v3-4-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","subject":"[PATCH v3 04/10] builtin/fsck: don't check alternates with \"--no-full\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:28Z","receivedAt":"2026-09-11T13:27:41Z","isPatch":true,"body":"According to git-fsck(1), the \"--full\" option behaves in the following\nway:\n\n  Check not just objects in GIT_OBJECT_DIRECTORY ($GIT_DIR/objects), but\n  also the ones found in alternate object pools listed in\n  GIT_ALTERNATE_OBJECT_DIRECTORIES or $GIT_DIR/objects/info/alternates,\n  and in packed Git archives found in $GIT_DIR/objects/pack and\n  corresponding pack subdirectories in alternate object pools.\n\nSo ultimately, it is supposed to control two things: (1) whether we only\ncheck the main object directory, and (2) whether we check packfiles.\n\nIn its current state though, the flag only controls whether we check\npackfiles or not, and if so we verify packfiles of all attached sources.\nBut we also have checks for loose objects in git-fsck(1), and here we\nunconditionally check them in all sources.\n\nThe flag is arguably conflating two unrelated concerns with one another,\nand it really should be split up into two flags: one that controls how\nthorough we want to check individual sources, and one that controls\nwhich sources we want to check in the first place. So ideally, we would\nhave:\n\n  - \"--include-alternates\": check all sources, not only the local one.\n\n  - \"--include-optimized-objects\": check not only loose objects, but\n    also those that have been packed. Note that we explicitly don't say\n    \"--include-packed-objects\" here to be more backend-agnostic.\n\n  - \"--full\": implies both of the above flags.\n\nThis feels out of scope for this series though. So for now, simply fix\nthe code by honoring locality of the sources for loose objects.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c  | 3 ++-\n t/t1450-fsck.sh | 5 +++++\n 2 files changed, 7 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 5132ff0f15..3f6056535f 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -1047,7 +1047,8 @@ int cmd_fsck(int argc,\n \t\t\t\t    mark_object_for_connectivity, repo, 0);\n \t} else {\n \t\tfor (source = repo->objects->sources; source; source = source->next)\n-\t\t\tfsck_source(repo, source);\n+\t\t\tif (check_full || source->local)\n+\t\t\t\tfsck_source(repo, source);\n \n \t\tif (check_full) {\n \t\t\tstruct packed_git *p;\ndiff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh\nindex 77cd96de78..1b4074304c 100755\n--- a/t/t1450-fsck.sh\n+++ b/t/t1450-fsck.sh\n@@ -844,6 +844,11 @@ test_expect_success 'alternate objects are correctly blamed' '\n \techo \"../../alt.git/objects\" >.git/objects/info/alternates &&\n \tmkdir alt.git/objects/$(dirname $path) &&\n \t>alt.git/objects/$(dirname $path)/$(basename $path) &&\n+\n+\t# Without \"--full\", only the local object source is checked.\n+\tgit fsck --no-full >out 2>&1 &&\n+\ttest_must_be_empty out &&\n+\n \ttest_must_fail git fsck >out 2>&1 &&\n \ttest_grep alt.git out\n '\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552545","messageId":"20260911-pks-odb-source-fsck-v3-5-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","subject":"[PATCH v3 05/10] odb: provide infrastructure for pluggable fsck checks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:29Z","receivedAt":"2026-09-11T13:27:44Z","isPatch":true,"body":"The on-disk consistency checks in git-fsck(1) are conceptually\nbackend-specific: while connectivity checks and object-level parsing\nchecks are generic, verifying the physical integrity of packfiles and\nloose objects is meaningful only to backends that use these formats:\nHaving these checks live in \"builtin/fsck.c\" violates that layering,\nbecause it forces the command to reach directly into format-specific\ninternals.\n\nProvide new infrastructure to make these format-specific checks\npluggable and implement stubs for the different source types we already\nhave. In subsequent commits we'll move functionality over piece by\npiece.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c        | 16 +++++++++++-----\n odb.c                 |  8 ++++++++\n odb.h                 | 23 +++++++++++++++++++++++\n odb/source-files.c    | 16 ++++++++++++++++\n odb/source-inmemory.c |  8 ++++++++\n odb/source-loose.c    |  7 +++++++\n odb/source-packed.c   |  8 ++++++++\n odb/source.h          | 21 +++++++++++++++++++++\n 8 files changed, 102 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 3f6056535f..adbe192e56 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -965,7 +965,9 @@ int cmd_fsck(int argc,\n \t     const char *prefix,\n \t     struct repository *repo)\n {\n-\tint check_full = 1;\n+\tstruct odb_fsck_options odb_fsck_opts = {\n+\t\t.flags = ODB_FSCK_FULL,\n+\t};\n \tint keep_cache_objects = 0;\n \tint name_objects = 0;\n \tint check_references = 1;\n@@ -977,7 +979,8 @@ int cmd_fsck(int argc,\n \t\tOPT_BOOL(0, \"root\", &show_root, N_(\"report root nodes\")),\n \t\tOPT_BOOL(0, \"cache\", &keep_cache_objects, N_(\"make index objects head nodes\")),\n \t\tOPT_BOOL(0, \"reflogs\", &include_reflogs, N_(\"make reflogs head nodes (default)\")),\n-\t\tOPT_BOOL(0, \"full\", &check_full, N_(\"also consider packs and alternate objects\")),\n+\t\tOPT_BIT(0, \"full\", &odb_fsck_opts.flags,\n+\t\t\tN_(\"also consider packs and alternate objects\"), ODB_FSCK_FULL),\n \t\tOPT_BOOL(0, \"connectivity-only\", &connectivity_only, N_(\"check only connectivity\")),\n \t\tOPT_BOOL(0, \"strict\", &check_strict, N_(\"enable more strict checking\")),\n \t\tOPT_BOOL(0, \"lost-found\", &write_lost_and_found,\n@@ -1018,7 +1021,7 @@ int cmd_fsck(int argc,\n \t\tshow_progress = 0;\n \n \tif (write_lost_and_found) {\n-\t\tcheck_full = 1;\n+\t\todb_fsck_opts.flags |= ODB_FSCK_FULL;\n \t\tinclude_reflogs = 0;\n \t}\n \n@@ -1047,10 +1050,13 @@ int cmd_fsck(int argc,\n \t\t\t\t    mark_object_for_connectivity, repo, 0);\n \t} else {\n \t\tfor (source = repo->objects->sources; source; source = source->next)\n-\t\t\tif (check_full || source->local)\n+\t\t\tif ((odb_fsck_opts.flags & ODB_FSCK_FULL) || source->local)\n \t\t\t\tfsck_source(repo, source);\n \n-\t\tif (check_full) {\n+\t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n+\t\t\terrors_found |= ERROR_OBJECT;\n+\n+\t\tif (odb_fsck_opts.flags & ODB_FSCK_FULL) {\n \t\t\tstruct packed_git *p;\n \t\t\tuint32_t total = 0, count = 0;\n \t\t\tstruct progress *progress = NULL;\ndiff --git a/odb.c b/odb.c\nindex 1fe20808eb..1c40da4cad 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -1177,3 +1177,11 @@ void odb_reprepare(struct object_database *o)\n {\n \todb_prepare(o, ODB_PREPARE_FLUSH_CACHES);\n }\n+\n+int odb_fsck(struct object_database *odb, struct odb_fsck_options *options)\n+{\n+\tint ret = 0;\n+\tfor (struct odb_source *source = odb->sources; source; source = source->next)\n+\t\tret |= odb_source_fsck(source, options);\n+\treturn ret;\n+}\ndiff --git a/odb.h b/odb.h\nindex e60174070f..76c15e48f5 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -206,6 +206,29 @@ void odb_prepare(struct object_database *o, enum odb_prepare_flags flags);\n /* Equivalent to `odb_prepare(o, ODB_PREPARE_FLUSH_CACHES)`. */\n void odb_reprepare(struct object_database *o);\n \n+enum odb_fsck_flags {\n+\t/*\n+\t * If set, perform a full consistency check for the full object\n+\t * database, including all of its sources and the contents of their\n+\t * optimized formats. Otherwise, only check the local source, and\n+\t * restrict checks of its optimized formats to cheap structural\n+\t * verification of their metadata.\n+\t */\n+\tODB_FSCK_FULL = (1 << 0),\n+};\n+\n+/* Options that shall be passed to `odb_fsck()`. */\n+struct odb_fsck_options {\n+\tenum odb_fsck_flags flags;\n+};\n+\n+/*\n+ * Run backend-specific integrity checks on all object sources. Each source\n+ * performs the checks appropriate to its type. Returns 0 on success, a\n+ * negative error code otherwise.\n+ */\n+int odb_fsck(struct object_database *odb, struct odb_fsck_options *opts);\n+\n /*\n  * Find source by its object directory path. Returns a `NULL` pointer in case\n  * the source could not be found.\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex bd4fdf3a6c..66a95e2b48 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -893,6 +893,21 @@ static int odb_source_files_generate_pack(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n+static int odb_source_files_fsck(struct odb_source *source,\n+\t\t\t\t struct odb_fsck_options *opts)\n+{\n+\tstruct odb_source_files *files = odb_source_files_downcast(source);\n+\tint ret = 0;\n+\n+\tif (!(opts->flags & ODB_FSCK_FULL) && !source->local)\n+\t\treturn 0;\n+\n+\tret |= odb_source_fsck(&files->loose->base, opts);\n+\tret |= odb_source_fsck(&files->packed->base, opts);\n+\n+\treturn ret;\n+}\n+\n struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \t\t\t\t\t      const char *path,\n \t\t\t\t\t      bool local)\n@@ -908,6 +923,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tfiles->base.close = odb_source_files_close;\n \tfiles->base.create_on_disk = odb_source_files_create_on_disk;\n \tfiles->base.prepare = odb_source_files_prepare;\n+\tfiles->base.fsck = odb_source_files_fsck;\n \tfiles->base.read_object_info = odb_source_files_read_object_info;\n \tfiles->base.read_object_stream = odb_source_files_read_object_stream;\n \tfiles->base.for_each_object = odb_source_files_for_each_object;\ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex 795672adf2..ba0f86da26 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"object-file.h\"\n #include \"odb.h\"\n+#include \"fsck.h\"\n #include \"odb/source-inmemory.h\"\n #include \"odb/streaming.h\"\n #include \"oidtree.h\"\n@@ -368,6 +369,12 @@ static void odb_source_inmemory_free(struct odb_source *source)\n \tfree(inmemory);\n }\n \n+static int odb_source_inmemory_fsck(struct odb_source *source UNUSED,\n+\t\t\t\t    struct odb_fsck_options *opts UNUSED)\n+{\n+\treturn 0;\n+}\n+\n struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n {\n \tstruct odb_source_inmemory *source;\n@@ -378,6 +385,7 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n \tsource->base.free = odb_source_inmemory_free;\n \tsource->base.close = odb_source_inmemory_close;\n \tsource->base.prepare = odb_source_inmemory_prepare;\n+\tsource->base.fsck = odb_source_inmemory_fsck;\n \tsource->base.read_object_info = odb_source_inmemory_read_object_info;\n \tsource->base.read_object_stream = odb_source_inmemory_read_object_stream;\n \tsource->base.for_each_object = odb_source_inmemory_for_each_object;\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex bb3455dfbd..f68d3c4d6c 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -1031,6 +1031,12 @@ static void odb_source_loose_free(struct odb_source *source)\n \tfree(loose);\n }\n \n+static int odb_source_loose_fsck(struct odb_source *source UNUSED,\n+\t\t\t\t struct odb_fsck_options *opts UNUSED)\n+{\n+\treturn 0;\n+}\n+\n struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \t\t\t\t\t      const char *path,\n \t\t\t\t\t      bool local)\n@@ -1043,6 +1049,7 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \tloose->base.free = odb_source_loose_free;\n \tloose->base.close = odb_source_loose_close;\n \tloose->base.prepare = odb_source_loose_prepare;\n+\tloose->base.fsck = odb_source_loose_fsck;\n \tloose->base.read_object_info = odb_source_loose_read_object_info;\n \tloose->base.read_object_stream = odb_source_loose_read_object_stream;\n \tloose->base.for_each_object = odb_source_loose_for_each_object;\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 630d955585..7aacf4bc45 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -2,6 +2,7 @@\n #include \"abspath.h\"\n #include \"chdir-notify.h\"\n #include \"dir.h\"\n+#include \"fsck.h\"\n #include \"git-zlib.h\"\n #include \"list-objects-filter-options.h\"\n #include \"mergesort.h\"\n@@ -826,6 +827,12 @@ static void odb_source_packed_free(struct odb_source *source)\n \tfree(packed);\n }\n \n+static int odb_source_packed_fsck(struct odb_source *source UNUSED,\n+\t\t\t\t  struct odb_fsck_options *opts UNUSED)\n+{\n+\treturn 0;\n+}\n+\n struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \t\t\t\t\t\tconst char *path,\n \t\t\t\t\t\tbool local)\n@@ -839,6 +846,7 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \tpacked->base.free = odb_source_packed_free;\n \tpacked->base.close = odb_source_packed_close;\n \tpacked->base.prepare = odb_source_packed_prepare;\n+\tpacked->base.fsck = odb_source_packed_fsck;\n \tpacked->base.read_object_info = odb_source_packed_read_object_info;\n \tpacked->base.read_object_stream = odb_source_packed_read_object_stream;\n \tpacked->base.for_each_object = odb_source_packed_for_each_object;\ndiff --git a/odb/source.h b/odb/source.h\nindex 559e2ea2e9..10a5dd5194 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -320,6 +320,17 @@ struct odb_source {\n \tint (*generate_pack)(struct odb_source *source,\n \t\t\t     struct odb_pack_generator **out,\n \t\t\t     const struct odb_generate_pack_options *opts);\n+\n+\t/*\n+\t * This callback is expected to check the integrity of the object source\n+\t * and report any errors found via the fsck options. The checks performed\n+\t * are backend-specific.\n+\t *\n+\t * The callback is expected to return 0 on success, a negative error\n+\t * code otherwise.\n+\t */\n+\tint (*fsck)(struct odb_source *source,\n+\t\t    struct odb_fsck_options *options);\n };\n \n /*\n@@ -588,4 +599,14 @@ static inline int odb_source_generate_pack(struct odb_source *source,\n \treturn source->generate_pack(source, out, opts);\n }\n \n+/*\n+ * Check the integrity of the object database source. The checks performed\n+ * are backend-specific. Returns 0 on success, a negative error code otherwise.\n+ */\n+static inline int odb_source_fsck(struct odb_source *source,\n+\t\t\t\t  struct odb_fsck_options *opts)\n+{\n+\treturn source->fsck(source, opts);\n+}\n+\n #endif\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552546","messageId":"20260911-pks-odb-source-fsck-v3-6-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","subject":"[PATCH v3 06/10] builtin/fsck: move packfile verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:30Z","receivedAt":"2026-09-11T13:27:46Z","isPatch":true,"body":"Move the packfile verification out of `cmd_fsck()` and into the \"packed\"\nsource. While doing so, thread the progress meter and object callback\nthrough the newly introduced `struct odb_fsck_options` so that the\ncaller's preferences are honoured without exposing those details at the\n\"builtin/fsck.c\" level.\n\nNote that the old code reported failures when verifying packfiles with\nthe `ERROR_PACK` bit, which gets returned to the caller via the exit\ncode. This bit is neither exercised in our test suite nor is it\ndocumented anywhere in our codebase. Furthermore, this bit is highly\nspecific to the object storage backend, which makes it a bad fit for the\nnew pluggable infrastructure. So instead of retaining these semantics,\nwe drop them and return the generic `ERROR_OBJECT` bit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c      | 33 ++++-----------------------------\n odb.h               |  7 +++++++\n odb/source-packed.c | 46 +++++++++++++++++++++++++++++++++++++++++++---\n 3 files changed, 54 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex adbe192e56..e504dae904 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -7,7 +7,6 @@\n #include \"blob.h\"\n #include \"tag.h\"\n #include \"refs.h\"\n-#include \"pack.h\"\n #include \"cache-tree.h\"\n #include \"fsck.h\"\n #include \"parse-options.h\"\n@@ -49,7 +48,6 @@ static int show_dangling = 1;\n static timestamp_t now;\n #define ERROR_OBJECT 01\n #define ERROR_REACHABLE 02\n-#define ERROR_PACK 04\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n #define ERROR_MULTI_PACK_INDEX 040\n@@ -967,6 +965,8 @@ int cmd_fsck(int argc,\n {\n \tstruct odb_fsck_options odb_fsck_opts = {\n \t\t.flags = ODB_FSCK_FULL,\n+\t\t.object_cb = fsck_obj_buffer,\n+\t\t.object_payload = repo,\n \t};\n \tint keep_cache_objects = 0;\n \tint name_objects = 0;\n@@ -1019,6 +1019,8 @@ int cmd_fsck(int argc,\n \t\tshow_progress = isatty(2);\n \tif (verbose)\n \t\tshow_progress = 0;\n+\tif (show_progress)\n+\t\todb_fsck_opts.flags |= ODB_FSCK_PROGRESS;\n \n \tif (write_lost_and_found) {\n \t\todb_fsck_opts.flags |= ODB_FSCK_FULL;\n@@ -1056,33 +1058,6 @@ int cmd_fsck(int argc,\n \t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n \t\t\terrors_found |= ERROR_OBJECT;\n \n-\t\tif (odb_fsck_opts.flags & ODB_FSCK_FULL) {\n-\t\t\tstruct packed_git *p;\n-\t\t\tuint32_t total = 0, count = 0;\n-\t\t\tstruct progress *progress = NULL;\n-\n-\t\t\tif (show_progress) {\n-\t\t\t\trepo_for_each_pack(repo, p) {\n-\t\t\t\t\tif (open_pack_index(p))\n-\t\t\t\t\t\tcontinue;\n-\t\t\t\t\ttotal += p->num_objects;\n-\t\t\t\t}\n-\n-\t\t\t\tprogress = start_progress(repo,\n-\t\t\t\t\t\t\t  _(\"Checking objects\"), total);\n-\t\t\t}\n-\n-\t\t\trepo_for_each_pack(repo, p) {\n-\t\t\t\t/* verify gives error messages itself */\n-\t\t\t\tif (verify_pack(repo,\n-\t\t\t\t\t\tp, fsck_obj_buffer, repo,\n-\t\t\t\t\t\tprogress, count))\n-\t\t\t\t\terrors_found |= ERROR_PACK;\n-\t\t\t\tcount += p->num_objects;\n-\t\t\t}\n-\t\t\tstop_progress(&progress);\n-\t\t}\n-\n \t\tif (fsck_finish(&fsck_obj_options))\n \t\t\terrors_found |= ERROR_OBJECT;\n \t}\ndiff --git a/odb.h b/odb.h\nindex 76c15e48f5..0bf6c8d7d2 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -215,11 +215,18 @@ enum odb_fsck_flags {\n \t * verification of their metadata.\n \t */\n \tODB_FSCK_FULL = (1 << 0),\n+\n+\t/* Display a progress meter, if sensible. */\n+\tODB_FSCK_PROGRESS = (1 << 1),\n };\n \n /* Options that shall be passed to `odb_fsck()`. */\n struct odb_fsck_options {\n \tenum odb_fsck_flags flags;\n+\n+\tint (*object_cb)(const struct object_id *oid, enum object_type type,\n+\t\t\t unsigned long size, void *buffer, int *eaten, void *cb_data);\n+\tvoid *object_payload;\n };\n \n /*\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 7aacf4bc45..0d3599f8fe 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -9,8 +9,10 @@\n #include \"midx.h\"\n #include \"odb/source-packed.h\"\n #include \"odb/streaming.h\"\n+#include \"pack.h\"\n #include \"packfile.h\"\n #include \"pack-bitmap.h\"\n+#include \"progress.h\"\n \n static int find_pack_entry(struct odb_source_packed *store,\n \t\t\t   const struct object_id *oid,\n@@ -827,10 +829,48 @@ static void odb_source_packed_free(struct odb_source *source)\n \tfree(packed);\n }\n \n-static int odb_source_packed_fsck(struct odb_source *source UNUSED,\n-\t\t\t\t  struct odb_fsck_options *opts UNUSED)\n+static int verify_packs(struct odb_source_packed *source,\n+\t\t\tstruct odb_fsck_options *opts)\n {\n-\treturn 0;\n+\tstruct progress *progress = NULL;\n+\tstruct packfile_list_entry *e;\n+\tuint32_t total = 0, count = 0;\n+\tint ret = 0;\n+\n+\tif (opts->flags & ODB_FSCK_PROGRESS) {\n+\t\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\t\tif (open_pack_index(e->pack))\n+\t\t\t\tcontinue;\n+\t\t\ttotal += e->pack->num_objects;\n+\t\t}\n+\n+\t\tprogress = start_progress(source->base.odb->repo,\n+\t\t\t\t\t  _(\"Checking objects\"), total);\n+\t}\n+\n+\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\t/* verify gives error messages itself */\n+\t\tif (verify_pack(source->base.odb->repo, e->pack,\n+\t\t\t\topts->object_cb, opts->object_payload,\n+\t\t\t\tprogress, count))\n+\t\t\tret = -1;\n+\t\tcount += e->pack->num_objects;\n+\t}\n+\tstop_progress(&progress);\n+\n+\treturn ret;\n+}\n+\n+static int odb_source_packed_fsck(struct odb_source *source,\n+\t\t\t\t  struct odb_fsck_options *opts)\n+{\n+\tstruct odb_source_packed *packed = odb_source_packed_downcast(source);\n+\tint ret = 0;\n+\n+\tif ((opts->flags & ODB_FSCK_FULL) && verify_packs(packed, opts) < 0)\n+\t\tret = -1;\n+\n+\treturn ret;\n }\n \n struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552547","messageId":"20260911-pks-odb-source-fsck-v3-7-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","subject":"[PATCH v3 07/10] builtin/fsck: move reverse index verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:31Z","receivedAt":"2026-09-11T13:27:48Z","isPatch":true,"body":"The checks for reverse indexes live in `check_pack_rev_indexes()`, which\nis hosted in \"builtin/fsck.c\". These checks are obviously specific to\nthe \"packed\" backend.\n\nMove the logic into `odb_source_packed_fsck()`. As in the preceding\ncommit, drop the dedicated `ERROR_PACK_REV_INDEX` bit and instead use\nthe generic `ERROR_OBJECT` bit.\n\nNote that this changes behaviour in two ways:\n\n  - The checks are now skipped when \"--connectivity-only\" was passed.\n    This is because we don't even run `odb_fsck()` at all when that\n    flag has been passed by the user, and not verifying data structures\n    of the object database matches the documented intent of that flag,\n    which is to only check the connectivity of reachable objects.\n\n  - The checks are now skipped for non-local sources when \"--no-full\"\n    was passed. This is, again, in line with the documented intent of\n    that flag.\n\nAdd a test to cast these semantics into stone.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c           | 37 -------------------------------------\n odb/source-packed.c      | 39 +++++++++++++++++++++++++++++++++++++++\n t/t5325-reverse-index.sh |  8 ++++++++\n 3 files changed, 47 insertions(+), 37 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex e504dae904..06e72877f3 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -23,7 +23,6 @@\n #include \"run-command.h\"\n #include \"sparse-index.h\"\n #include \"worktree.h\"\n-#include \"pack-revindex.h\"\n #include \"pack-bitmap.h\"\n \n #define REACHABLE 0x0001\n@@ -51,7 +50,6 @@ static timestamp_t now;\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n #define ERROR_MULTI_PACK_INDEX 040\n-#define ERROR_PACK_REV_INDEX 0100\n #define ERROR_BITMAP 0200\n \n static const char *describe_object(const struct object_id *oid)\n@@ -890,40 +888,6 @@ static int mark_object_for_connectivity(const struct object_id *oid,\n \treturn 0;\n }\n \n-static int check_pack_rev_indexes(struct repository *r, int show_progress)\n-{\n-\tstruct progress *progress = NULL;\n-\tstruct packed_git *p;\n-\tuint32_t pack_count = 0;\n-\tint res = 0;\n-\n-\tif (show_progress) {\n-\t\trepo_for_each_pack(r, p)\n-\t\t\tpack_count++;\n-\t\tprogress = start_delayed_progress(r,\n-\t\t\t\t\t\t  \"Verifying reverse pack-indexes\", pack_count);\n-\t\tpack_count = 0;\n-\t}\n-\n-\trepo_for_each_pack(r, p) {\n-\t\tint load_error = load_pack_revindex_from_disk(p);\n-\n-\t\tif (load_error < 0) {\n-\t\t\terror(_(\"unable to load rev-index for pack '%s'\"), p->pack_name);\n-\t\t\tres = ERROR_PACK_REV_INDEX;\n-\t\t} else if (!load_error &&\n-\t\t\t   !load_pack_revindex(r, p) &&\n-\t\t\t   verify_pack_revindex(p)) {\n-\t\t\terror(_(\"invalid rev-index for pack '%s'\"), p->pack_name);\n-\t\t\tres = ERROR_PACK_REV_INDEX;\n-\t\t}\n-\t\tdisplay_progress(progress, ++pack_count);\n-\t}\n-\tstop_progress(&progress);\n-\n-\treturn res;\n-}\n-\n static void fsck_refs(struct repository *r)\n {\n \tstruct child_process refs_verify = CHILD_PROCESS_INIT;\n@@ -1104,7 +1068,6 @@ int cmd_fsck(int argc,\n \t\tfree_worktrees(worktrees);\n \t}\n \n-\terrors_found |= check_pack_rev_indexes(repo, show_progress);\n \tif (verify_bitmap_files(repo))\n \t\terrors_found |= ERROR_BITMAP;\n \ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 0d3599f8fe..e5e69636dd 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -10,6 +10,7 @@\n #include \"odb/source-packed.h\"\n #include \"odb/streaming.h\"\n #include \"pack.h\"\n+#include \"pack-revindex.h\"\n #include \"packfile.h\"\n #include \"pack-bitmap.h\"\n #include \"progress.h\"\n@@ -861,6 +862,41 @@ static int verify_packs(struct odb_source_packed *source,\n \treturn ret;\n }\n \n+static int verify_reverse_indices(struct odb_source_packed *source,\n+\t\t\t\t  struct odb_fsck_options *opts)\n+{\n+\tstruct progress *progress = NULL;\n+\tstruct packfile_list_entry *e;\n+\tuint32_t pack_count = 0;\n+\tint res = 0;\n+\n+\tif (opts->flags & ODB_FSCK_PROGRESS) {\n+\t\tfor (e = packfile_store_get_packs(source); e; e = e->next)\n+\t\t\tpack_count++;\n+\t\tprogress = start_delayed_progress(source->base.odb->repo,\n+\t\t\t\t\t\t  \"Verifying reverse pack-indexes\", pack_count);\n+\t\tpack_count = 0;\n+\t}\n+\n+\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\tint load_error = load_pack_revindex_from_disk(e->pack);\n+\n+\t\tif (load_error < 0) {\n+\t\t\terror(_(\"unable to load rev-index for pack '%s'\"), e->pack->pack_name);\n+\t\t\tres = -1;\n+\t\t} else if (!load_error &&\n+\t\t\t   !load_pack_revindex(source->base.odb->repo, e->pack) &&\n+\t\t\t   verify_pack_revindex(e->pack)) {\n+\t\t\terror(_(\"invalid rev-index for pack '%s'\"), e->pack->pack_name);\n+\t\t\tres = -1;\n+\t\t}\n+\t\tdisplay_progress(progress, ++pack_count);\n+\t}\n+\tstop_progress(&progress);\n+\n+\treturn res;\n+}\n+\n static int odb_source_packed_fsck(struct odb_source *source,\n \t\t\t\t  struct odb_fsck_options *opts)\n {\n@@ -870,6 +906,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n \tif ((opts->flags & ODB_FSCK_FULL) && verify_packs(packed, opts) < 0)\n \t\tret = -1;\n \n+\tif (verify_reverse_indices(packed, opts) < 0)\n+\t\tret = -1;\n+\n \treturn ret;\n }\n \ndiff --git a/t/t5325-reverse-index.sh b/t/t5325-reverse-index.sh\nindex 5493791938..6b81abf663 100755\n--- a/t/t5325-reverse-index.sh\n+++ b/t/t5325-reverse-index.sh\n@@ -204,4 +204,12 @@ test_expect_success 'fsck catches invalid header: hash function' '\n \t\t\"reverse-index file .* has unsupported hash id\"\n '\n \n+test_expect_success 'fsck --no-full checks rev-index, --connectivity-only does not' '\n+\ttest_must_fail git -C corrupt fsck --no-full 2>err &&\n+\ttest_grep \"has unsupported hash id\" err &&\n+\n+\tgit -C corrupt fsck --connectivity-only 2>err &&\n+\ttest_grep ! \"has unsupported hash id\" err\n+'\n+\n test_done\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552548","messageId":"20260911-pks-odb-source-fsck-v3-8-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","subject":"[PATCH v3 08/10] builtin/fsck: move bitmap verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:32Z","receivedAt":"2026-09-11T13:27:51Z","isPatch":true,"body":"The checks for bitmaps live in `verify_bitmap_files()`, which is called\nby \"builtin/fsck.c\". These checks are obviously specific to the \"packed\"\nbackend.\n\nMove the logic into `odb_source_packed_fsck()`. As in preceding commits,\nthis means that we now properly honor both \"--connectivity-only\" and\n\"--no-full\". Furthermore, we drop the dedicated `ERROR_BITMAP` bit and\ninstead use the generic `ERROR_OBJECT` bit.\n\nNote that this change also adapts `verify_bitmap_files()` to be\nfocused on a single \"packed\" source instead of verifying bitmaps from\nall sources. This change is required as we already know to loop around\nthe sources in `odb_fsck()` itself.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c                |  5 -----\n odb/source-packed.c           |  3 +++\n pack-bitmap.c                 | 26 ++++++++++----------------\n pack-bitmap.h                 |  2 +-\n t/t5326-multi-pack-bitmaps.sh | 10 +++++++++-\n 5 files changed, 23 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 06e72877f3..2f7d29aa56 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -23,7 +23,6 @@\n #include \"run-command.h\"\n #include \"sparse-index.h\"\n #include \"worktree.h\"\n-#include \"pack-bitmap.h\"\n \n #define REACHABLE 0x0001\n #define SEEN      0x0002\n@@ -50,7 +49,6 @@ static timestamp_t now;\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n #define ERROR_MULTI_PACK_INDEX 040\n-#define ERROR_BITMAP 0200\n \n static const char *describe_object(const struct object_id *oid)\n {\n@@ -1068,9 +1066,6 @@ int cmd_fsck(int argc,\n \t\tfree_worktrees(worktrees);\n \t}\n \n-\tif (verify_bitmap_files(repo))\n-\t\terrors_found |= ERROR_BITMAP;\n-\n \tcheck_connectivity(repo);\n \n \tif (repo->settings.core_commit_graph) {\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex e5e69636dd..2b5dc502f5 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -909,6 +909,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n \tif (verify_reverse_indices(packed, opts) < 0)\n \t\tret = -1;\n \n+\tif (verify_bitmap_files(packed))\n+\t\tret = -1;\n+\n \treturn ret;\n }\n \ndiff --git a/pack-bitmap.c b/pack-bitmap.c\nindex e0fb57d332..3de8e9590c 100644\n--- a/pack-bitmap.c\n+++ b/pack-bitmap.c\n@@ -3410,28 +3410,22 @@ static int verify_bitmap_file(const struct git_hash_algo *algop,\n \treturn res;\n }\n \n-int verify_bitmap_files(struct repository *r)\n+int verify_bitmap_files(struct odb_source_packed *source)\n {\n-\tstruct odb_source *source;\n-\tstruct packed_git *p;\n+\tstruct packfile_list_entry *e;\n+\tstruct multi_pack_index *m;\n \tint res = 0;\n \n-\tfor (source = r->objects->sources; source; source = source->next) {\n-\t\tstruct odb_source_files *files = odb_source_files_downcast(source);\n-\t\tstruct multi_pack_index *m = get_multi_pack_index(files->packed);\n-\t\tchar *midx_bitmap_name;\n-\n-\t\tif (!m)\n-\t\t\tcontinue;\n-\n-\t\tmidx_bitmap_name = midx_bitmap_filename(m);\n-\t\tres |= verify_bitmap_file(r->hash_algo, midx_bitmap_name);\n+\tm = get_multi_pack_index(source);\n+\tif (m) {\n+\t\tchar *midx_bitmap_name = midx_bitmap_filename(m);\n+\t\tres |= verify_bitmap_file(source->base.odb->repo->hash_algo, midx_bitmap_name);\n \t\tfree(midx_bitmap_name);\n \t}\n \n-\trepo_for_each_pack(r, p) {\n-\t\tchar *pack_bitmap_name = pack_bitmap_filename(p);\n-\t\tres |= verify_bitmap_file(r->hash_algo, pack_bitmap_name);\n+\tfor (e = packfile_store_get_packs(source); e; e = e->next) {\n+\t\tchar *pack_bitmap_name = pack_bitmap_filename(e->pack);\n+\t\tres |= verify_bitmap_file(source->base.odb->repo->hash_algo, pack_bitmap_name);\n \t\tfree(pack_bitmap_name);\n \t}\n \ndiff --git a/pack-bitmap.h b/pack-bitmap.h\nindex 1385027c1f..847ad4762d 100644\n--- a/pack-bitmap.h\n+++ b/pack-bitmap.h\n@@ -205,7 +205,7 @@ int bitmap_is_midx(struct bitmap_index *bitmap_git);\n \n int bitmap_is_preferred_refname(struct repository *r, const char *refname);\n \n-int verify_bitmap_files(struct repository *r);\n+int verify_bitmap_files(struct odb_source_packed *source);\n \n struct ewah_bitmap *read_bitmap(const unsigned char *map,\n \t\t\t\tsize_t map_size, size_t *map_pos);\ndiff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh\nindex 86beab1dae..8047459b00 100755\n--- a/t/t5326-multi-pack-bitmaps.sh\n+++ b/t/t5326-multi-pack-bitmaps.sh\n@@ -498,7 +498,15 @@ test_expect_success 'git fsck correctly identifies good and bad bitmaps' '\n \tcorrupt_file \"$packbitmap\" &&\n \ttest_must_fail git fsck 2>err &&\n \ttest_grep \"bitmap file '\\''$midxbitmap'\\'' has invalid checksum\" err &&\n-\ttest_grep \"bitmap file '\\''$packbitmap'\\'' has invalid checksum\" err\n+\ttest_grep \"bitmap file '\\''$packbitmap'\\'' has invalid checksum\" err &&\n+\n+\t# The bitmap checks are performed with \"--no-full\", but not with\n+\t# \"--connectivity-only\".\n+\ttest_must_fail git fsck --no-full 2>err &&\n+\ttest_grep \"bitmap file '\\''$midxbitmap'\\'' has invalid checksum\" err &&\n+\ttest_grep \"bitmap file '\\''$packbitmap'\\'' has invalid checksum\" err &&\n+\tgit fsck --connectivity-only 2>err &&\n+\ttest_grep ! \"invalid checksum\" err\n '\n \n test_expect_success 'corrupt MIDX with bitmap causes fallback' '\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552549","messageId":"20260911-pks-odb-source-fsck-v3-9-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","subject":"[PATCH v3 09/10] builtin/fsck: move multi-pack index verification into the packed source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:33Z","receivedAt":"2026-09-11T13:27:52Z","isPatch":true,"body":"The checks for multi-pack indexes are hosted in `cmd_fsck()` directly.\nThese checks are obviously specific to the \"packed\" backend.\n\nMove the logic into `odb_source_packed_fsck()`. As in preceding commits,\nthis means that we now properly honor both \"--connectivity-only\" and\n\"--no-full\". Furthermore, we drop the dedicated `ERROR_MULTI_PACK_INDEX`\nbit and instead use the generic `ERROR_OBJECT` bit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c              | 18 ------------------\n odb/source-packed.c         | 27 +++++++++++++++++++++++++++\n t/t5319-multi-pack-index.sh | 13 +++++++++++++\n 3 files changed, 40 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 2f7d29aa56..7eaea340b0 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -48,7 +48,6 @@ static timestamp_t now;\n #define ERROR_REACHABLE 02\n #define ERROR_REFS 010\n #define ERROR_COMMIT_GRAPH 020\n-#define ERROR_MULTI_PACK_INDEX 040\n \n static const char *describe_object(const struct object_id *oid)\n {\n@@ -1085,23 +1084,6 @@ int cmd_fsck(int argc,\n \t\t}\n \t}\n \n-\tif (repo->settings.core_multi_pack_index) {\n-\t\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n-\n-\t\tfor (source = repo->objects->sources; source; source = source->next) {\n-\t\t\tchild_process_init(&midx_verify);\n-\t\t\tmidx_verify.git_cmd = 1;\n-\t\t\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n-\t\t\t\t     \"verify\", \"--object-dir\", source->path, NULL);\n-\t\t\tif (show_progress)\n-\t\t\t\tstrvec_push(&midx_verify.args, \"--progress\");\n-\t\t\telse\n-\t\t\t\tstrvec_push(&midx_verify.args, \"--no-progress\");\n-\t\t\tif (run_command(&midx_verify))\n-\t\t\t\terrors_found |= ERROR_MULTI_PACK_INDEX;\n-\t\t}\n-\t}\n-\n \tfree_snapshot_refs(&snap);\n \treturn errors_found;\n }\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 2b5dc502f5..9f54a5e83a 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -14,6 +14,7 @@\n #include \"packfile.h\"\n #include \"pack-bitmap.h\"\n #include \"progress.h\"\n+#include \"run-command.h\"\n \n static int find_pack_entry(struct odb_source_packed *store,\n \t\t\t   const struct object_id *oid,\n@@ -897,6 +898,29 @@ static int verify_reverse_indices(struct odb_source_packed *source,\n \treturn res;\n }\n \n+static int verify_midx(struct odb_source_packed *source,\n+\t\t       struct odb_fsck_options *opts)\n+{\n+\tstruct child_process midx_verify = CHILD_PROCESS_INIT;\n+\n+\tprepare_repo_settings(source->base.odb->repo);\n+\tif (!source->base.odb->repo->settings.core_multi_pack_index)\n+\t\treturn 0;\n+\n+\tchild_process_init(&midx_verify);\n+\tmidx_verify.git_cmd = 1;\n+\tstrvec_pushl(&midx_verify.args, \"multi-pack-index\",\n+\t\t     \"verify\", \"--object-dir\", source->base.path, NULL);\n+\tif (opts->flags & ODB_FSCK_PROGRESS)\n+\t\tstrvec_push(&midx_verify.args, \"--progress\");\n+\telse\n+\t\tstrvec_push(&midx_verify.args, \"--no-progress\");\n+\tif (run_command(&midx_verify))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n static int odb_source_packed_fsck(struct odb_source *source,\n \t\t\t\t  struct odb_fsck_options *opts)\n {\n@@ -912,6 +936,9 @@ static int odb_source_packed_fsck(struct odb_source *source,\n \tif (verify_bitmap_files(packed))\n \t\tret = -1;\n \n+\tif (verify_midx(packed, opts) < 0)\n+\t\tret = -1;\n+\n \treturn ret;\n }\n \ndiff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh\nindex 68143cb5b7..20b010c33b 100755\n--- a/t/t5319-multi-pack-index.sh\n+++ b/t/t5319-multi-pack-index.sh\n@@ -573,6 +573,19 @@ test_expect_success 'verify incorrect checksum' '\n \t\t$objdir \"incorrect checksum\"\n '\n \n+test_expect_success 'git fsck --no-full checks multi-pack-index, --connectivity-only does not' '\n+\tpos=$(($(wc -c <$objdir/pack/multi-pack-index) - 10)) &&\n+\tcorrupt_midx_and_verify $pos \\\n+\t\t\"\\377\\377\\377\\377\\377\\377\\377\\377\\377\\377\" \\\n+\t\t$objdir \"incorrect checksum\" &&\n+\n+\ttest_must_fail git fsck --no-full 2>err &&\n+\ttest_grep \"incorrect checksum\" err &&\n+\n+\tgit fsck --connectivity-only 2>err &&\n+\ttest_grep ! \"incorrect checksum\" err\n+'\n+\n test_expect_success 'setup for v1-specific fsck tests' '\n \tgit -c midx.version=1 multi-pack-index write\n '\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552550","messageId":"20260911-pks-odb-source-fsck-v3-10-ef2fdc085e38@pks.im","threadId":"66217","inReplyTo":"20260911-pks-odb-source-fsck-v3-0-ef2fdc085e38@pks.im","subject":"[PATCH v3 10/10] builtin/fsck: move loose object verification into the loose source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T13:27:34Z","receivedAt":"2026-09-11T13:27:55Z","isPatch":true,"body":"The consistency checks for loose objects are hosted by \"builtin/fsck.c\".\nThese checks are obviously specific to the \"loose\" backend.\n\nMove the logic into `odb_source_loose_fsck()`. Introduce a new \"verbose\"\nflag so that we can properly retain semantics around whether or not we\nwant to print some status messages.\n\nNote that this fixes a bug as a side effect: the progress meter was\ncaptured in the callback data before `start_progress()` was even called,\nso the per-subdirectory progress updates always operated on a NULL\npointer and the meter jumped straight from 0 to 256 upon completion. The\nnew code only sets up the callback data's progress meter after it has\nbeen created, so the progress display now advances incrementally again.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c     | 91 ++----------------------------------------------------\n odb.h              |  3 ++\n odb/source-loose.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++--\n 3 files changed, 93 insertions(+), 90 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 7eaea340b0..4af1d874cc 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -12,7 +12,6 @@\n #include \"parse-options.h\"\n #include \"progress.h\"\n #include \"packfile.h\"\n-#include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n #include \"odb/streaming.h\"\n@@ -695,88 +694,6 @@ static void process_refs(struct repository *repo, struct snapshot *snap)\n \t}\n }\n \n-struct for_each_loose_cb {\n-\tstruct repository *repo;\n-\tstruct progress *progress;\n-};\n-\n-static int fsck_loose(const struct object_id *oid, const char *path,\n-\t\t      void *cb_data)\n-{\n-\tstruct for_each_loose_cb *data = cb_data;\n-\tenum object_type type = OBJ_NONE;\n-\tsize_t size;\n-\tvoid *contents = NULL;\n-\tint eaten;\n-\tstruct object_info oi = OBJECT_INFO_INIT;\n-\tstruct object_id real_oid = *null_oid(data->repo->hash_algo);\n-\tint err = 0;\n-\n-\toi.sizep = &size;\n-\toi.typep = &type;\n-\n-\tif (read_loose_object(data->repo, path, oid, &real_oid, &contents, &oi) < 0) {\n-\t\tif (contents && !oideq(&real_oid, oid))\n-\t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n-\t\t\t\t    oid_to_hex(&real_oid), path);\n-\t\telse\n-\t\t\terr = error(_(\"%s: object corrupt or missing: %s\"),\n-\t\t\t\t    oid_to_hex(oid), path);\n-\t}\n-\tif (err < 0) {\n-\t\terrors_found |= ERROR_OBJECT;\n-\t\tfree(contents);\n-\t\treturn 0; /* keep checking other objects */\n-\t}\n-\n-\tif (!contents && type != OBJ_BLOB)\n-\t\tBUG(\"read_loose_object streamed a non-blob\");\n-\n-\tif (fsck_obj_buffer(oid, type, size, contents, &eaten, data->repo))\n-\t\terrors_found |= ERROR_OBJECT;\n-\n-\tif (!eaten)\n-\t\tfree(contents);\n-\treturn 0; /* keep checking other objects, even if we saw an error */\n-}\n-\n-static int fsck_cruft(const char *basename, const char *path,\n-\t\t      void *data UNUSED)\n-{\n-\tif (!starts_with(basename, \"tmp_obj_\"))\n-\t\tfprintf_ln(stderr, _(\"bad sha1 file: %s\"), path);\n-\treturn 0;\n-}\n-\n-static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *data)\n-{\n-\tstruct for_each_loose_cb *cb_data = data;\n-\tstruct progress *progress = cb_data->progress;\n-\tdisplay_progress(progress, nr + 1);\n-\treturn 0;\n-}\n-\n-static void fsck_source(struct repository *repo, struct odb_source *source)\n-{\n-\tstruct progress *progress = NULL;\n-\tstruct for_each_loose_cb cb_data = {\n-\t\t.repo = source->odb->repo,\n-\t\t.progress = progress,\n-\t};\n-\n-\tif (verbose)\n-\t\tfprintf_ln(stderr, _(\"Checking object directory\"));\n-\n-\tif (show_progress)\n-\t\tprogress = start_progress(repo,\n-\t\t\t\t\t  _(\"Checking object directories\"), 256);\n-\n-\tfor_each_loose_file_in_source(source, fsck_loose,\n-\t\t\t\t      fsck_cruft, fsck_subdir, &cb_data);\n-\tdisplay_progress(progress, 256);\n-\tstop_progress(&progress);\n-}\n-\n static int fsck_cache_tree(struct repository *repo, struct cache_tree *it, const char *index_path)\n {\n \tint i;\n@@ -978,8 +895,10 @@ int cmd_fsck(int argc,\n \n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n-\tif (verbose)\n+\tif (verbose) {\n \t\tshow_progress = 0;\n+\t\todb_fsck_opts.flags |= ODB_FSCK_VERBOSE;\n+\t}\n \tif (show_progress)\n \t\todb_fsck_opts.flags |= ODB_FSCK_PROGRESS;\n \n@@ -1012,10 +931,6 @@ int cmd_fsck(int argc,\n \t\todb_for_each_object(repo->objects, NULL,\n \t\t\t\t    mark_object_for_connectivity, repo, 0);\n \t} else {\n-\t\tfor (source = repo->objects->sources; source; source = source->next)\n-\t\t\tif ((odb_fsck_opts.flags & ODB_FSCK_FULL) || source->local)\n-\t\t\t\tfsck_source(repo, source);\n-\n \t\tif (odb_fsck(repo->objects, &odb_fsck_opts) < 0)\n \t\t\terrors_found |= ERROR_OBJECT;\n \ndiff --git a/odb.h b/odb.h\nindex 0bf6c8d7d2..b87f281cbd 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -218,6 +218,9 @@ enum odb_fsck_flags {\n \n \t/* Display a progress meter, if sensible. */\n \tODB_FSCK_PROGRESS = (1 << 1),\n+\n+\t/* Be extra verbose when checking the database. */\n+\tODB_FSCK_VERBOSE = (1 << 2),\n };\n \n /* Options that shall be passed to `odb_fsck()`. */\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex f68d3c4d6c..efef9ca61f 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -12,6 +12,7 @@\n #include \"odb/streaming.h\"\n #include \"oidtree.h\"\n #include \"path.h\"\n+#include \"progress.h\"\n #include \"repository.h\"\n #include \"strbuf.h\"\n #include \"tempfile.h\"\n@@ -1031,12 +1032,96 @@ static void odb_source_loose_free(struct odb_source *source)\n \tfree(loose);\n }\n \n-static int odb_source_loose_fsck(struct odb_source *source UNUSED,\n-\t\t\t\t struct odb_fsck_options *opts UNUSED)\n+struct fsck_loose_data {\n+\tstruct odb_source_loose *source;\n+\tstruct odb_fsck_options *opts;\n+\tstruct progress *progress;\n+\tbool error_found;\n+};\n+\n+static int fsck_loose(const struct object_id *oid, const char *path,\n+\t\t      void *cb_data)\n {\n+\tstruct fsck_loose_data *data = cb_data;\n+\tenum object_type type = OBJ_NONE;\n+\tsize_t size;\n+\tvoid *contents = NULL;\n+\tint eaten = 0;\n+\tstruct object_info oi = OBJECT_INFO_INIT;\n+\tstruct object_id real_oid = *null_oid(data->source->base.odb->repo->hash_algo);\n+\tint err = 0;\n+\n+\toi.sizep = &size;\n+\toi.typep = &type;\n+\n+\tif (read_loose_object(data->source->base.odb->repo,\n+\t\t\t      path, oid, &real_oid, &contents, &oi) < 0) {\n+\t\tif (contents && !oideq(&real_oid, oid))\n+\t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n+\t\t\t\t    oid_to_hex(&real_oid), path);\n+\t\telse\n+\t\t\terr = error(_(\"%s: object corrupt or missing: %s\"),\n+\t\t\t\t    oid_to_hex(oid), path);\n+\t}\n+\tif (err < 0)\n+\t\tgoto out;\n+\n+\tif (!contents && type != OBJ_BLOB)\n+\t\tBUG(\"read_loose_object streamed a non-blob\");\n+\n+\tif (data->opts->object_cb(oid, type, size, contents, &eaten,\n+\t\t\t\t  data->opts->object_payload)) {\n+\t\terr = -1;\n+\t\tgoto out;\n+\t}\n+\n+out:\n+\tif (err)\n+\t\tdata->error_found = true;\n+\tif (!eaten)\n+\t\tfree(contents);\n+\treturn 0; /* keep checking other objects, even if we saw an error */\n+}\n+\n+static int fsck_cruft(const char *basename, const char *path,\n+\t\t      void *data UNUSED)\n+{\n+\tif (!starts_with(basename, \"tmp_obj_\"))\n+\t\tfprintf_ln(stderr, _(\"bad sha1 file: %s\"), path);\n+\treturn 0;\n+}\n+\n+static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *cb_data)\n+{\n+\tstruct fsck_loose_data *data = cb_data;\n+\tdisplay_progress(data->progress, nr + 1);\n \treturn 0;\n }\n \n+static int odb_source_loose_fsck(struct odb_source *source,\n+\t\t\t\t struct odb_fsck_options *opts)\n+{\n+\tstruct odb_source_loose *loose = odb_source_loose_downcast(source);\n+\tstruct fsck_loose_data data = {\n+\t\t.source = loose,\n+\t\t.opts = opts,\n+\t};\n+\n+\tif (opts->flags & ODB_FSCK_VERBOSE)\n+\t\tfprintf_ln(stderr, _(\"Checking object directory\"));\n+\n+\tif (opts->flags & ODB_FSCK_PROGRESS)\n+\t\tdata.progress = start_progress(source->odb->repo,\n+\t\t\t\t\t       _(\"Checking object directories\"), 256);\n+\n+\tfor_each_loose_file_in_source(source, fsck_loose,\n+\t\t\t\t      fsck_cruft, fsck_subdir, &data);\n+\tdisplay_progress(data.progress, 256);\n+\tstop_progress(&data.progress);\n+\n+\treturn data.error_found ? -1 : 0;\n+}\n+\n struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \t\t\t\t\t      const char *path,\n \t\t\t\t\t      bool local)\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"}]}