aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/fsck-msgids.txt6
-rw-r--r--Documentation/git-refs.txt13
-rw-r--r--builtin/fsck.c17
-rw-r--r--builtin/mktag.c3
-rw-r--r--builtin/refs.c34
-rw-r--r--fsck.c125
-rw-r--r--fsck.h76
-rw-r--r--object-file.c9
-rw-r--r--refs.c5
-rw-r--r--refs.h8
-rw-r--r--refs/debug.c11
-rw-r--r--refs/files-backend.c115
-rw-r--r--refs/packed-backend.c8
-rw-r--r--refs/refs-internal.h6
-rw-r--r--refs/reftable-backend.c8
-rwxr-xr-xt/t0602-reffiles-fsck.sh92
16 files changed, 477 insertions, 59 deletions
diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt
index f643585a34..68a2801f15 100644
--- a/Documentation/fsck-msgids.txt
+++ b/Documentation/fsck-msgids.txt
@@ -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.
diff --git a/Documentation/git-refs.txt b/Documentation/git-refs.txt
index 5b99e04385..ce31f93061 100644
--- a/Documentation/git-refs.txt
+++ b/Documentation/git-refs.txt
@@ -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
-----------------
diff --git a/builtin/fsck.c b/builtin/fsck.c
index ef6ee1268b..ef014bbbcd 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -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;
diff --git a/builtin/mktag.c b/builtin/mktag.c
index 4767f1a97e..c6b644219f 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -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)
diff --git a/builtin/refs.c b/builtin/refs.c
index 46dcd150d4..131f98be98 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -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(),
};
diff --git a/fsck.c b/fsck.c
index eea7145470..3756f52459 100644
--- a/fsck.c
+++ b/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);
}
diff --git a/fsck.h b/fsck.h
index 6085a384f6..500b4c04d2 100644
--- a/fsck.h
+++ b/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
@@ -210,6 +235,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.
*
* If fsck_enable_object_names() has not been called, all other functions are
diff --git a/object-file.c b/object-file.c
index 065103be3e..05ac6ebed6 100644
--- a/object-file.c
+++ b/object-file.c
@@ -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;
diff --git a/refs.c b/refs.c
index 0d9ba93cf6..74de3d3009 100644
--- a/refs.c
+++ b/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))
diff --git a/refs.h b/refs.h
index 6599ffb94b..f8b919a138 100644
--- a/refs.h
+++ b/refs.h
@@ -4,6 +4,7 @@
#include "commit.h"
#include "repository.h"
+struct fsck_options;
struct object_id;
struct ref_store;
struct strbuf;
@@ -542,6 +543,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".
*/
diff --git a/refs/debug.c b/refs/debug.c
index 547d9245b9..45e2e784a0 100644
--- a/refs/debug.c
+++ b/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,
};
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 68a3f71f70..8d6ec9458d 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -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,
};
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 89976aa359..f00106df14 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -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,
};
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 390a2ab001..2313c830d8 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -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;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index db2ae1dfcb..8b7ffbf66f 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -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,
};
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh
new file mode 100755
index 0000000000..71a4d1a5ae
--- /dev/null
+++ b/t/t0602-reffiles-fsck.sh
@@ -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