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
commit b3d175409d
16 changed files with 477 additions and 59 deletions

View file

@ -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.

View file

@ -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
-----------------

View file

@ -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;

View file

@ -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)

View file

@ -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 @@ static int cmd_refs_migrate(int argc, const char **argv, const char *prefix)
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
View file

@ -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
View file

@ -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.
*

View file

@ -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
View file

@ -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
View file

@ -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".

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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;

View file

@ -2309,6 +2309,12 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
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,
};

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