Merge branch 'ps/odb-pluggable-fsck' into jch

The consistency checks for the object database (fsck) have been
decoupled from the generic builtin implementation and moved into the
backend-specific object source layers, making them pluggable for
different object storage formats.

* ps/odb-pluggable-fsck:
  builtin/fsck: move loose object verification into the loose source
  builtin/fsck: move multi-pack index verification into the packed source
  builtin/fsck: move bitmap verification into the packed source
  builtin/fsck: move reverse index verification into the packed source
  builtin/fsck: move packfile verification into the packed source
  odb: provide infrastructure for pluggable fsck checks
  builtin/fsck: don't check alternates with "--no-full"
  builtin/fsck: de-globalize option handling
  builtin/fsck: merge `fsck_obj_buffer()` and `fsck_obj()`
  builtin/fsck: use `fsck_obj_buffer()` when checking loose objects
jch
Junio C Hamano 2026-08-31 11:12:02 -07:00
commit 9f70572c3d
14 changed files with 394 additions and 259 deletions

View File

@ -7,13 +7,11 @@
#include "blob.h"
#include "tag.h"
#include "refs.h"
#include "pack.h"
#include "cache-tree.h"
#include "fsck.h"
#include "parse-options.h"
#include "progress.h"
#include "packfile.h"
#include "object-file.h"
#include "object-name.h"
#include "odb.h"
#include "odb/streaming.h"
@ -24,8 +22,6 @@
#include "run-command.h"
#include "sparse-index.h"
#include "worktree.h"
#include "pack-revindex.h"
#include "pack-bitmap.h"

#define REACHABLE 0x0001
#define SEEN 0x0002
@ -37,10 +33,8 @@ static int show_root;
static int show_tags;
static int show_unreachable;
static int include_reflogs = 1;
static int check_full = 1;
static int connectivity_only;
static int check_strict;
static int keep_cache_objects;
static struct fsck_options fsck_walk_options;
static struct fsck_options fsck_obj_options;
static int errors_found;
@ -48,17 +42,11 @@ static int write_lost_and_found;
static int verbose;
static int show_progress = -1;
static int show_dangling = 1;
static int name_objects;
static int check_references = 1;
static timestamp_t now;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
#define ERROR_REFS 010
#define ERROR_COMMIT_GRAPH 020
#define ERROR_MULTI_PACK_INDEX 040
#define ERROR_PACK_REV_INDEX 0100
#define ERROR_BITMAP 0200

static const char *describe_object(const struct object_id *oid)
{
@ -401,14 +389,27 @@ static void check_connectivity(struct repository *repo)
}
}

static int fsck_obj(struct repository *repo,
struct object *obj, void *buffer, unsigned long size)
static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,
unsigned long size, void *buffer, int *eaten, void *cb_data)
{
struct repository *repo = cb_data;
struct object *obj;
int err;

if (obj->flags & SEEN)
return 0;
obj->flags |= SEEN;
/*
* Note, buffer may be NULL if type is OBJ_BLOB. See
* verify_packfile(), data_valid variable for details.
*/
obj = parse_object_buffer(repo, oid, type, size, buffer, eaten);
if (!obj) {
errors_found |= ERROR_OBJECT;
err = error(_("%s: object corrupt or missing"),
oid_to_hex(oid));
goto out;
}

obj->flags &= ~REACHABLE;
obj->flags |= HAS_OBJ | SEEN;

if (verbose)
fprintf_ln(stderr, _("Checking %s %s"),
@ -417,6 +418,7 @@ static int fsck_obj(struct repository *repo,

if (fsck_walk(obj, NULL, &fsck_obj_options))
objerror(repo, obj, _("broken links"));

err = fsck_object(obj, buffer, size, &fsck_obj_options);
if (err)
goto out;
@ -442,32 +444,11 @@ static int fsck_obj(struct repository *repo,
}

out:
if (obj->type == OBJ_TREE)
if (obj && obj->type == OBJ_TREE)
free_tree_buffer((struct tree *)obj);
return err;
}

static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,
unsigned long size, void *buffer, int *eaten, void *cb_data)
{
struct repository *repo = cb_data;
struct object *obj;

/*
* Note, buffer may be NULL if type is OBJ_BLOB. See
* verify_packfile(), data_valid variable for details.
*/
obj = parse_object_buffer(repo, oid, type, size, buffer, eaten);
if (!obj) {
errors_found |= ERROR_OBJECT;
return error(_("%s: object corrupt or missing"),
oid_to_hex(oid));
}
obj->flags &= ~(REACHABLE | SEEN);
obj->flags |= HAS_OBJ;
return fsck_obj(repo, obj, buffer, size);
}

static int default_refs;

static void fsck_handle_reflog_oid(struct repository *repo,
@ -713,103 +694,6 @@ static void process_refs(struct repository *repo, struct snapshot *snap)
}
}

