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"maint
commit
b3d175409d
|
@ -19,6 +19,12 @@
|
|||
`badParentSha1`::
|
||||
(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`::
|
||||
(INFO) A tag has an invalid format.
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ SYNOPSIS
|
|||
--------
|
||||
[verse]
|
||||
'git refs migrate' --ref-format=<format> [--dry-run]
|
||||
'git refs verify' [--strict] [--verbose]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
@ -22,6 +23,9 @@ COMMANDS
|
|||
migrate::
|
||||
Migrate ref store between different formats.
|
||||
|
||||
verify::
|
||||
Verify reference database consistency.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
|
||||
|
@ -39,6 +43,15 @@ include::ref-storage-format.txt[]
|
|||
can be used to double check that the migration works as expected before
|
||||
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
|
||||
-----------------
|
||||
|
||||
|
|
|
@ -89,13 +89,16 @@ static int objerror(struct object *obj, const char *err)
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int fsck_error_func(struct fsck_options *o UNUSED,
|
||||
const struct object_id *oid,
|
||||
enum object_type object_type,
|
||||
enum fsck_msg_type msg_type,
|
||||
enum fsck_msg_id msg_id UNUSED,
|
||||
const char *message)
|
||||
static int fsck_objects_error_func(struct fsck_options *o UNUSED,
|
||||
void *fsck_report,
|
||||
enum fsck_msg_type msg_type,
|
||||
enum fsck_msg_id msg_id UNUSED,
|
||||
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) {
|
||||
case FSCK_WARN:
|
||||
/* 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_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)
|
||||
fsck_obj_options.strict = 1;
|
||||
|
||||
|
|
|
@ -18,8 +18,7 @@ static int option_strict = 1;
|
|||
static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
|
||||
|
||||
static int mktag_fsck_error_func(struct fsck_options *o UNUSED,
|
||||
const struct object_id *oid UNUSED,
|
||||
enum object_type object_type UNUSED,
|
||||
void *fsck_report UNUSED,
|
||||
enum fsck_msg_type msg_type,
|
||||
enum fsck_msg_id msg_id UNUSED,
|
||||
const char *message)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include "builtin.h"
|
||||
#include "config.h"
|
||||
#include "fsck.h"
|
||||
#include "parse-options.h"
|
||||
#include "refs.h"
|
||||
#include "repository.h"
|
||||
|
@ -7,6 +9,9 @@
|
|||
#define REFS_MIGRATE_USAGE \
|
||||
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)
|
||||
{
|
||||
const char * const migrate_usage[] = {
|
||||
|
@ -58,15 +63,44 @@ out:
|
|||
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)
|
||||
{
|
||||
const char * const refs_usage[] = {
|
||||
REFS_MIGRATE_USAGE,
|
||||
REFS_VERIFY_USAGE,
|
||||
NULL,
|
||||
};
|
||||
parse_opt_subcommand_fn *fn = NULL;
|
||||
struct option opts[] = {
|
||||
OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
|
||||
OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
|
||||
OPT_END(),
|
||||
};
|
||||
|
||||
|
|
125
fsck.c
125
fsck.c
|
@ -205,7 +205,7 @@ void fsck_set_msg_types(struct fsck_options *options, const char *values)
|
|||
if (!strcmp(buf, "skiplist")) {
|
||||
if (equal == len)
|
||||
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);
|
||||
buf += len + 1;
|
||||
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,
|
||||
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,
|
||||
const struct object_id *oid, enum object_type object_type,
|
||||
enum fsck_msg_id msg_id, const char *fmt, ...)
|
||||
/*
|
||||
* Provide the common functionality for either fscking refs or objects.
|
||||
* It will get the current msg error type and call the error_func callback
|
||||
* 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;
|
||||
enum fsck_msg_type msg_type = fsck_msg_type(msg_id, options);
|
||||
int result;
|
||||
|
@ -239,9 +242,6 @@ static int report(struct fsck_options *options,
|
|||
if (msg_type == FSCK_IGNORE)
|
||||
return 0;
|
||||
|
||||
if (object_on_skiplist(options, oid))
|
||||
return 0;
|
||||
|
||||
if (msg_type == FSCK_FATAL)
|
||||
msg_type = FSCK_ERROR;
|
||||
else if (msg_type == FSCK_INFO)
|
||||
|
@ -250,16 +250,49 @@ static int report(struct fsck_options *options,
|
|||
prepare_msg_ids();
|
||||
strbuf_addf(&sb, "%s: ", msg_id_info[msg_id].camelcased);
|
||||
|
||||
va_start(ap, fmt);
|
||||
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);
|
||||
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);
|
||||
|
||||
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)
|
||||
{
|
||||
if (!options->object_names)
|
||||
|
@ -1200,13 +1233,15 @@ int fsck_buffer(const struct object_id *oid, enum object_type type,
|
|||
type);
|
||||
}
|
||||
|
||||
int fsck_error_function(struct fsck_options *o,
|
||||
const struct object_id *oid,
|
||||
enum object_type object_type UNUSED,
|
||||
enum fsck_msg_type msg_type,
|
||||
enum fsck_msg_id msg_id UNUSED,
|
||||
const char *message)
|
||||
int fsck_objects_error_function(struct fsck_options *o,
|
||||
void *fsck_report,
|
||||
enum fsck_msg_type msg_type,
|
||||
enum fsck_msg_id msg_id UNUSED,
|
||||
const char *message)
|
||||
{
|
||||
struct fsck_object_report *report = fsck_report;
|
||||
const struct object_id *oid = report->oid;
|
||||
|
||||
if (msg_type == FSCK_WARN) {
|
||||
warning("object %s: %s", fsck_describe_object(o, oid), message);
|
||||
return 0;
|
||||
|
@ -1215,6 +1250,32 @@ int fsck_error_function(struct fsck_options *o,
|
|||
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,
|
||||
enum fsck_msg_id msg_missing, enum fsck_msg_id msg_type,
|
||||
struct fsck_options *options, const char *blob_type)
|
||||
|
@ -1270,6 +1331,17 @@ int fsck_finish(struct fsck_options *options)
|
|||
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,
|
||||
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.
|
||||
*/
|
||||
|
||||
int fsck_error_cb_print_missing_gitmodules(struct fsck_options *o,
|
||||
const struct object_id *oid,
|
||||
enum object_type object_type,
|
||||
enum fsck_msg_type msg_type,
|
||||
enum fsck_msg_id msg_id,
|
||||
const char *message)
|
||||
int fsck_objects_error_cb_print_missing_gitmodules(struct fsck_options *o,
|
||||
void *fsck_report,
|
||||
enum fsck_msg_type msg_type,
|
||||
enum fsck_msg_id msg_id,
|
||||
const char *message)
|
||||
{
|
||||
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 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
76
fsck.h
|
@ -31,6 +31,8 @@ enum fsck_msg_type {
|
|||
FUNC(BAD_NAME, ERROR) \
|
||||
FUNC(BAD_OBJECT_SHA1, ERROR) \
|
||||
FUNC(BAD_PARENT_SHA1, ERROR) \
|
||||
FUNC(BAD_REF_FILETYPE, ERROR) \
|
||||
FUNC(BAD_REF_NAME, ERROR) \
|
||||
FUNC(BAD_TIMEZONE, ERROR) \
|
||||
FUNC(BAD_TREE, 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,
|
||||
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,
|
||||
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,
|
||||
const char *message);
|
||||
|
||||
int fsck_error_function(struct fsck_options *o,
|
||||
const struct object_id *oid, enum object_type object_type,
|
||||
enum fsck_msg_type msg_type, enum fsck_msg_id msg_id,
|
||||
const char *message);
|
||||
int fsck_error_cb_print_missing_gitmodules(struct fsck_options *o,
|
||||
const struct object_id *oid,
|
||||
enum object_type object_type,
|
||||
enum fsck_msg_type msg_type,
|
||||
enum fsck_msg_id msg_id,
|
||||
const char *message);
|
||||
int fsck_objects_error_function(struct fsck_options *o,
|
||||
void *fsck_report,
|
||||
enum fsck_msg_type msg_type, enum fsck_msg_id msg_id,
|
||||
const char *message);
|
||||
int fsck_objects_error_cb_print_missing_gitmodules(struct fsck_options *o,
|
||||
void *fsck_report,
|
||||
enum fsck_msg_type msg_type,
|
||||
enum fsck_msg_id msg_id,
|
||||
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 {
|
||||
fsck_walk_func walk;
|
||||
fsck_error error_func;
|
||||
unsigned strict:1;
|
||||
unsigned strict;
|
||||
unsigned verbose;
|
||||
enum fsck_msg_type *msg_type;
|
||||
struct oidset skiplist;
|
||||
struct oidset skip_oids;
|
||||
struct oidset gitmodules_found;
|
||||
struct oidset gitmodules_done;
|
||||
struct oidset gitattributes_found;
|
||||
|
@ -145,12 +167,12 @@ struct fsck_options {
|
|||
};
|
||||
|
||||
#define FSCK_OPTIONS_DEFAULT { \
|
||||
.skiplist = OIDSET_INIT, \
|
||||
.skip_oids = OIDSET_INIT, \
|
||||
.gitmodules_found = OIDSET_INIT, \
|
||||
.gitmodules_done = OIDSET_INIT, \
|
||||
.gitattributes_found = OIDSET_INIT, \
|
||||
.gitattributes_done = OIDSET_INIT, \
|
||||
.error_func = fsck_error_function \
|
||||
.error_func = fsck_objects_error_function \
|
||||
}
|
||||
#define FSCK_OPTIONS_STRICT { \
|
||||
.strict = 1, \
|
||||
|
@ -158,7 +180,7 @@ struct fsck_options {
|
|||
.gitmodules_done = OIDSET_INIT, \
|
||||
.gitattributes_found = OIDSET_INIT, \
|
||||
.gitattributes_done = OIDSET_INIT, \
|
||||
.error_func = fsck_error_function, \
|
||||
.error_func = fsck_objects_error_function, \
|
||||
}
|
||||
#define FSCK_OPTIONS_MISSING_GITMODULES { \
|
||||
.strict = 1, \
|
||||
|
@ -166,7 +188,10 @@ struct fsck_options {
|
|||
.gitmodules_done = OIDSET_INIT, \
|
||||
.gitattributes_found = 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
|
||||
|
@ -209,6 +234,21 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -2470,11 +2470,10 @@ int repo_has_object_file(struct repository *r,
|
|||
* give more context.
|
||||
*/
|
||||
static int hash_format_check_report(struct fsck_options *opts UNUSED,
|
||||
const struct object_id *oid UNUSED,
|
||||
enum object_type object_type UNUSED,
|
||||
enum fsck_msg_type msg_type UNUSED,
|
||||
enum fsck_msg_id msg_id UNUSED,
|
||||
const char *message)
|
||||
void *fsck_report UNUSED,
|
||||
enum fsck_msg_type msg_type UNUSED,
|
||||
enum fsck_msg_id msg_id UNUSED,
|
||||
const char *message)
|
||||
{
|
||||
error(_("object fails fsck: %s"), message);
|
||||
return 1;
|
||||
|
|
5
refs.c
5
refs.c
|
@ -316,6 +316,11 @@ int check_refname_format(const char *refname, int flags)
|
|||
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)
|
||||
{
|
||||
if (check_or_sanitize_refname(refname, REFNAME_ALLOW_ONELEVEL, out))
|
||||
|
|
8
refs.h
8
refs.h
|
@ -4,6 +4,7 @@
|
|||
#include "commit.h"
|
||||
#include "repository.h"
|
||||
|
||||
struct fsck_options;
|
||||
struct object_id;
|
||||
struct ref_store;
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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
|
||||
* is acceptable, and place the result in "out".
|
||||
|
|
11
refs/debug.c
11
refs/debug.c
|
@ -419,6 +419,15 @@ static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
|
|||
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 = {
|
||||
.name = "debug",
|
||||
.init = NULL,
|
||||
|
@ -451,4 +460,6 @@ struct ref_storage_be refs_be_debug = {
|
|||
.create_reflog = debug_create_reflog,
|
||||
.delete_reflog = debug_delete_reflog,
|
||||
.reflog_expire = debug_reflog_expire,
|
||||
|
||||
.fsck = debug_fsck,
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "../gettext.h"
|
||||
#include "../hash.h"
|
||||
#include "../hex.h"
|
||||
#include "../fsck.h"
|
||||
#include "../refs.h"
|
||||
#include "refs-internal.h"
|
||||
#include "ref-cache.h"
|
||||
|
@ -3419,6 +3420,116 @@ static int files_ref_store_remove_on_disk(struct ref_store *ref_store,
|
|||
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 = {
|
||||
.name = "files",
|
||||
.init = files_ref_store_init,
|
||||
|
@ -3445,5 +3556,7 @@ struct ref_storage_be refs_be_files = {
|
|||
.reflog_exists = files_reflog_exists,
|
||||
.create_reflog = files_create_reflog,
|
||||
.delete_reflog = files_delete_reflog,
|
||||
.reflog_expire = files_reflog_expire
|
||||
.reflog_expire = files_reflog_expire,
|
||||
|
||||
.fsck = files_fsck,
|
||||
};
|
||||
|
|
|
@ -1733,6 +1733,12 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
|
|||
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 = {
|
||||
.name = "packed",
|
||||
.init = packed_ref_store_init,
|
||||
|
@ -1760,4 +1766,6 @@ struct ref_storage_be refs_be_packed = {
|
|||
.create_reflog = NULL,
|
||||
.delete_reflog = NULL,
|
||||
.reflog_expire = NULL,
|
||||
|
||||
.fsck = packed_fsck,
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "refs.h"
|
||||
#include "iterator.h"
|
||||
|
||||
struct fsck_options;
|
||||
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,
|
||||
struct strbuf *referent);
|
||||
|
||||
typedef int fsck_fn(struct ref_store *ref_store,
|
||||
struct fsck_options *o);
|
||||
|
||||
struct ref_storage_be {
|
||||
const char *name;
|
||||
ref_store_init_fn *init;
|
||||
|
@ -678,6 +682,8 @@ struct ref_storage_be {
|
|||
create_reflog_fn *create_reflog;
|
||||
delete_reflog_fn *delete_reflog;
|
||||
reflog_expire_fn *reflog_expire;
|
||||
|
||||
fsck_fn *fsck;
|
||||
};
|
||||
|
||||
extern struct ref_storage_be refs_be_files;
|
||||
|
|
|
@ -2309,6 +2309,12 @@ done:
|
|||
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 = {
|
||||
.name = "reftable",
|
||||
.init = reftable_be_init,
|
||||
|
@ -2336,4 +2342,6 @@ struct ref_storage_be refs_be_reftable = {
|
|||
.create_reflog = reftable_be_create_reflog,
|
||||
.delete_reflog = reftable_be_delete_reflog,
|
||||
.reflog_expire = reftable_be_reflog_expire,
|
||||
|
||||
.fsck = reftable_be_fsck,
|
||||
};
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue