Merge branch 'sj/ref-fsck'

"git fsck" infrastructure has been taught to also check the sanity
of the ref database, in addition to the object database.

* sj/ref-fsck:
  fsck: add ref name check for files backend
  files-backend: add unified interface for refs scanning
  builtin/refs: add verify subcommand
  refs: set up ref consistency check infrastructure
  fsck: add refs report function
  fsck: add a unified interface for reporting fsck messages
  fsck: make "fsck_error" callback generic
  fsck: rename objects-related fsck error functions
  fsck: rename "skiplist" to "skip_oids"
This commit is contained in:
Junio C Hamano
2024-08-16 12:51:51 -07:00
16 changed files with 477 additions and 59 deletions

View File

@ -19,6 +19,12 @@
`badParentSha1`:: `badParentSha1`::
(ERROR) A commit object has a bad parent sha1. (ERROR) A commit object has a bad parent sha1.
`badRefFiletype`::
(ERROR) A ref has a bad file type.
`badRefName`::
(ERROR) A ref has an invalid format.
`badTagName`:: `badTagName`::
(INFO) A tag has an invalid format. (INFO) A tag has an invalid format.

View File

@ -10,6 +10,7 @@ SYNOPSIS
-------- --------
[verse] [verse]
'git refs migrate' --ref-format=<format> [--dry-run] 'git refs migrate' --ref-format=<format> [--dry-run]
'git refs verify' [--strict] [--verbose]
DESCRIPTION DESCRIPTION
----------- -----------
@ -22,6 +23,9 @@ COMMANDS
migrate:: migrate::
Migrate ref store between different formats. Migrate ref store between different formats.
verify::
Verify reference database consistency.
OPTIONS OPTIONS
------- -------
@ -39,6 +43,15 @@ include::ref-storage-format.txt[]
can be used to double check that the migration works as expected before can be used to double check that the migration works as expected before
performing the actual migration. performing the actual migration.
The following options are specific to 'git refs verify':
--strict::
Enable stricter error checking. This will cause warnings to be
reported as errors. See linkgit:git-fsck[1].
--verbose::
When verifying the reference database consistency, be chatty.
KNOWN LIMITATIONS KNOWN LIMITATIONS
----------------- -----------------

View File

@ -89,13 +89,16 @@ static int objerror(struct object *obj, const char *err)
return -1; return -1;
} }
static int fsck_error_func(struct fsck_options *o UNUSED, static int fsck_objects_error_func(struct fsck_options *o UNUSED,
const struct object_id *oid, void *fsck_report,
enum object_type object_type, enum fsck_msg_type msg_type,
enum fsck_msg_type msg_type, enum fsck_msg_id msg_id UNUSED,
enum fsck_msg_id msg_id UNUSED, const char *message)
const char *message)
{ {
struct fsck_object_report *report = fsck_report;
const struct object_id *oid = report->oid;
enum object_type object_type = report->object_type;
switch (msg_type) { switch (msg_type) {
case FSCK_WARN: case FSCK_WARN:
/* TRANSLATORS: e.g. warning in tree 01bfda: <more explanation> */ /* TRANSLATORS: e.g. warning in tree 01bfda: <more explanation> */
@ -938,7 +941,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
fsck_walk_options.walk = mark_object; fsck_walk_options.walk = mark_object;
fsck_obj_options.walk = mark_used; fsck_obj_options.walk = mark_used;
fsck_obj_options.error_func = fsck_error_func; fsck_obj_options.error_func = fsck_objects_error_func;
if (check_strict) if (check_strict)
fsck_obj_options.strict = 1; fsck_obj_options.strict = 1;

View File

@ -18,8 +18,7 @@ static int option_strict = 1;
static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
static int mktag_fsck_error_func(struct fsck_options *o UNUSED, static int mktag_fsck_error_func(struct fsck_options *o UNUSED,
const struct object_id *oid UNUSED, void *fsck_report UNUSED,
enum object_type object_type UNUSED,
enum fsck_msg_type msg_type, enum fsck_msg_type msg_type,
enum fsck_msg_id msg_id UNUSED, enum fsck_msg_id msg_id UNUSED,
const char *message) const char *message)

View File

@ -1,4 +1,6 @@
#include "builtin.h" #include "builtin.h"
#include "config.h"
#include "fsck.h"
#include "parse-options.h" #include "parse-options.h"
#include "refs.h" #include "refs.h"
#include "repository.h" #include "repository.h"
@ -7,6 +9,9 @@
#define REFS_MIGRATE_USAGE \ #define REFS_MIGRATE_USAGE \
N_("git refs migrate --ref-format=<format> [--dry-run]") N_("git refs migrate --ref-format=<format> [--dry-run]")
#define REFS_VERIFY_USAGE \
N_("git refs verify [--strict] [--verbose]")
static int cmd_refs_migrate(int argc, const char **argv, const char *prefix) static int cmd_refs_migrate(int argc, const char **argv, const char *prefix)
{ {
const char * const migrate_usage[] = { const char * const migrate_usage[] = {
@ -58,15 +63,44 @@ out:
return err; return err;
} }
static int cmd_refs_verify(int argc, const char **argv, const char *prefix)
{
struct fsck_options fsck_refs_options = FSCK_REFS_OPTIONS_DEFAULT;
const char * const verify_usage[] = {
REFS_VERIFY_USAGE,
NULL,
};
struct option options[] = {
OPT_BOOL(0, "verbose", &fsck_refs_options.verbose, N_("be verbose")),
OPT_BOOL(0, "strict", &fsck_refs_options.strict, N_("enable strict checking")),
OPT_END(),
};
int ret;
argc = parse_options(argc, argv, prefix, options, verify_usage, 0);
if (argc)
usage(_("'git refs verify' takes no arguments"));
git_config(git_fsck_config, &fsck_refs_options);
prepare_repo_settings(the_repository);
ret = refs_fsck(get_main_ref_store(the_repository), &fsck_refs_options);
fsck_options_clear(&fsck_refs_options);
return ret;
}
int cmd_refs(int argc, const char **argv, const char *prefix) int cmd_refs(int argc, const char **argv, const char *prefix)
{ {
const char * const refs_usage[] = { const char * const refs_usage[] = {
REFS_MIGRATE_USAGE, REFS_MIGRATE_USAGE,
REFS_VERIFY_USAGE,
NULL, NULL,
}; };
parse_opt_subcommand_fn *fn = NULL; parse_opt_subcommand_fn *fn = NULL;
struct option opts[] = { struct option opts[] = {
OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate), OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
OPT_END(), OPT_END(),
}; };

125
fsck.c
View File

@ -205,7 +205,7 @@ void fsck_set_msg_types(struct fsck_options *options, const char *values)
if (!strcmp(buf, "skiplist")) { if (!strcmp(buf, "skiplist")) {
if (equal == len) if (equal == len)
die("skiplist requires a path"); die("skiplist requires a path");
oidset_parse_file(&options->skiplist, buf + equal + 1, oidset_parse_file(&options->skip_oids, buf + equal + 1,
the_repository->hash_algo); the_repository->hash_algo);
buf += len + 1; buf += len + 1;
continue; continue;
@ -223,15 +223,18 @@ void fsck_set_msg_types(struct fsck_options *options, const char *values)
static int object_on_skiplist(struct fsck_options *opts, static int object_on_skiplist(struct fsck_options *opts,
const struct object_id *oid) const struct object_id *oid)
{ {
return opts && oid && oidset_contains(&opts->skiplist, oid); return opts && oid && oidset_contains(&opts->skip_oids, oid);
} }
__attribute__((format (printf, 5, 6))) /*
static int report(struct fsck_options *options, * Provide the common functionality for either fscking refs or objects.
const struct object_id *oid, enum object_type object_type, * It will get the current msg error type and call the error_func callback
enum fsck_msg_id msg_id, const char *fmt, ...) * which is registered in the "fsck_options" struct.
*/
static int fsck_vreport(struct fsck_options *options,
void *fsck_report,
enum fsck_msg_id msg_id, const char *fmt, va_list ap)
{ {
va_list ap;
struct strbuf sb = STRBUF_INIT; struct strbuf sb = STRBUF_INIT;
enum fsck_msg_type msg_type = fsck_msg_type(msg_id, options); enum fsck_msg_type msg_type = fsck_msg_type(msg_id, options);
int result; int result;
@ -239,9 +242,6 @@ static int report(struct fsck_options *options,
if (msg_type == FSCK_IGNORE) if (msg_type == FSCK_IGNORE)
return 0; return 0;
if (object_on_skiplist(options, oid))
return 0;
if (msg_type == FSCK_FATAL) if (msg_type == FSCK_FATAL)
msg_type = FSCK_ERROR; msg_type = FSCK_ERROR;
else if (msg_type == FSCK_INFO) else if (msg_type == FSCK_INFO)
@ -250,16 +250,49 @@ static int report(struct fsck_options *options,
prepare_msg_ids(); prepare_msg_ids();
strbuf_addf(&sb, "%s: ", msg_id_info[msg_id].camelcased); strbuf_addf(&sb, "%s: ", msg_id_info[msg_id].camelcased);
va_start(ap, fmt);
strbuf_vaddf(&sb, fmt, ap); strbuf_vaddf(&sb, fmt, ap);
result = options->error_func(options, oid, object_type, result = options->error_func(options, fsck_report,
msg_type, msg_id, sb.buf); msg_type, msg_id, sb.buf);
strbuf_release(&sb); strbuf_release(&sb);
return result;
}
__attribute__((format (printf, 5, 6)))
static int report(struct fsck_options *options,
const struct object_id *oid, enum object_type object_type,
enum fsck_msg_id msg_id, const char *fmt, ...)
{
va_list ap;
struct fsck_object_report report = {
.oid = oid,
.object_type = object_type
};
int result;
if (object_on_skiplist(options, oid))
return 0;
va_start(ap, fmt);
result = fsck_vreport(options, &report, msg_id, fmt, ap);
va_end(ap); va_end(ap);
return result; return result;
} }
int fsck_report_ref(struct fsck_options *options,
struct fsck_ref_report *report,
enum fsck_msg_id msg_id,
const char *fmt, ...)
{
va_list ap;
int result;
va_start(ap, fmt);
result = fsck_vreport(options, report, msg_id, fmt, ap);
va_end(ap);
return result;
}
void fsck_enable_object_names(struct fsck_options *options) void fsck_enable_object_names(struct fsck_options *options)
{ {
if (!options->object_names) if (!options->object_names)
@ -1200,13 +1233,15 @@ int fsck_buffer(const struct object_id *oid, enum object_type type,
type); type);
} }
int fsck_error_function(struct fsck_options *o, int fsck_objects_error_function(struct fsck_options *o,
const struct object_id *oid, void *fsck_report,
enum object_type object_type UNUSED, enum fsck_msg_type msg_type,
enum fsck_msg_type msg_type, enum fsck_msg_id msg_id UNUSED,
enum fsck_msg_id msg_id UNUSED, const char *message)
const char *message)
{ {
struct fsck_object_report *report = fsck_report;
const struct object_id *oid = report->oid;
if (msg_type == FSCK_WARN) { if (msg_type == FSCK_WARN) {
warning("object %s: %s", fsck_describe_object(o, oid), message); warning("object %s: %s", fsck_describe_object(o, oid), message);
return 0; return 0;
@ -1215,6 +1250,32 @@ int fsck_error_function(struct fsck_options *o,
return 1; return 1;
} }
int fsck_refs_error_function(struct fsck_options *options UNUSED,
void *fsck_report,
enum fsck_msg_type msg_type,
enum fsck_msg_id msg_id UNUSED,
const char *message)
{
struct fsck_ref_report *report = fsck_report;
struct strbuf sb = STRBUF_INIT;
int ret = 0;
strbuf_addstr(&sb, report->path);
if (report->oid)
strbuf_addf(&sb, " -> (%s)", oid_to_hex(report->oid));
else if (report->referent)
strbuf_addf(&sb, " -> (%s)", report->referent);
if (msg_type == FSCK_WARN)
warning("%s: %s", sb.buf, message);
else
ret = error("%s: %s", sb.buf, message);
strbuf_release(&sb);
return ret;
}
static int fsck_blobs(struct oidset *blobs_found, struct oidset *blobs_done, static int fsck_blobs(struct oidset *blobs_found, struct oidset *blobs_done,
enum fsck_msg_id msg_missing, enum fsck_msg_id msg_type, enum fsck_msg_id msg_missing, enum fsck_msg_id msg_type,
struct fsck_options *options, const char *blob_type) struct fsck_options *options, const char *blob_type)
@ -1270,6 +1331,17 @@ int fsck_finish(struct fsck_options *options)
return ret; return ret;
} }
void fsck_options_clear(struct fsck_options *options)
{
free(options->msg_type);
oidset_clear(&options->skip_oids);
oidset_clear(&options->gitmodules_found);
oidset_clear(&options->gitmodules_done);
oidset_clear(&options->gitattributes_found);
oidset_clear(&options->gitattributes_done);
kh_clear_oid_map(options->object_names);
}
int git_fsck_config(const char *var, const char *value, int git_fsck_config(const char *var, const char *value,
const struct config_context *ctx, void *cb) const struct config_context *ctx, void *cb)
{ {
@ -1303,16 +1375,17 @@ int git_fsck_config(const char *var, const char *value,
* Custom error callbacks that are used in more than one place. * Custom error callbacks that are used in more than one place.
*/ */
int fsck_error_cb_print_missing_gitmodules(struct fsck_options *o, int fsck_objects_error_cb_print_missing_gitmodules(struct fsck_options *o,
const struct object_id *oid, void *fsck_report,
enum object_type object_type, enum fsck_msg_type msg_type,
enum fsck_msg_type msg_type, enum fsck_msg_id msg_id,
enum fsck_msg_id msg_id, const char *message)
const char *message)
{ {
if (msg_id == FSCK_MSG_GITMODULES_MISSING) { if (msg_id == FSCK_MSG_GITMODULES_MISSING) {
puts(oid_to_hex(oid)); struct fsck_object_report *report = fsck_report;
puts(oid_to_hex(report->oid));
return 0; return 0;
} }
return fsck_error_function(o, oid, object_type, msg_type, msg_id, message); return fsck_objects_error_function(o, fsck_report,
msg_type, msg_id, message);
} }

76
fsck.h
View File

@ -31,6 +31,8 @@ enum fsck_msg_type {
FUNC(BAD_NAME, ERROR) \ FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \ FUNC(BAD_OBJECT_SHA1, ERROR) \
FUNC(BAD_PARENT_SHA1, ERROR) \ FUNC(BAD_PARENT_SHA1, ERROR) \
FUNC(BAD_REF_FILETYPE, ERROR) \
FUNC(BAD_REF_NAME, ERROR) \
FUNC(BAD_TIMEZONE, ERROR) \ FUNC(BAD_TIMEZONE, ERROR) \
FUNC(BAD_TREE, ERROR) \ FUNC(BAD_TREE, ERROR) \
FUNC(BAD_TREE_SHA1, ERROR) \ FUNC(BAD_TREE_SHA1, ERROR) \
@ -114,29 +116,49 @@ int is_valid_msg_type(const char *msg_id, const char *msg_type);
typedef int (*fsck_walk_func)(struct object *obj, enum object_type object_type, typedef int (*fsck_walk_func)(struct object *obj, enum object_type object_type,
void *data, struct fsck_options *options); void *data, struct fsck_options *options);
/* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */ /*
* Callback for reporting errors either for objects or refs. The "fsck_report"
* is a generic pointer that can be used to pass any information.
*/
typedef int (*fsck_error)(struct fsck_options *o, typedef int (*fsck_error)(struct fsck_options *o,
const struct object_id *oid, enum object_type object_type, void *fsck_report,
enum fsck_msg_type msg_type, enum fsck_msg_id msg_id, enum fsck_msg_type msg_type, enum fsck_msg_id msg_id,
const char *message); const char *message);
int fsck_error_function(struct fsck_options *o, int fsck_objects_error_function(struct fsck_options *o,
const struct object_id *oid, enum object_type object_type, void *fsck_report,
enum fsck_msg_type msg_type, enum fsck_msg_id msg_id, enum fsck_msg_type msg_type, enum fsck_msg_id msg_id,
const char *message); const char *message);
int fsck_error_cb_print_missing_gitmodules(struct fsck_options *o, int fsck_objects_error_cb_print_missing_gitmodules(struct fsck_options *o,
const struct object_id *oid, void *fsck_report,
enum object_type object_type, enum fsck_msg_type msg_type,
enum fsck_msg_type msg_type, enum fsck_msg_id msg_id,
enum fsck_msg_id msg_id, const char *message);
const char *message);
int fsck_refs_error_function(struct fsck_options *options,
void *fsck_report,
enum fsck_msg_type msg_type,
enum fsck_msg_id msg_id,
const char *message);
struct fsck_object_report {
const struct object_id *oid;
enum object_type object_type;
};
struct fsck_ref_report {
const char *path;
const struct object_id *oid;
const char *referent;
};
struct fsck_options { struct fsck_options {
fsck_walk_func walk; fsck_walk_func walk;
fsck_error error_func; fsck_error error_func;
unsigned strict:1; unsigned strict;
unsigned verbose;
enum fsck_msg_type *msg_type; enum fsck_msg_type *msg_type;
struct oidset skiplist; struct oidset skip_oids;
struct oidset gitmodules_found; struct oidset gitmodules_found;
struct oidset gitmodules_done; struct oidset gitmodules_done;
struct oidset gitattributes_found; struct oidset gitattributes_found;
@ -145,12 +167,12 @@ struct fsck_options {
}; };
#define FSCK_OPTIONS_DEFAULT { \ #define FSCK_OPTIONS_DEFAULT { \
.skiplist = OIDSET_INIT, \ .skip_oids = OIDSET_INIT, \
.gitmodules_found = OIDSET_INIT, \ .gitmodules_found = OIDSET_INIT, \
.gitmodules_done = OIDSET_INIT, \ .gitmodules_done = OIDSET_INIT, \
.gitattributes_found = OIDSET_INIT, \ .gitattributes_found = OIDSET_INIT, \
.gitattributes_done = OIDSET_INIT, \ .gitattributes_done = OIDSET_INIT, \
.error_func = fsck_error_function \ .error_func = fsck_objects_error_function \
} }
#define FSCK_OPTIONS_STRICT { \ #define FSCK_OPTIONS_STRICT { \
.strict = 1, \ .strict = 1, \
@ -158,7 +180,7 @@ struct fsck_options {
.gitmodules_done = OIDSET_INIT, \ .gitmodules_done = OIDSET_INIT, \
.gitattributes_found = OIDSET_INIT, \ .gitattributes_found = OIDSET_INIT, \
.gitattributes_done = OIDSET_INIT, \ .gitattributes_done = OIDSET_INIT, \
.error_func = fsck_error_function, \ .error_func = fsck_objects_error_function, \
} }
#define FSCK_OPTIONS_MISSING_GITMODULES { \ #define FSCK_OPTIONS_MISSING_GITMODULES { \
.strict = 1, \ .strict = 1, \
@ -166,7 +188,10 @@ struct fsck_options {
.gitmodules_done = OIDSET_INIT, \ .gitmodules_done = OIDSET_INIT, \
.gitattributes_found = OIDSET_INIT, \ .gitattributes_found = OIDSET_INIT, \
.gitattributes_done = OIDSET_INIT, \ .gitattributes_done = OIDSET_INIT, \
.error_func = fsck_error_cb_print_missing_gitmodules, \ .error_func = fsck_objects_error_cb_print_missing_gitmodules, \
}
#define FSCK_REFS_OPTIONS_DEFAULT { \
.error_func = fsck_refs_error_function, \
} }
/* descend in all linked child objects /* descend in all linked child objects
@ -209,6 +234,21 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
*/ */
int fsck_finish(struct fsck_options *options); int fsck_finish(struct fsck_options *options);
/*
* Clear the fsck_options struct, freeing any allocated memory.
*/
void fsck_options_clear(struct fsck_options *options);
/*
* Report an error or warning for refs.
*/
__attribute__((format (printf, 4, 5)))
int fsck_report_ref(struct fsck_options *options,
struct fsck_ref_report *report,
enum fsck_msg_id msg_id,
const char *fmt, ...);
/* /*
* Subsystem for storing human-readable names for each object. * Subsystem for storing human-readable names for each object.
* *

View File

@ -2470,11 +2470,10 @@ int repo_has_object_file(struct repository *r,
* give more context. * give more context.
*/ */
static int hash_format_check_report(struct fsck_options *opts UNUSED, static int hash_format_check_report(struct fsck_options *opts UNUSED,
const struct object_id *oid UNUSED, void *fsck_report UNUSED,
enum object_type object_type UNUSED, enum fsck_msg_type msg_type UNUSED,
enum fsck_msg_type msg_type UNUSED, enum fsck_msg_id msg_id UNUSED,
enum fsck_msg_id msg_id UNUSED, const char *message)
const char *message)
{ {
error(_("object fails fsck: %s"), message); error(_("object fails fsck: %s"), message);
return 1; return 1;

5
refs.c
View File

@ -316,6 +316,11 @@ int check_refname_format(const char *refname, int flags)
return check_or_sanitize_refname(refname, flags, NULL); return check_or_sanitize_refname(refname, flags, NULL);
} }
int refs_fsck(struct ref_store *refs, struct fsck_options *o)
{
return refs->be->fsck(refs, o);
}
void sanitize_refname_component(const char *refname, struct strbuf *out) void sanitize_refname_component(const char *refname, struct strbuf *out)
{ {
if (check_or_sanitize_refname(refname, REFNAME_ALLOW_ONELEVEL, out)) if (check_or_sanitize_refname(refname, REFNAME_ALLOW_ONELEVEL, out))

8
refs.h
View File

@ -4,6 +4,7 @@
#include "commit.h" #include "commit.h"
#include "repository.h" #include "repository.h"
struct fsck_options;
struct object_id; struct object_id;
struct ref_store; struct ref_store;
struct strbuf; struct strbuf;
@ -541,6 +542,13 @@ int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_dat
*/ */
int check_refname_format(const char *refname, int flags); int check_refname_format(const char *refname, int flags);
/*
* Check the reference database for consistency. Return 0 if refs and
* reflogs are consistent, and non-zero otherwise. The errors will be
* written to stderr.
*/
int refs_fsck(struct ref_store *refs, struct fsck_options *o);
/* /*
* Apply the rules from check_refname_format, but mutate the result until it * Apply the rules from check_refname_format, but mutate the result until it
* is acceptable, and place the result in "out". * is acceptable, and place the result in "out".

View File

@ -419,6 +419,15 @@ static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
return res; return res;
} }
static int debug_fsck(struct ref_store *ref_store,
struct fsck_options *o)
{
struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
int res = drefs->refs->be->fsck(drefs->refs, o);
trace_printf_key(&trace_refs, "fsck: %d\n", res);
return res;
}
struct ref_storage_be refs_be_debug = { struct ref_storage_be refs_be_debug = {
.name = "debug", .name = "debug",
.init = NULL, .init = NULL,
@ -451,4 +460,6 @@ struct ref_storage_be refs_be_debug = {
.create_reflog = debug_create_reflog, .create_reflog = debug_create_reflog,
.delete_reflog = debug_delete_reflog, .delete_reflog = debug_delete_reflog,
.reflog_expire = debug_reflog_expire, .reflog_expire = debug_reflog_expire,
.fsck = debug_fsck,
}; };

View File

@ -4,6 +4,7 @@
#include "../gettext.h" #include "../gettext.h"
#include "../hash.h" #include "../hash.h"
#include "../hex.h" #include "../hex.h"
#include "../fsck.h"
#include "../refs.h" #include "../refs.h"
#include "refs-internal.h" #include "refs-internal.h"
#include "ref-cache.h" #include "ref-cache.h"
@ -3419,6 +3420,116 @@ static int files_ref_store_remove_on_disk(struct ref_store *ref_store,
return ret; return ret;
} }
/*
* For refs and reflogs, they share a unified interface when scanning
* the whole directory. This function is used as the callback for each
* regular file or symlink in the directory.
*/
typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
struct fsck_options *o,
const char *refs_check_dir,
struct dir_iterator *iter);
static int files_fsck_refs_name(struct ref_store *ref_store UNUSED,
struct fsck_options *o,
const char *refs_check_dir,
struct dir_iterator *iter)
{
struct strbuf sb = STRBUF_INIT;
int ret = 0;
/*
* Ignore the files ending with ".lock" as they may be lock files
* However, do not allow bare ".lock" files.
*/
if (iter->basename[0] != '.' && ends_with(iter->basename, ".lock"))
goto cleanup;
if (check_refname_format(iter->basename, REFNAME_ALLOW_ONELEVEL)) {
struct fsck_ref_report report = { .path = NULL };
strbuf_addf(&sb, "%s/%s", refs_check_dir, iter->relative_path);
report.path = sb.buf;
ret = fsck_report_ref(o, &report,
FSCK_MSG_BAD_REF_NAME,
"invalid refname format");
}
cleanup:
strbuf_release(&sb);
return ret;
}
static int files_fsck_refs_dir(struct ref_store *ref_store,
struct fsck_options *o,
const char *refs_check_dir,
files_fsck_refs_fn *fsck_refs_fn)
{
struct strbuf sb = STRBUF_INIT;
struct dir_iterator *iter;
int iter_status;
int ret = 0;
strbuf_addf(&sb, "%s/%s", ref_store->gitdir, refs_check_dir);
iter = dir_iterator_begin(sb.buf, 0);
if (!iter) {
ret = error_errno(_("cannot open directory %s"), sb.buf);
goto out;
}
while ((iter_status = dir_iterator_advance(iter)) == ITER_OK) {
if (S_ISDIR(iter->st.st_mode)) {
continue;
} else if (S_ISREG(iter->st.st_mode) ||
S_ISLNK(iter->st.st_mode)) {
if (o->verbose)
fprintf_ln(stderr, "Checking %s/%s",
refs_check_dir, iter->relative_path);
for (size_t i = 0; fsck_refs_fn[i]; i++) {
if (fsck_refs_fn[i](ref_store, o, refs_check_dir, iter))
ret = -1;
}
} else {
struct fsck_ref_report report = { .path = iter->basename };
if (fsck_report_ref(o, &report,
FSCK_MSG_BAD_REF_FILETYPE,
"unexpected file type"))
ret = -1;
}
}
if (iter_status != ITER_DONE)
ret = error(_("failed to iterate over '%s'"), sb.buf);
out:
strbuf_release(&sb);
return ret;
}
static int files_fsck_refs(struct ref_store *ref_store,
struct fsck_options *o)
{
files_fsck_refs_fn fsck_refs_fn[]= {
files_fsck_refs_name,
NULL,
};
if (o->verbose)
fprintf_ln(stderr, _("Checking references consistency"));
return files_fsck_refs_dir(ref_store, o, "refs", fsck_refs_fn);
}
static int files_fsck(struct ref_store *ref_store,
struct fsck_options *o)
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_READ, "fsck");
return files_fsck_refs(ref_store, o) |
refs->packed_ref_store->be->fsck(refs->packed_ref_store, o);
}
struct ref_storage_be refs_be_files = { struct ref_storage_be refs_be_files = {
.name = "files", .name = "files",
.init = files_ref_store_init, .init = files_ref_store_init,
@ -3445,5 +3556,7 @@ struct ref_storage_be refs_be_files = {
.reflog_exists = files_reflog_exists, .reflog_exists = files_reflog_exists,
.create_reflog = files_create_reflog, .create_reflog = files_create_reflog,
.delete_reflog = files_delete_reflog, .delete_reflog = files_delete_reflog,
.reflog_expire = files_reflog_expire .reflog_expire = files_reflog_expire,
.fsck = files_fsck,
}; };

View File

@ -1733,6 +1733,12 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
return empty_ref_iterator_begin(); return empty_ref_iterator_begin();
} }
static int packed_fsck(struct ref_store *ref_store,
struct fsck_options *o)
{
return 0;
}
struct ref_storage_be refs_be_packed = { struct ref_storage_be refs_be_packed = {
.name = "packed", .name = "packed",
.init = packed_ref_store_init, .init = packed_ref_store_init,
@ -1760,4 +1766,6 @@ struct ref_storage_be refs_be_packed = {
.create_reflog = NULL, .create_reflog = NULL,
.delete_reflog = NULL, .delete_reflog = NULL,
.reflog_expire = NULL, .reflog_expire = NULL,
.fsck = packed_fsck,
}; };

View File

@ -4,6 +4,7 @@
#include "refs.h" #include "refs.h"
#include "iterator.h" #include "iterator.h"
struct fsck_options;
struct ref_transaction; struct ref_transaction;
/* /*
@ -651,6 +652,9 @@ typedef int read_raw_ref_fn(struct ref_store *ref_store, const char *refname,
typedef int read_symbolic_ref_fn(struct ref_store *ref_store, const char *refname, typedef int read_symbolic_ref_fn(struct ref_store *ref_store, const char *refname,
struct strbuf *referent); struct strbuf *referent);
typedef int fsck_fn(struct ref_store *ref_store,
struct fsck_options *o);
struct ref_storage_be { struct ref_storage_be {
const char *name; const char *name;
ref_store_init_fn *init; ref_store_init_fn *init;
@ -678,6 +682,8 @@ struct ref_storage_be {
create_reflog_fn *create_reflog; create_reflog_fn *create_reflog;
delete_reflog_fn *delete_reflog; delete_reflog_fn *delete_reflog;
reflog_expire_fn *reflog_expire; reflog_expire_fn *reflog_expire;
fsck_fn *fsck;
}; };
extern struct ref_storage_be refs_be_files; extern struct ref_storage_be refs_be_files;

View File

@ -2309,6 +2309,12 @@ done:
return ret; return ret;
} }
static int reftable_be_fsck(struct ref_store *ref_store,
struct fsck_options *o)
{
return 0;
}
struct ref_storage_be refs_be_reftable = { struct ref_storage_be refs_be_reftable = {
.name = "reftable", .name = "reftable",
.init = reftable_be_init, .init = reftable_be_init,
@ -2336,4 +2342,6 @@ struct ref_storage_be refs_be_reftable = {
.create_reflog = reftable_be_create_reflog, .create_reflog = reftable_be_create_reflog,
.delete_reflog = reftable_be_delete_reflog, .delete_reflog = reftable_be_delete_reflog,
.reflog_expire = reftable_be_reflog_expire, .reflog_expire = reftable_be_reflog_expire,
.fsck = reftable_be_fsck,
}; };

92
t/t0602-reffiles-fsck.sh Executable file
View File

@ -0,0 +1,92 @@
#!/bin/sh
test_description='Test reffiles backend consistency check'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
GIT_TEST_DEFAULT_REF_FORMAT=files
export GIT_TEST_DEFAULT_REF_FORMAT
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'ref name should be checked' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
cd repo &&
git commit --allow-empty -m initial &&
git checkout -b branch-1 &&
git tag tag-1 &&
git commit --allow-empty -m second &&
git checkout -b branch-2 &&
git tag tag-2 &&
git tag multi_hierarchy/tag-2 &&
cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
test_must_fail git refs verify 2>err &&
cat >expect <<-EOF &&
error: refs/heads/.branch-1: badRefName: invalid refname format
EOF
rm $branch_dir_prefix/.branch-1 &&
test_cmp expect err &&
cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
test_must_fail git refs verify 2>err &&
cat >expect <<-EOF &&
error: refs/heads/@: badRefName: invalid refname format
EOF
rm $branch_dir_prefix/@ &&
test_cmp expect err &&
cp $tag_dir_prefix/multi_hierarchy/tag-2 $tag_dir_prefix/multi_hierarchy/@ &&
test_must_fail git refs verify 2>err &&
cat >expect <<-EOF &&
error: refs/tags/multi_hierarchy/@: badRefName: invalid refname format
EOF
rm $tag_dir_prefix/multi_hierarchy/@ &&
test_cmp expect err &&
cp $tag_dir_prefix/tag-1 $tag_dir_prefix/tag-1.lock &&
git refs verify 2>err &&
rm $tag_dir_prefix/tag-1.lock &&
test_must_be_empty err &&
cp $tag_dir_prefix/tag-1 $tag_dir_prefix/.lock &&
test_must_fail git refs verify 2>err &&
cat >expect <<-EOF &&
error: refs/tags/.lock: badRefName: invalid refname format
EOF
rm $tag_dir_prefix/.lock &&
test_cmp expect err
'
test_expect_success 'ref name check should be adapted into fsck messages' '
test_when_finished "rm -rf repo" &&
git init repo &&
branch_dir_prefix=.git/refs/heads &&
tag_dir_prefix=.git/refs/tags &&
cd repo &&
git commit --allow-empty -m initial &&
git checkout -b branch-1 &&
git tag tag-1 &&
git commit --allow-empty -m second &&
git checkout -b branch-2 &&
git tag tag-2 &&
cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 &&
git -c fsck.badRefName=warn refs verify 2>err &&
cat >expect <<-EOF &&
warning: refs/heads/.branch-1: badRefName: invalid refname format
EOF
rm $branch_dir_prefix/.branch-1 &&
test_cmp expect err &&
cp $branch_dir_prefix/branch-1 $branch_dir_prefix/@ &&
git -c fsck.badRefName=ignore refs verify 2>err &&
test_must_be_empty err
'
test_done