struct for_each_loose_cb {
struct repository *repo;
struct progress *progress;
};

static int fsck_loose(const struct object_id *oid, const char *path,
void *cb_data)
{
struct for_each_loose_cb *data = cb_data;
struct object *obj;
enum object_type type = OBJ_NONE;
size_t size;
void *contents = NULL;
int eaten;
struct object_info oi = OBJECT_INFO_INIT;
struct object_id real_oid = *null_oid(data->repo->hash_algo);
int err = 0;

oi.sizep = &size;
oi.typep = &type;

if (read_loose_object(data->repo, path, oid, &real_oid, &contents, &oi) < 0) {
if (contents && !oideq(&real_oid, oid))
err = error(_("%s: hash-path mismatch, found at: %s"),
oid_to_hex(&real_oid), path);
else
err = error(_("%s: object corrupt or missing: %s"),
oid_to_hex(oid), path);
}
if (err < 0) {
errors_found |= ERROR_OBJECT;
free(contents);
return 0; /* keep checking other objects */
}

if (!contents && type != OBJ_BLOB)
BUG("read_loose_object streamed a non-blob");

obj = parse_object_buffer(data->repo, oid, type, size,
contents, &eaten);

if (!obj) {
errors_found |= ERROR_OBJECT;
error(_("%s: object could not be parsed: %s"),
oid_to_hex(oid), path);
if (!eaten)
free(contents);
return 0; /* keep checking other objects */
}

obj->flags &= ~(REACHABLE | SEEN);
obj->flags |= HAS_OBJ;
if (fsck_obj(data->repo, obj, contents, size))
errors_found |= ERROR_OBJECT;

if (!eaten)
free(contents);
return 0; /* keep checking other objects, even if we saw an error */
}

static int fsck_cruft(const char *basename, const char *path,
void *data UNUSED)
{
if (!starts_with(basename, "tmp_obj_"))
fprintf_ln(stderr, _("bad sha1 file: %s"), path);
return 0;
}

static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *data)
{
struct for_each_loose_cb *cb_data = data;
struct progress *progress = cb_data->progress;
display_progress(progress, nr + 1);
return 0;
}

static void fsck_source(struct repository *repo, struct odb_source *source)
{
struct progress *progress = NULL;
struct for_each_loose_cb cb_data = {
.repo = source->odb->repo,
.progress = progress,
};

if (verbose)
fprintf_ln(stderr, _("Checking object directory"));

if (show_progress)
progress = start_progress(repo,
_("Checking object directories"), 256);

for_each_loose_file_in_source(source, fsck_loose,
fsck_cruft, fsck_subdir, &cb_data);
display_progress(progress, 256);
stop_progress(&progress);
}

static int fsck_cache_tree(struct repository *repo, struct cache_tree *it, const char *index_path)
{
int i;
@ -918,40 +802,6 @@ static int mark_object_for_connectivity(const struct object_id *oid,
return 0;
}

static int check_pack_rev_indexes(struct repository *r, int show_progress)
{
struct progress *progress = NULL;
struct packed_git *p;
uint32_t pack_count = 0;
int res = 0;

if (show_progress) {
repo_for_each_pack(r, p)
pack_count++;
progress = start_delayed_progress(r,
"Verifying reverse pack-indexes", pack_count);
pack_count = 0;
}

repo_for_each_pack(r, p) {
int load_error = load_pack_revindex_from_disk(p);

if (load_error < 0) {
error(_("unable to load rev-index for pack '%s'"), p->pack_name);
res = ERROR_PACK_REV_INDEX;
} else if (!load_error &&
!load_pack_revindex(r, p) &&
verify_pack_revindex(p)) {
error(_("invalid rev-index for pack '%s'"), p->pack_name);
res = ERROR_PACK_REV_INDEX;
}
display_progress(progress, ++pack_count);
}
stop_progress(&progress);

return res;
}

static void fsck_refs(struct repository *r)
{
struct child_process refs_verify = CHILD_PROCESS_INIT;
@ -986,30 +836,38 @@ static char const * const fsck_usage[] = {
NULL
};

static struct option fsck_opts[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_BOOL(0, "unreachable", &show_unreachable, N_("show unreachable objects")),
OPT_BOOL(0, "dangling", &show_dangling, N_("show dangling objects")),
OPT_BOOL(0, "tags", &show_tags, N_("report tags")),
OPT_BOOL(0, "root", &show_root, N_("report root nodes")),
OPT_BOOL(0, "cache", &keep_cache_objects, N_("make index objects head nodes")),
OPT_BOOL(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")),
OPT_BOOL(0, "full", &check_full, N_("also consider packs and alternate objects")),
OPT_BOOL(0, "connectivity-only", &connectivity_only, N_("check only connectivity")),
OPT_BOOL(0, "strict", &check_strict, N_("enable more strict checking")),
OPT_BOOL(0, "lost-found", &write_lost_and_found,
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
OPT_BOOL(0, "references", &check_references, N_("check reference database consistency")),
OPT_END(),
};

int cmd_fsck(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
struct odb_fsck_options odb_fsck_opts = {
.flags = ODB_FSCK_FULL,
.object_cb = fsck_obj_buffer,
.object_payload = repo,
};
int keep_cache_objects = 0;
int name_objects = 0;
int check_references = 1;
struct option fsck_opts[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_BOOL(0, "unreachable", &show_unreachable, N_("show unreachable objects")),
OPT_BOOL(0, "dangling", &show_dangling, N_("show dangling objects")),
OPT_BOOL(0, "tags", &show_tags, N_("report tags")),
OPT_BOOL(0, "root", &show_root, N_("report root nodes")),
OPT_BOOL(0, "cache", &keep_cache_objects, N_("make index objects head nodes")),
OPT_BOOL(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")),
OPT_BIT(0, "full", &odb_fsck_opts.flags,
N_("also consider packs and alternate objects"), ODB_FSCK_FULL),
OPT_BOOL(0, "connectivity-only", &connectivity_only, N_("check only connectivity")),
OPT_BOOL(0, "strict", &check_strict, N_("enable more strict checking")),
OPT_BOOL(0, "lost-found", &write_lost_and_found,
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
OPT_BOOL(0, "references", &check_references, N_("check reference database consistency")),
OPT_END(),
};
struct odb_source *source;
struct snapshot snap = {
.nr = 0,
@ -1037,11 +895,15 @@ int cmd_fsck(int argc,

if (show_progress == -1)
show_progress = isatty(2);
if (verbose)
if (verbose) {
show_progress = 0;
odb_fsck_opts.flags |= ODB_FSCK_VERBOSE;
}
if (show_progress)
odb_fsck_opts.flags |= ODB_FSCK_PROGRESS;

if (write_lost_and_found) {
check_full = 1;
odb_fsck_opts.flags |= ODB_FSCK_FULL;
include_reflogs = 0;
}

@ -1069,35 +931,8 @@ int cmd_fsck(int argc,
odb_for_each_object(repo->objects, NULL,
mark_object_for_connectivity, repo, 0);
} else {
for (source = repo->objects->sources; source; source = source->next)
fsck_source(repo, source);

if (check_full) {
struct packed_git *p;
uint32_t total = 0, count = 0;
struct progress *progress = NULL;

if (show_progress) {
repo_for_each_pack(repo, p) {
if (open_pack_index(p))
continue;
total += p->num_objects;
}

progress = start_progress(repo,
_("Checking objects"), total);
}

repo_for_each_pack(repo, p) {
/* verify gives error messages itself */
if (verify_pack(repo,
p, fsck_obj_buffer, repo,
progress, count))
errors_found |= ERROR_PACK;
count += p->num_objects;
}
stop_progress(&progress);
}
if (odb_fsck(repo->objects, &odb_fsck_opts) < 0)
errors_found |= ERROR_OBJECT;

if (fsck_finish(&fsck_obj_options))
errors_found |= ERROR_OBJECT;
@ -1145,10 +980,6 @@ int cmd_fsck(int argc,
free_worktrees(worktrees);
}

errors_found |= check_pack_rev_indexes(repo, show_progress);
if (verify_bitmap_files(repo))
errors_found |= ERROR_BITMAP;

check_connectivity(repo);

if (repo->settings.core_commit_graph) {
@ -1168,23 +999,6 @@ int cmd_fsck(int argc,
}
}

if (repo->settings.core_multi_pack_index) {
struct child_process midx_verify = CHILD_PROCESS_INIT;

for (source = repo->objects->sources; source; source = source->next) {
child_process_init(&midx_verify);
midx_verify.git_cmd = 1;
strvec_pushl(&midx_verify.args, "multi-pack-index",
"verify", "--object-dir", source->path, NULL);
if (show_progress)
strvec_push(&midx_verify.args, "--progress");
else
strvec_push(&midx_verify.args, "--no-progress");
if (run_command(&midx_verify))
errors_found |= ERROR_MULTI_PACK_INDEX;
}
}

free_snapshot_refs(&snap);
return errors_found;
}

9
odb.c
View File

@ -1190,3 +1190,12 @@ void odb_reprepare(struct object_database *o)
{
odb_prepare(o, ODB_PREPARE_FLUSH_CACHES);
}

int odb_fsck(struct object_database *odb, struct odb_fsck_options *options)
{
int ret = 0;
for (struct odb_source *source = odb->sources; source; source = source->next)
if ((options->flags & ODB_FSCK_FULL) || source->local)
ret |= odb_source_fsck(source, options);
return ret;
}

33
odb.h
View File

@ -198,6 +198,39 @@ void odb_prepare(struct object_database *o, enum odb_prepare_flags flags);
/* Equivalent to `odb_prepare(o, ODB_PREPARE_FLUSH_CACHES)`. */
void odb_reprepare(struct object_database *o);

enum odb_fsck_flags {
/*
* If set, perform a full consistency check for the full object
* database, including all of its sources and the contents of their
* optimized formats. Otherwise, only check the local source, and
* restrict checks of its optimized formats to cheap structural
* verification of their metadata.
*/
ODB_FSCK_FULL = (1 << 0),

/* Display a progress meter, if sensible. */
ODB_FSCK_PROGRESS = (1 << 1),

/* Be extra verbose when checking the database. */
ODB_FSCK_VERBOSE = (1 << 2),
};

/* Options that shall be passed to `odb_fsck()`. */
struct odb_fsck_options {
enum odb_fsck_flags flags;

int (*object_cb)(const struct object_id *oid, enum object_type type,
unsigned long size, void *buffer, int *eaten, void *cb_data);
void *object_payload;
};

/*
* Run backend-specific integrity checks on all object sources. Each source
* performs the checks appropriate to its type. Returns 0 on success, a
* negative error code otherwise.
*/
int odb_fsck(struct object_database *odb, struct odb_fsck_options *opts);

/*
* Find source by its object directory path. Returns a `NULL` pointer in case
* the source could not be found.

View File

@ -928,6 +928,18 @@ static int odb_source_files_generate_pack(struct odb_source *source UNUSED,
return 0;
}

static int odb_source_files_fsck(struct odb_source *source,
struct odb_fsck_options *opts)
{
struct odb_source_files *files = odb_source_files_downcast(source);
int ret = 0;

ret |= odb_source_fsck(&files->loose->base, opts);
ret |= odb_source_fsck(&files->packed->base, opts);

return ret;
}

struct odb_source_files *odb_source_files_new(struct object_database *odb,
const char *path,
bool local)
@ -943,6 +955,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
files->base.close = odb_source_files_close;
files->base.create_on_disk = odb_source_files_create_on_disk;
files->base.prepare = odb_source_files_prepare;
files->base.fsck = odb_source_files_fsck;
files->base.read_object_info = odb_source_files_read_object_info;
files->base.read_object_stream = odb_source_files_read_object_stream;
files->base.for_each_object = odb_source_files_for_each_object;

View File

@ -1,6 +1,7 @@
#include "git-compat-util.h"
#include "object-file.h"
#include "odb.h"
#include "fsck.h"
#include "odb/source-inmemory.h"
#include "odb/streaming.h"
#include "oidtree.h"
@ -363,6 +364,12 @@ static void odb_source_inmemory_free(struct odb_source *source)
free(inmemory);
}

static int odb_source_inmemory_fsck(struct odb_source *source UNUSED,
struct odb_fsck_options *opts UNUSED)
{
return 0;
}

struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
{
struct odb_source_inmemory *source;
@ -373,6 +380,7 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
source->base.free = odb_source_inmemory_free;
source->base.close = odb_source_inmemory_close;
source->base.prepare = odb_source_inmemory_prepare;
source->base.fsck = odb_source_inmemory_fsck;
source->base.read_object_info = odb_source_inmemory_read_object_info;
source->base.read_object_stream = odb_source_inmemory_read_object_stream;
source->base.for_each_object = odb_source_inmemory_for_each_object;

View File

@ -12,6 +12,7 @@
#include "odb/streaming.h"
#include "oidtree.h"
#include "path.h"
#include "progress.h"
#include "repository.h"
#include "strbuf.h"
#include "tempfile.h"
@ -1032,6 +1033,96 @@ static void odb_source_loose_free(struct odb_source *source)
free(loose);
}

struct fsck_loose_data {
struct odb_source_loose *source;
struct odb_fsck_options *opts;
struct progress *progress;
bool error_found;
};

static int fsck_loose(const struct object_id *oid, const char *path,
void *cb_data)
{
struct fsck_loose_data *data = cb_data;
enum object_type type = OBJ_NONE;
size_t size;
void *contents = NULL;
int eaten = 0;
struct object_info oi = OBJECT_INFO_INIT;
struct object_id real_oid = *null_oid(data->source->base.odb->repo->hash_algo);
int err = 0;

oi.sizep = &size;
oi.typep = &type;

if (read_loose_object(data->source->base.odb->repo,
path, oid, &real_oid, &contents, &oi) < 0) {
if (contents && !oideq(&real_oid, oid))
err = error(_("%s: hash-path mismatch, found at: %s"),
oid_to_hex(&real_oid), path);
else
err = error(_("%s: object corrupt or missing: %s"),
oid_to_hex(oid), path);
}
if (err < 0)
goto out;

if (!contents && type != OBJ_BLOB)
BUG("read_loose_object streamed a non-blob");

if (data->opts->object_cb(oid, type, size, contents, &eaten,
data->opts->object_payload)) {
err = -1;
goto out;
}

out:
if (err)
data->error_found = true;
if (!eaten)
free(contents);
return 0; /* keep checking other objects, even if we saw an error */
}

static int fsck_cruft(const char *basename, const char *path,
void *data UNUSED)
{
if (!starts_with(basename, "tmp_obj_"))
fprintf_ln(stderr, _("bad sha1 file: %s"), path);
return 0;
}

static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *cb_data)
{
struct fsck_loose_data *data = cb_data;
display_progress(data->progress, nr + 1);
return 0;
}

static int odb_source_loose_fsck(struct odb_source *source,
struct odb_fsck_options *opts)
{
struct odb_source_loose *loose = odb_source_loose_downcast(source);
struct fsck_loose_data data = {
.source = loose,
.opts = opts,
};

if (opts->flags & ODB_FSCK_VERBOSE)
fprintf_ln(stderr, _("Checking object directory"));

if (opts->flags & ODB_FSCK_PROGRESS)
data.progress = start_progress(source->odb->repo,
_("Checking object directories"), 256);

for_each_loose_file_in_source(source, fsck_loose,
fsck_cruft, fsck_subdir, &data);
display_progress(data.progress, 256);
stop_progress(&data.progress);

return data.error_found ? -1 : 0;
}

struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
const char *path,
bool local)
@ -1044,6 +1135,7 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
loose->base.free = odb_source_loose_free;
loose->base.close = odb_source_loose_close;
loose->base.prepare = odb_source_loose_prepare;
loose->base.fsck = odb_source_loose_fsck;
loose->base.read_object_info = odb_source_loose_read_object_info;
loose->base.read_object_stream = odb_source_loose_read_object_stream;
loose->base.for_each_object = odb_source_loose_for_each_object;

View File

@ -3,6 +3,7 @@
#include "chdir-notify.h"
#include "dir.h"
#include "gettext.h"
#include "fsck.h"
#include "git-zlib.h"
#include "hex.h"
#include "list-objects-filter-options.h"
@ -10,9 +11,13 @@
#include "midx.h"
#include "odb/source-packed.h"
#include "odb/streaming.h"
#include "pack.h"
#include "pack-revindex.h"
#include "packfile.h"
#include "pack-bitmap.h"
#include "strbuf.h"
#include "progress.h"
#include "run-command.h"

static int find_pack_entry(struct odb_source_packed *store,
const struct object_id *oid,
@ -849,6 +854,117 @@ static void odb_source_packed_free(struct odb_source *source)
free(packed);
}

static int verify_packs(struct odb_source_packed *source,
struct odb_fsck_options *opts)
{
struct progress *progress = NULL;
struct packfile_list_entry *e;
uint32_t total = 0, count = 0;
int ret = 0;

if (opts->flags & ODB_FSCK_PROGRESS) {
for (e = packfile_store_get_packs(source); e; e = e->next) {
if (open_pack_index(e->pack))
continue;
total += e->pack->num_objects;
}

progress = start_progress(source->base.odb->repo,
_("Checking objects"), total);
}

for (e = packfile_store_get_packs(source); e; e = e->next) {
/* verify gives error messages itself */
if (verify_pack(source->base.odb->repo, e->pack,
opts->object_cb, opts->object_payload,
progress, count))
ret = -1;
count += e->pack->num_objects;
}
stop_progress(&progress);

return ret;
}

static int verify_reverse_indices(struct odb_source_packed *source,
struct odb_fsck_options *opts)
{
struct progress *progress = NULL;
struct packfile_list_entry *e;
uint32_t pack_count = 0;
int res = 0;

if (opts->flags & ODB_FSCK_PROGRESS) {
for (e = packfile_store_get_packs(source); e; e = e->next)
pack_count++;
progress = start_delayed_progress(source->base.odb->repo,
"Verifying reverse pack-indexes", pack_count);
pack_count = 0;
}

for (e = packfile_store_get_packs(source); e; e = e->next) {
int load_error = load_pack_revindex_from_disk(e->pack);

if (load_error < 0) {
error(_("unable to load rev-index for pack '%s'"), e->pack->pack_name);
res = -1;
} else if (!load_error &&
!load_pack_revindex(source->base.odb->repo, e->pack) &&
verify_pack_revindex(e->pack)) {
error(_("invalid rev-index for pack '%s'"), e->pack->pack_name);
res = -1;
}
display_progress(progress, ++pack_count);
}
stop_progress(&progress);

return res;
}

static int verify_midx(struct odb_source_packed *source,
struct odb_fsck_options *opts)
{
struct child_process midx_verify = CHILD_PROCESS_INIT;
int ret = 0;

if (!source->base.odb->repo->settings.core_multi_pack_index)
return 0;

child_process_init(&midx_verify);
midx_verify.git_cmd = 1;
strvec_pushl(&midx_verify.args, "multi-pack-index",
"verify", "--object-dir", source->base.path, NULL);
if (opts->flags & ODB_FSCK_PROGRESS)
strvec_push(&midx_verify.args, "--progress");
else
strvec_push(&midx_verify.args, "--no-progress");
if (run_command(&midx_verify))
ret = -1;

return ret;
}

static int odb_source_packed_fsck(struct odb_source *source,
struct odb_fsck_options *opts)
{
struct odb_source_packed *packed = odb_source_packed_downcast(source);
int ret = 0;

if ((opts->flags & ODB_FSCK_FULL) && verify_packs(packed, opts) < 0)
ret = -1;

if (verify_reverse_indices(packed, opts) < 0)
ret = -1;

if (verify_bitmap_files(packed))
ret = -1;

if (verify_midx(packed, opts) < 0)
ret = -1;

return ret;
}

struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
const char *path,
bool local)
@ -862,6 +978,7 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
packed->base.free = odb_source_packed_free;
packed->base.close = odb_source_packed_close;
packed->base.prepare = odb_source_packed_prepare;
packed->base.fsck = odb_source_packed_fsck;
packed->base.read_object_info = odb_source_packed_read_object_info;
packed->base.read_object_stream = odb_source_packed_read_object_stream;
packed->base.for_each_object = odb_source_packed_for_each_object;

View File

@ -322,6 +322,17 @@ struct odb_source {
int (*generate_pack)(struct odb_source *source,
struct odb_pack_generator **out,
const struct odb_generate_pack_options *opts);

/*
* This callback is expected to check the integrity of the object source
* and report any errors found via the fsck options. The checks performed
* are backend-specific.
*
* The callback is expected to return 0 on success, a negative error
* code otherwise.
*/
int (*fsck)(struct odb_source *source,
struct odb_fsck_options *options);
};

/*
@ -583,4 +594,14 @@ static inline int odb_source_generate_pack(struct odb_source *source,
return source->generate_pack(source, out, opts);
}

/*
* Check the integrity of the object database source. The checks performed
* are backend-specific. Returns 0 on success, a negative error code otherwise.
*/
static inline int odb_source_fsck(struct odb_source *source,
struct odb_fsck_options *opts)
{
return source->fsck(source, opts);
}

#endif

View File

@ -3410,28 +3410,22 @@ static int verify_bitmap_file(const struct git_hash_algo *algop,
return res;
}

int verify_bitmap_files(struct repository *r)
int verify_bitmap_files(struct odb_source_packed *source)
{
struct odb_source *source;
struct packed_git *p;
struct packfile_list_entry *e;
struct multi_pack_index *m;
int res = 0;

for (source = r->objects->sources; source; source = source->next) {
struct odb_source_files *files = odb_source_files_downcast(source);
struct multi_pack_index *m = get_multi_pack_index(files->packed);
char *midx_bitmap_name;

if (!m)
continue;

midx_bitmap_name = midx_bitmap_filename(m);
res |= verify_bitmap_file(r->hash_algo, midx_bitmap_name);
m = get_multi_pack_index(source);
if (m) {
char *midx_bitmap_name = midx_bitmap_filename(m);
res |= verify_bitmap_file(source->base.odb->repo->hash_algo, midx_bitmap_name);
free(midx_bitmap_name);
}

repo_for_each_pack(r, p) {
char *pack_bitmap_name = pack_bitmap_filename(p);
res |= verify_bitmap_file(r->hash_algo, pack_bitmap_name);
for (e = packfile_store_get_packs(source); e; e = e->next) {
char *pack_bitmap_name = pack_bitmap_filename(e->pack);
res |= verify_bitmap_file(source->base.odb->repo->hash_algo, pack_bitmap_name);
free(pack_bitmap_name);
}


View File

@ -205,7 +205,7 @@ int bitmap_is_midx(struct bitmap_index *bitmap_git);

int bitmap_is_preferred_refname(struct repository *r, const char *refname);

int verify_bitmap_files(struct repository *r);
int verify_bitmap_files(struct odb_source_packed *source);

struct ewah_bitmap *read_bitmap(const unsigned char *map,
size_t map_size, size_t *map_pos);

View File

@ -844,6 +844,11 @@ test_expect_success 'alternate objects are correctly blamed' '
echo "../../alt.git/objects" >.git/objects/info/alternates &&
mkdir alt.git/objects/$(dirname $path) &&
>alt.git/objects/$(dirname $path)/$(basename $path) &&

# Without "--full", only the local object source is checked.
git fsck --no-full >out 2>&1 &&
test_must_be_empty out &&

test_must_fail git fsck >out 2>&1 &&
test_grep alt.git out
'

View File

@ -573,6 +573,19 @@ test_expect_success 'verify incorrect checksum' '
$objdir "incorrect checksum"
'

test_expect_success 'git fsck --no-full checks multi-pack-index, --connectivity-only does not' '
pos=$(($(wc -c <$objdir/pack/multi-pack-index) - 10)) &&
corrupt_midx_and_verify $pos \
"\377\377\377\377\377\377\377\377\377\377" \
$objdir "incorrect checksum" &&

test_must_fail git fsck --no-full 2>err &&
test_grep "incorrect checksum" err &&

git fsck --connectivity-only 2>err &&
test_grep ! "incorrect checksum" err
'

test_expect_success 'setup for v1-specific fsck tests' '
git -c midx.version=1 multi-pack-index write
'

View File

@ -204,4 +204,12 @@ test_expect_success 'fsck catches invalid header: hash function' '
"reverse-index file .* has unsupported hash id"
'

test_expect_success 'fsck --no-full checks rev-index, --connectivity-only does not' '
test_must_fail git -C corrupt fsck --no-full 2>err &&
test_grep "has unsupported hash id" err &&

git -C corrupt fsck --connectivity-only 2>err &&
test_grep ! "has unsupported hash id" err
'

test_done

View File

@ -498,7 +498,15 @@ test_expect_success 'git fsck correctly identifies good and bad bitmaps' '
corrupt_file "$packbitmap" &&
test_must_fail git fsck 2>err &&
test_grep "bitmap file '\''$midxbitmap'\'' has invalid checksum" err &&
test_grep "bitmap file '\''$packbitmap'\'' has invalid checksum" err
test_grep "bitmap file '\''$packbitmap'\'' has invalid checksum" err &&

# The bitmap checks are performed with "--no-full", but not with
# "--connectivity-only".
test_must_fail git fsck --no-full 2>err &&
test_grep "bitmap file '\''$midxbitmap'\'' has invalid checksum" err &&
test_grep "bitmap file '\''$packbitmap'\'' has invalid checksum" err &&
git fsck --connectivity-only 2>err &&
test_grep ! "invalid checksum" err
'

test_expect_success 'corrupt MIDX with bitmap causes fallback' '