diff options
author | Junio C Hamano <gitster@pobox.com> | 2015-08-03 11:01:18 -0700 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2015-08-03 11:01:18 -0700 |
commit | b2f44feba593ed0c4294a6f9933c8a1b6f190e04 (patch) | |
tree | 3b3d19bc7fd5883a4cdf6e0bbf8cf161a63eeab9 | |
parent | be9cb560e31c76c00760dadb151b5e3059970586 (diff) | |
parent | 1335f732892601368876e0e41c9ea288c2ab18f3 (diff) | |
download | git-b2f44feba593ed0c4294a6f9933c8a1b6f190e04.tar.gz git-b2f44feba593ed0c4294a6f9933c8a1b6f190e04.tar.xz |
Merge branch 'js/fsck-opt'
Allow ignoring fsck errors on specific set of known-to-be-bad
objects, and also tweaking warning level of various kinds of non
critical breakages reported.
* js/fsck-opt:
fsck: support ignoring objects in `git fsck` via fsck.skiplist
fsck: git receive-pack: support excluding objects from fsck'ing
fsck: introduce `git fsck --connectivity-only`
fsck: support demoting errors to warnings
fsck: document the new receive.fsck.<msg-id> options
fsck: allow upgrading fsck warnings to errors
fsck: optionally ignore specific fsck issues completely
fsck: disallow demoting grave fsck errors to warnings
fsck: add a simple test for receive.fsck.<msg-id>
fsck: make fsck_tag() warn-friendly
fsck: handle multiple authors in commits specially
fsck: make fsck_commit() warn-friendly
fsck: make fsck_ident() warn-friendly
fsck: report the ID of the error/warning
fsck (receive-pack): allow demoting errors to warnings
fsck: offer a function to demote fsck errors to warnings
fsck: provide a function to parse fsck message IDs
fsck: introduce identifiers for fsck messages
fsck: introduce fsck options
-rw-r--r-- | Documentation/config.txt | 41 | ||||
-rw-r--r-- | Documentation/git-fsck.txt | 7 | ||||
-rw-r--r-- | builtin/fsck.c | 78 | ||||
-rw-r--r-- | builtin/index-pack.c | 13 | ||||
-rw-r--r-- | builtin/receive-pack.c | 28 | ||||
-rw-r--r-- | builtin/unpack-objects.c | 16 | ||||
-rw-r--r-- | fsck.c | 555 | ||||
-rw-r--r-- | fsck.h | 30 | ||||
-rwxr-xr-x | t/t1450-fsck.sh | 37 | ||||
-rwxr-xr-x | t/t5302-pack-index.sh | 2 | ||||
-rwxr-xr-x | t/t5504-fetch-receive-strict.sh | 51 |
11 files changed, 693 insertions, 165 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 75c372238..9358f4c16 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1242,6 +1242,25 @@ filter.<driver>.smudge:: object to a worktree file upon checkout. See linkgit:gitattributes[5] for details. +fsck.<msg-id>:: + Allows overriding the message type (error, warn or ignore) of a + specific message ID such as `missingEmail`. ++ +For convenience, fsck prefixes the error/warning with the message ID, +e.g. "missingEmail: invalid author/committer line - missing email" means +that setting `fsck.missingEmail = ignore` will hide that issue. ++ +This feature is intended to support working with legacy repositories +which cannot be repaired without disruptive changes. + +fsck.skipList:: + The path to a sorted list of object names (i.e. one SHA-1 per + line) that are known to be broken in a non-fatal way and should + be ignored. This feature is useful when an established project + should be accepted despite early commits containing errors that + can be safely ignored such as invalid committer email addresses. + Note: corrupt objects cannot be skipped with this setting. + gc.aggressiveDepth:: The depth parameter used in the delta compression algorithm used by 'git gc --aggressive'. This defaults @@ -2202,6 +2221,28 @@ receive.fsckObjects:: Defaults to false. If not set, the value of `transfer.fsckObjects` is used instead. +receive.fsck.<msg-id>:: + When `receive.fsckObjects` is set to true, errors can be switched + to warnings and vice versa by configuring the `receive.fsck.<msg-id>` + setting where the `<msg-id>` is the fsck message ID and the value + is one of `error`, `warn` or `ignore`. For convenience, fsck prefixes + the error/warning with the message ID, e.g. "missingEmail: invalid + author/committer line - missing email" means that setting + `receive.fsck.missingEmail = ignore` will hide that issue. ++ +This feature is intended to support working with legacy repositories +which would not pass pushing when `receive.fsckObjects = true`, allowing +the host to accept repositories with certain known issues but still catch +other issues. + +receive.fsck.skipList:: + The path to a sorted list of object names (i.e. one SHA-1 per + line) that are known to be broken in a non-fatal way and should + be ignored. This feature is useful when an established project + should be accepted despite early commits containing errors that + can be safely ignored such as invalid committer email addresses. + Note: corrupt objects cannot be skipped with this setting. + receive.unpackLimit:: If the number of objects received in a push is below this limit then the objects will be unpacked into loose object diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 25c431d3c..84ee92e15 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] [--[no-]full] [--strict] [--verbose] [--lost-found] - [--[no-]dangling] [--[no-]progress] [<object>*] + [--[no-]dangling] [--[no-]progress] [--connectivity-only] [<object>*] DESCRIPTION ----------- @@ -60,6 +60,11 @@ index file, all SHA-1 references in `refs` namespace, and all reflogs object pools. This is now default; you can turn it off with --no-full. +--connectivity-only:: + Check only the connectivity of tags, commits and tree objects. By + avoiding to unpack blobs, this speeds up the operation, at the + expense of missing corrupt objects or other problematic issues. + --strict:: Enable more strict checking, namely to catch a file mode recorded with g+w bit set, which was created by older diff --git a/builtin/fsck.c b/builtin/fsck.c index 267979304..f4b87e9b3 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -23,8 +23,11 @@ static int show_tags; static int show_unreachable; static int include_reflogs = 1; static int check_full = 1; +static int connectivity_only; static int check_strict; static int keep_cache_objects; +static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT; +static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT; static struct object_id head_oid; static const char *head_points_at; static int errors_found; @@ -44,39 +47,52 @@ static int show_dangling = 1; #define DIRENT_SORT_HINT(de) ((de)->d_ino) #endif -static void objreport(struct object *obj, const char *severity, - const char *err, va_list params) +static int fsck_config(const char *var, const char *value, void *cb) { - fprintf(stderr, "%s in %s %s: ", - severity, typename(obj->type), sha1_to_hex(obj->sha1)); - vfprintf(stderr, err, params); - fputs("\n", stderr); + if (strcmp(var, "fsck.skiplist") == 0) { + const char *path; + struct strbuf sb = STRBUF_INIT; + + if (git_config_pathname(&path, var, value)) + return 1; + strbuf_addf(&sb, "skiplist=%s", path); + free((char *)path); + fsck_set_msg_types(&fsck_obj_options, sb.buf); + strbuf_release(&sb); + return 0; + } + + if (skip_prefix(var, "fsck.", &var)) { + fsck_set_msg_type(&fsck_obj_options, var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static void objreport(struct object *obj, const char *msg_type, + const char *err) +{ + fprintf(stderr, "%s in %s %s: %s\n", + msg_type, typename(obj->type), sha1_to_hex(obj->sha1), err); } -__attribute__((format (printf, 2, 3))) -static int objerror(struct object *obj, const char *err, ...) +static int objerror(struct object *obj, const char *err) { - va_list params; - va_start(params, err); errors_found |= ERROR_OBJECT; - objreport(obj, "error", err, params); - va_end(params); + objreport(obj, "error", err); return -1; } -__attribute__((format (printf, 3, 4))) -static int fsck_error_func(struct object *obj, int type, const char *err, ...) +static int fsck_error_func(struct object *obj, int type, const char *message) { - va_list params; - va_start(params, err); - objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params); - va_end(params); + objreport(obj, (type == FSCK_WARN) ? "warning" : "error", message); return (type == FSCK_WARN) ? 0 : 1; } static struct object_array pending; -static int mark_object(struct object *obj, int type, void *data) +static int mark_object(struct object *obj, int type, void *data, struct fsck_options *options) { struct object *parent = data; @@ -119,7 +135,7 @@ static int mark_object(struct object *obj, int type, void *data) static void mark_object_reachable(struct object *obj) { - mark_object(obj, OBJ_ANY, NULL); + mark_object(obj, OBJ_ANY, NULL, NULL); } static int traverse_one_object(struct object *obj) @@ -132,7 +148,7 @@ static int traverse_one_object(struct object *obj) if (parse_tree(tree) < 0) return 1; /* error already displayed */ } - result = fsck_walk(obj, mark_object, obj); + result = fsck_walk(obj, obj, &fsck_walk_options); if (tree) free_tree_buffer(tree); return result; @@ -158,7 +174,7 @@ static int traverse_reachable(void) return !!result; } -static int mark_used(struct object *obj, int type, void *data) +static int mark_used(struct object *obj, int type, void *data, struct fsck_options *options) { if (!obj) return 1; @@ -179,6 +195,8 @@ static void check_reachable_object(struct object *obj) if (!(obj->flags & HAS_OBJ)) { if (has_sha1_pack(obj->sha1)) return; /* it is in pack - forget about it */ + if (connectivity_only && has_sha1_file(obj->sha1)) + return; printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); errors_found |= ERROR_REACHABLE; return; @@ -296,9 +314,9 @@ static int fsck_obj(struct object *obj) fprintf(stderr, "Checking %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); - if (fsck_walk(obj, mark_used, NULL)) + if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, "broken links"); - if (fsck_object(obj, NULL, 0, check_strict, fsck_error_func)) + if (fsck_object(obj, NULL, 0, &fsck_obj_options)) return -1; if (obj->type == OBJ_TREE) { @@ -621,6 +639,7 @@ static struct option fsck_opts[] = { OPT_BOOL(0, "cache", &keep_cache_objects, N_("make index objects head nodes")), OPT_BOOL(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")), OPT_BOOL(0, "full", &check_full, N_("also consider packs and alternate objects")), + OPT_BOOL(0, "connectivity-only", &connectivity_only, N_("check only connectivity")), OPT_BOOL(0, "strict", &check_strict, N_("enable more strict checking")), OPT_BOOL(0, "lost-found", &write_lost_and_found, N_("write dangling objects in .git/lost-found")), @@ -638,6 +657,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); + fsck_walk_options.walk = mark_object; + fsck_obj_options.walk = mark_used; + fsck_obj_options.error_func = fsck_error_func; + if (check_strict) + fsck_obj_options.strict = 1; + if (show_progress == -1) show_progress = isatty(2); if (verbose) @@ -648,8 +673,11 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) include_reflogs = 0; } + git_config(fsck_config, NULL); + fsck_head_link(); - fsck_object_dir(get_object_directory()); + if (!connectivity_only) + fsck_object_dir(get_object_directory()); prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) { diff --git a/builtin/index-pack.c b/builtin/index-pack.c index f07bc66ed..3f1084044 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -75,6 +75,7 @@ static int nr_threads; static int from_stdin; static int strict; static int do_fsck_object; +static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; static int verbose; static int show_stat; static int check_self_contained_and_connected; @@ -192,7 +193,7 @@ static void cleanup_thread(void) #endif -static int mark_link(struct object *obj, int type, void *data) +static int mark_link(struct object *obj, int type, void *data, struct fsck_options *options) { if (!obj) return -1; @@ -838,10 +839,9 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, if (!obj) die(_("invalid %s"), typename(type)); if (do_fsck_object && - fsck_object(obj, buf, size, 1, - fsck_error_function)) + fsck_object(obj, buf, size, &fsck_options)) die(_("Error in object")); - if (fsck_walk(obj, mark_link, NULL)) + if (fsck_walk(obj, NULL, &fsck_options)) die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1)); if (obj->type == OBJ_TREE) { @@ -1615,6 +1615,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) usage(index_pack_usage); check_replace_refs = 0; + fsck_options.walk = mark_link; reset_pack_idx_option(&opts); git_config(git_index_pack_config, &opts); @@ -1632,6 +1633,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) } else if (!strcmp(arg, "--strict")) { strict = 1; do_fsck_object = 1; + } else if (skip_prefix(arg, "--strict=", &arg)) { + strict = 1; + do_fsck_object = 1; + fsck_set_msg_types(&fsck_options, arg); } else if (!strcmp(arg, "--check-self-contained-and-connected")) { strict = 1; check_self_contained_and_connected = 1; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 94d057177..4ced2eba3 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -19,6 +19,7 @@ #include "tag.h" #include "gpg-interface.h" #include "sigchain.h" +#include "fsck.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -36,6 +37,7 @@ static enum deny_action deny_current_branch = DENY_UNCONFIGURED; static enum deny_action deny_delete_current = DENY_UNCONFIGURED; static int receive_fsck_objects = -1; static int transfer_fsck_objects = -1; +static struct strbuf fsck_msg_types = STRBUF_INIT; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int advertise_atomic_push = 1; @@ -115,6 +117,26 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.fsck.skiplist") == 0) { + const char *path; + + if (git_config_pathname(&path, var, value)) + return 1; + strbuf_addf(&fsck_msg_types, "%cskiplist=%s", + fsck_msg_types.len ? ',' : '=', path); + free((char *)path); + return 0; + } + + if (skip_prefix(var, "receive.fsck.", &var)) { + if (is_valid_msg_type(var, value)) + strbuf_addf(&fsck_msg_types, "%c%s=%s", + fsck_msg_types.len ? ',' : '=', var, value); + else + warning("Skipping unknown msg id '%s'", var); + return 0; + } + if (strcmp(var, "receive.fsckobjects") == 0) { receive_fsck_objects = git_config_bool(var, value); return 0; @@ -1490,7 +1512,8 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (quiet) argv_array_push(&child.args, "-q"); if (fsck_objects) - argv_array_push(&child.args, "--strict"); + argv_array_pushf(&child.args, "--strict%s", + fsck_msg_types.buf); child.no_stdout = 1; child.err = err_fd; child.git_cmd = 1; @@ -1508,7 +1531,8 @@ static const char *unpack(int err_fd, struct shallow_info *si) argv_array_pushl(&child.args, "index-pack", "--stdin", hdr_arg, keep_arg, NULL); if (fsck_objects) - argv_array_push(&child.args, "--strict"); + argv_array_pushf(&child.args, "--strict%s", + fsck_msg_types.buf); if (fix_thin) argv_array_push(&child.args, "--fix-thin"); child.out = -1; diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index ac6667242..7cc086f5f 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -20,6 +20,7 @@ static unsigned char buffer[4096]; static unsigned int offset, len; static off_t consumed_bytes; static git_SHA_CTX ctx; +static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; /* * When running under --strict mode, objects whose reachability are @@ -178,7 +179,7 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) * that have reachability requirements and calls this function. * Verify its reachability and validity recursively and write it out. */ -static int check_object(struct object *obj, int type, void *data) +static int check_object(struct object *obj, int type, void *data, struct fsck_options *options) { struct obj_buffer *obj_buf; @@ -203,10 +204,10 @@ static int check_object(struct object *obj, int type, void *data) obj_buf = lookup_object_buffer(obj); if (!obj_buf) die("Whoops! Cannot find object '%s'", sha1_to_hex(obj->sha1)); - if (fsck_object(obj, obj_buf->buffer, obj_buf->size, 1, - fsck_error_function)) + if (fsck_object(obj, obj_buf->buffer, obj_buf->size, &fsck_options)) die("Error in object"); - if (fsck_walk(obj, check_object, NULL)) + fsck_options.walk = check_object; + if (fsck_walk(obj, NULL, &fsck_options)) die("Error on reachable objects of %s", sha1_to_hex(obj->sha1)); write_cached_object(obj, obj_buf); return 0; @@ -217,7 +218,7 @@ static void write_rest(void) unsigned i; for (i = 0; i < nr_objects; i++) { if (obj_list[i].obj) - check_object(obj_list[i].obj, OBJ_ANY, NULL); + check_object(obj_list[i].obj, OBJ_ANY, NULL, NULL); } } @@ -529,6 +530,11 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) strict = 1; continue; } + if (skip_prefix(arg, "--strict=", &arg)) { + strict = 1; + fsck_set_msg_types(&fsck_options, arg); + continue; + } if (starts_with(arg, "--pack_header=")) { struct pack_header *hdr; char *c; @@ -8,8 +8,294 @@ #include "fsck.h" #include "refs.h" #include "utf8.h" +#include "sha1-array.h" + +#define FSCK_FATAL -1 +#define FSCK_INFO -2 + +#define FOREACH_MSG_ID(FUNC) \ + /* fatal errors */ \ + FUNC(NUL_IN_HEADER, FATAL) \ + FUNC(UNTERMINATED_HEADER, FATAL) \ + /* errors */ \ + FUNC(BAD_DATE, ERROR) \ + FUNC(BAD_DATE_OVERFLOW, ERROR) \ + FUNC(BAD_EMAIL, ERROR) \ + FUNC(BAD_NAME, ERROR) \ + FUNC(BAD_OBJECT_SHA1, ERROR) \ + FUNC(BAD_PARENT_SHA1, ERROR) \ + FUNC(BAD_TAG_OBJECT, ERROR) \ + FUNC(BAD_TIMEZONE, ERROR) \ + FUNC(BAD_TREE, ERROR) \ + FUNC(BAD_TREE_SHA1, ERROR) \ + FUNC(BAD_TYPE, ERROR) \ + FUNC(DUPLICATE_ENTRIES, ERROR) \ + FUNC(MISSING_AUTHOR, ERROR) \ + FUNC(MISSING_COMMITTER, ERROR) \ + FUNC(MISSING_EMAIL, ERROR) \ + FUNC(MISSING_GRAFT, ERROR) \ + FUNC(MISSING_NAME_BEFORE_EMAIL, ERROR) \ + FUNC(MISSING_OBJECT, ERROR) \ + FUNC(MISSING_PARENT, ERROR) \ + FUNC(MISSING_SPACE_BEFORE_DATE, ERROR) \ + FUNC(MISSING_SPACE_BEFORE_EMAIL, ERROR) \ + FUNC(MISSING_TAG, ERROR) \ + FUNC(MISSING_TAG_ENTRY, ERROR) \ + FUNC(MISSING_TAG_OBJECT, ERROR) \ + FUNC(MISSING_TREE, ERROR) \ + FUNC(MISSING_TYPE, ERROR) \ + FUNC(MISSING_TYPE_ENTRY, ERROR) \ + FUNC(MULTIPLE_AUTHORS, ERROR) \ + FUNC(TAG_OBJECT_NOT_TAG, ERROR) \ + FUNC(TREE_NOT_SORTED, ERROR) \ + FUNC(UNKNOWN_TYPE, ERROR) \ + FUNC(ZERO_PADDED_DATE, ERROR) \ + /* warnings */ \ + FUNC(BAD_FILEMODE, WARN) \ + FUNC(EMPTY_NAME, WARN) \ + FUNC(FULL_PATHNAME, WARN) \ + FUNC(HAS_DOT, WARN) \ + FUNC(HAS_DOTDOT, WARN) \ + FUNC(HAS_DOTGIT, WARN) \ + FUNC(NULL_SHA1, WARN) \ + FUNC(ZERO_PADDED_FILEMODE, WARN) \ + /* infos (reported as warnings, but ignored by default) */ \ + FUNC(BAD_TAG_NAME, INFO) \ + FUNC(MISSING_TAGGER_ENTRY, INFO) + +#define MSG_ID(id, msg_type) FSCK_MSG_##id, +enum fsck_msg_id { + FOREACH_MSG_ID(MSG_ID) + FSCK_MSG_MAX +}; +#undef MSG_ID + +#define STR(x) #x +#define MSG_ID(id, msg_type) { STR(id), NULL, FSCK_##msg_type }, +static struct { + const char *id_string; + const char *downcased; + int msg_type; +} msg_id_info[FSCK_MSG_MAX + 1] = { + FOREACH_MSG_ID(MSG_ID) + { NULL, NULL, -1 } +}; +#undef MSG_ID + +static int parse_msg_id(const char *text) +{ + int i; + + if (!msg_id_info[0].downcased) { + /* convert id_string to lower case, without underscores. */ + for (i = 0; i < FSCK_MSG_MAX; i++) { + const char *p = msg_id_info[i].id_string; + int len = strlen(p); + char *q = xmalloc(len); + + msg_id_info[i].downcased = q; + while (*p) + if (*p == '_') + p++; + else + *(q)++ = tolower(*(p)++); + *q = '\0'; + } + } + + for (i = 0; i < FSCK_MSG_MAX; i++) + if (!strcmp(text, msg_id_info[i].downcased)) + return i; + + return -1; +} + +static int fsck_msg_type(enum fsck_msg_id msg_id, + struct fsck_options *options) +{ + int msg_type; + + assert(msg_id >= 0 && msg_id < FSCK_MSG_MAX); + + if (options->msg_type) + msg_type = options->msg_type[msg_id]; + else { + msg_type = msg_id_info[msg_id].msg_type; + if (options->strict && msg_type == FSCK_WARN) + msg_type = FSCK_ERROR; + } + + return msg_type; +} + +static void init_skiplist(struct fsck_options *options, const char *path) +{ + static struct sha1_array skiplist = SHA1_ARRAY_INIT; + int sorted, fd; + char buffer[41]; + unsigned char sha1[20]; + + if (options->skiplist) + sorted = options->skiplist->sorted; + else { + sorted = 1; + options->skiplist = &skiplist; + } + + fd = open(path, O_RDONLY); + if (fd < 0) + die("Could not open skip list: %s", path); + for (;;) { + int result = read_in_full(fd, buffer, sizeof(buffer)); + if (result < 0) + die_errno("Could not read '%s'", path); + if (!result) + break; + if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') + die("Invalid SHA-1: %s", buffer); + sha1_array_append(&skiplist, sha1); + if (sorted && skiplist.nr > 1 && + hashcmp(skiplist.sha1[skiplist.nr - 2], + sha1) > 0) + sorted = 0; + } + close(fd); + + if (sorted) + skiplist.sorted = 1; +} + +static int parse_msg_type(const char *str) +{ + if (!strcmp(str, "error")) + return FSCK_ERROR; + else if (!strcmp(str, "warn")) + return FSCK_WARN; + else if (!strcmp(str, "ignore")) + return FSCK_IGNORE; + else + die("Unknown fsck message type: '%s'", str); +} + +int is_valid_msg_type(const char *msg_id, const char *msg_type) +{ + if (parse_msg_id(msg_id) < 0) + return 0; + parse_msg_type(msg_type); + return 1; +} + +void fsck_set_msg_type(struct fsck_options *options, + const char *msg_id, const char *msg_type) +{ + int id = parse_msg_id(msg_id), type; + + if (id < 0) + die("Unhandled message id: %s", msg_id); + type = parse_msg_type(msg_type); + + if (type != FSCK_ERROR && msg_id_info[id].msg_type == FSCK_FATAL) + die("Cannot demote %s to %s", msg_id, msg_type); + + if (!options->msg_type) { + int i; + int *msg_type = xmalloc(sizeof(int) * FSCK_MSG_MAX); + for (i = 0; i < FSCK_MSG_MAX; i++) + msg_type[i] = fsck_msg_type(i, options); + options->msg_type = msg_type; + } + + options->msg_type[id] = type; +} + +void fsck_set_msg_types(struct fsck_options *options, const char *values) +{ + char *buf = xstrdup(values), *to_free = buf; + int done = 0; + + while (!done) { + int len = strcspn(buf, " ,|"), equal; + + done = !buf[len]; + if (!len) { + buf++; + continue; + } + buf[len] = '\0'; + + for (equal = 0; + equal < len && buf[equal] != '=' && buf[equal] != ':'; + equal++) + buf[equal] = tolower(buf[equal]); + buf[equal] = '\0'; + + if (!strcmp(buf, "skiplist")) { + if (equal == len) + die("skiplist requires a path"); + init_skiplist(options, buf + equal + 1); + buf += len + 1; + continue; + } + + if (equal == len) + die("Missing '=': '%s'", buf); -static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data) + fsck_set_msg_type(options, buf, buf + equal + 1); + buf += len + 1; + } + free(to_free); +} + +static void append_msg_id(struct strbuf *sb, const char *msg_id) +{ + for (;;) { + char c = *(msg_id)++; + + if (!c) + break; + if (c != '_') + strbuf_addch(sb, tolower(c)); + else { + assert(*msg_id); + strbuf_addch(sb, *(msg_id)++); + } + } + + strbuf_addstr(sb, ": "); +} + +__attribute__((format (printf, 4, 5))) +static int report(struct fsck_options *options, struct object *object, + enum fsck_msg_id id, const char *fmt, ...) +{ + va_list ap; + struct strbuf sb = STRBUF_INIT; + int msg_type = fsck_msg_type(id, options), result; + + if (msg_type == FSCK_IGNORE) + return 0; + + if (options->skiplist && object && + sha1_array_lookup(options->skiplist, object->sha1) >= 0) + return 0; + + if (msg_type == FSCK_FATAL) + msg_type = FSCK_ERROR; + else if (msg_type == FSCK_INFO) + msg_type = FSCK_WARN; + + append_msg_id(&sb, msg_id_info[id].id_string); + + va_start(ap, fmt); + strbuf_vaddf(&sb, fmt, ap); + result = options->error_func(object, msg_type, sb.buf); + strbuf_release(&sb); + va_end(ap); + + return result; +} + +static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *options) { struct tree_desc desc; struct name_entry entry; @@ -25,9 +311,9 @@ static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data) if (S_ISGITLINK(entry.mode)) continue; if (S_ISDIR(entry.mode)) - result = walk(&lookup_tree(entry.sha1)->object, OBJ_TREE, data); + result = options->walk(&lookup_tree(entry.sha1)->object, OBJ_TREE, data, options); else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode)) - result = walk(&lookup_blob(entry.sha1)->object, OBJ_BLOB, data); + result = options->walk(&lookup_blob(entry.sha1)->object, OBJ_BLOB, data, options); else { result = error("in tree %s: entry %s has bad mode %.6o", sha1_to_hex(tree->object.sha1), entry.path, entry.mode); @@ -40,7 +326,7 @@ static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data) return res; } -static int fsck_walk_commit(struct commit *commit, fsck_walk_func walk, void *data) +static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_options *options) { struct commit_list *parents; int res; @@ -49,14 +335,14 @@ static int fsck_walk_commit(struct commit *commit, fsck_walk_func walk, void *da if (parse_commit(commit)) return -1; - result = walk((struct object *)commit->tree, OBJ_TREE, data); + result = options->walk((struct object *)commit->tree, OBJ_TREE, data, options); if (result < 0) return result; res = result; parents = commit->parents; while (parents) { - result = walk((struct object *)parents->item, OBJ_COMMIT, data); + result = options->walk((struct object *)parents->item, OBJ_COMMIT, data, options); if (result < 0) return result; if (!res) @@ -66,14 +352,14 @@ static int fsck_walk_commit(struct commit *commit, fsck_walk_func walk, void *da return res; } -static int fsck_walk_tag(struct tag *tag, fsck_walk_func walk, void *data) +static int fsck_walk_tag(struct tag *tag, void *data, struct fsck_options *options) { if (parse_tag(tag)) return -1; - return walk(tag->tagged, OBJ_ANY, data); + return options->walk(tag->tagged, OBJ_ANY, data, options); } -int fsck_walk(struct object *obj, fsck_walk_func walk, void *data) +int fsck_walk(struct object *obj, void *data, struct fsck_options *options) { if (!obj) return -1; @@ -81,11 +367,11 @@ int fsck_walk(struct object *obj, fsck_walk_func walk, void *data) case OBJ_BLOB: return 0; case OBJ_TREE: - return fsck_walk_tree((struct tree *)obj, walk, data); + return fsck_walk_tree((struct tree *)obj, data, options); case OBJ_COMMIT: - return fsck_walk_commit((struct commit *)obj, walk, data); + return fsck_walk_commit((struct commit *)obj, data, options); case OBJ_TAG: - return fsck_walk_tag((struct tag *)obj, walk, data); + return fsck_walk_tag((struct tag *)obj, data, options); default: error("Unknown object type for %s", sha1_to_hex(obj->sha1)); return -1; @@ -138,7 +424,7 @@ static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, con return c1 < c2 ? 0 : TREE_UNORDERED; } -static int fsck_tree(struct tree *item, int strict, fsck_error error_func) +static int fsck_tree(struct tree *item, struct fsck_options *options) { int retval; int has_null_sha1 = 0; @@ -194,7 +480,7 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) * bits.. */ case S_IFREG | 0664: - if (!strict) + if (!options->strict) break; default: has_bad_modes = 1; @@ -219,30 +505,30 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) retval = 0; if (has_null_sha1) - retval += error_func(&item->object, FSCK_WARN, "contains entries pointing to null sha1"); + retval += report(options, &item->object, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1"); if (has_full_path) - retval += error_func(&item->object, FSCK_WARN, "contains full pathnames"); + retval += report(options, &item->object, FSCK_MSG_FULL_PATHNAME, "contains full pathnames"); if (has_empty_name) - retval += error_func(&item->object, FSCK_WARN, "contains empty pathname"); + retval += report(options, &item->object, FSCK_MSG_EMPTY_NAME, "contains empty pathname"); if (has_dot) - retval += error_func(&item->object, FSCK_WARN, "contains '.'"); + retval += report(options, &item->object, FSCK_MSG_HAS_DOT, "contains '.'"); if (has_dotdot) - retval += error_func(&item->object, FSCK_WARN, "contains '..'"); + retval += report(options, &item->object, FSCK_MSG_HAS_DOTDOT, "contains '..'"); if (has_dotgit) - retval += error_func(&item->object, FSCK_WARN, "contains '.git'"); + retval += report(options, &item->object, FSCK_MSG_HAS_DOTGIT, "contains '.git'"); if (has_zero_pad) - retval += error_func(&item->object, FSCK_WARN, "contains zero-padded file modes"); + retval += report(options, &item->object, FSCK_MSG_ZERO_PADDED_FILEMODE, "contains zero-padded file modes"); if (has_bad_modes) - retval += error_func(&item->object, FSCK_WARN, "contains bad file modes"); + retval += report(options, &item->object, FSCK_MSG_BAD_FILEMODE, "contains bad file modes"); if (has_dup_entries) - retval += error_func(&item->object, FSCK_ERROR, "contains duplicate file entries"); + retval += report(options, &item->object, FSCK_MSG_DUPLICATE_ENTRIES, "contains duplicate file entries"); if (not_properly_sorted) - retval += error_func(&item->object, FSCK_ERROR, "not properly sorted"); + retval += report(options, &item->object, FSCK_MSG_TREE_NOT_SORTED, "not properly sorted"); return retval; } static int verify_headers(const void *data, unsigned long size, - struct object *obj, fsck_error error_func) + struct object *obj, struct fsck_options *options) { const char *buffer = (const char *)data; unsigned long i; @@ -250,8 +536,9 @@ static int verify_headers(const void *data, unsigned long size, for (i = 0; i < size; i++) { switch (buffer[i]) { case '\0': - return error_func(obj, FSCK_ERROR, - "unterminated header: NUL at offset %d", i); + return report(options, obj, + FSCK_MSG_NUL_IN_HEADER, + "unterminated header: NUL at offset %ld", i); case '\n': if (i + 1 < size && buffer[i + 1] == '\n') return 0; @@ -267,67 +554,79 @@ static int verify_headers(const void *data, unsigned long size, if (size && buffer[size - 1] == '\n') return 0; - return error_func(obj, FSCK_ERROR, "unterminated header"); + return report(options, obj, + FSCK_MSG_UNTERMINATED_HEADER, "unterminated header"); } -static int fsck_ident(const char **ident, struct object *obj, fsck_error error_func) +static int fsck_ident(const char **ident, struct object *obj, struct fsck_options *options) { + const char *p = *ident; char *end; - if (**ident == '<') - return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email"); - *ident += strcspn(*ident, "<>\n"); - if (**ident == '>') - return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad name"); - if (**ident != '<') - return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing email"); - if ((*ident)[-1] != ' ') - return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email"); - (*ident)++; - *ident += strcspn(*ident, "<>\n"); - if (**ident != '>') - return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad email"); - (*ident)++; - if (**ident != ' ') - return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before date"); - (*ident)++; - if (**ident == '0' && (*ident)[1] != ' ') - return error_func(obj, FSCK_ERROR, "invalid author/committer line - zero-padded date"); - if (date_overflows(strtoul(*ident, &end, 10))) - return error_func(obj, FSCK_ERROR, "invalid author/committer line - date causes integer overflow"); - if (end == *ident || *end != ' ') - return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad date"); - *ident = end + 1; - if ((**ident != '+' && **ident != '-') || - !isdigit((*ident)[1]) || - !isdigit((*ident)[2]) || - !isdigit((*ident)[3]) || - !isdigit((*ident)[4]) || - ((*ident)[5] != '\n')) - return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad time zone"); - (*ident) += 6; + *ident = strchrnul(*ident, '\n'); + if (**ident == '\n') + (*ident)++; + + if (*p == '<') + return report(options, obj, FSCK_MSG_MISSING_NAME_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); + p += strcspn(p, "<>\n"); + if (*p == '>') + return report(options, obj, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name"); + if (*p != '<') + return report(options, obj, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email"); + if (p[-1] != ' ') + return report(options, obj, FSCK_MSG_MISSING_SPACE_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); + p++; + p += strcspn(p, "<>\n"); + if (*p != '>') + return report(options, obj, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email"); + p++; + if (*p != ' ') + return report(options, obj, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date"); + p++; + if (*p == '0' && p[1] != ' ') + return report(options, obj, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date"); + if (date_overflows(strtoul(p, &end, 10))) + return report(options, obj, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow"); + if ((end == p || *end != ' ')) + return report(options, obj, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date"); + p = end + 1; + if ((*p != '+' && *p != '-') || + !isdigit(p[1]) || + !isdigit(p[2]) || + !isdigit(p[3]) || + !isdigit(p[4]) || + (p[5] != '\n')) + return report(options, obj, FSCK_MSG_BAD_TIMEZONE, "invalid author/committer line - bad time zone"); + p += 6; return 0; } static int fsck_commit_buffer(struct commit *commit, const char *buffer, - unsigned long size, fsck_error error_func) + unsigned long size, struct fsck_options *options) { unsigned char tree_sha1[20], sha1[20]; struct commit_graft *graft; - unsigned parent_count, parent_line_count = 0; + unsigned parent_count, parent_line_count = 0, author_count; int err; - if (verify_headers(buffer, size, &commit->object, error_func)) + if (verify_headers(buffer, size, &commit->object, options)) return -1; if (!skip_prefix(buffer, "tree ", &buffer)) - return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line"); - if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n') - return error_func(&commit->object, FSCK_ERROR, "invalid 'tree' line format - bad sha1"); + return report(options, &commit->object, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line"); + if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n') { + err = report(options, &commit->object, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1"); + if (err) + return err; + } buffer += 41; while (skip_prefix(buffer, "parent ", &buffer)) { - if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') - return error_func(&commit->object, FSCK_ERROR, "invalid 'parent' line format - bad sha1"); + if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') { + err = report(options, &commit->object, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1"); + if (err) + return err; + } buffer += 41; parent_line_count++; } @@ -336,40 +635,54 @@ static int fsck_commit_buffer(struct commit *commit, const char *buffer, if (graft) { if (graft->nr_parent == -1 && !parent_count) ; /* shallow commit */ - else if (graft->nr_parent != parent_count) - return error_func(&commit->object, FSCK_ERROR, "graft objects missing"); + else if (graft->nr_parent != parent_count) { + err = report(options, &commit->object, FSCK_MSG_MISSING_GRAFT, "graft objects missing"); + if (err) + return err; + } } else { - if (parent_count != parent_line_count) - return error_func(&commit->object, FSCK_ERROR, "parent objects missing"); + if (parent_count != parent_line_count) { + err = report(options, &commit->object, FSCK_MSG_MISSING_PARENT, "parent objects missing"); + if (err) + return err; + } + } + author_count = 0; + while (skip_prefix(buffer, "author ", &buffer)) { + author_count++; + err = fsck_ident(&buffer, &commit->object, options); + if (err) + return err; } - if (!skip_prefix(buffer, "author ", &buffer)) - return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'author' line"); - err = fsck_ident(&buffer, &commit->object, error_func); + if (author_count < 1) + err = report(options, &commit->object, FSCK_MSG_MISSING_AUTHOR, "invalid format - expected 'author' line"); + else if (author_count > 1) + err = report(options, &commit->object, FSCK_MSG_MULTIPLE_AUTHORS, "invalid format - multiple 'author' lines"); if (err) return err; if (!skip_prefix(buffer, "committer ", &buffer)) - return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'committer' line"); - err = fsck_ident(&buffer, &commit->object, error_func); + return report(options, &commit->object, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line"); + err = fsck_ident(&buffer, &commit->object, options); if (err) return err; if (!commit->tree) - return error_func(&commit->object, FSCK_ERROR, "could not load commit's tree %s", sha1_to_hex(tree_sha1)); + return report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", sha1_to_hex(tree_sha1)); return 0; } static int fsck_commit(struct commit *commit, const char *data, - unsigned long size, fsck_error error_func) + unsigned long size, struct fsck_options *options) { const char *buffer = data ? data : get_commit_buffer(commit, &size); - int ret = fsck_commit_buffer(commit, buffer, size, error_func); + int ret = fsck_commit_buffer(commit, buffer, size, options); if (!data) unuse_commit_buffer(commit, buffer); return ret; } static int fsck_tag_buffer(struct tag *tag, const char *data, - unsigned long size, fsck_error error_func) + unsigned long size, struct fsck_options *options) { unsigned char sha1[20]; int ret = 0; @@ -385,65 +698,75 @@ static int fsck_tag_buffer(struct tag *tag, const char *data, buffer = to_free = read_sha1_file(tag->object.sha1, &type, &size); if (!buffer) - return error_func(&tag->object, FSCK_ERROR, + return report(options, &tag->object, + FSCK_MSG_MISSING_TAG_OBJECT, "cannot read tag object"); if (type != OBJ_TAG) { - ret = error_func(&tag->object, FSCK_ERROR, + ret = report(options, &tag->object, + FSCK_MSG_TAG_OBJECT_NOT_TAG, "expected tag got %s", typename(type)); goto done; } } - if (verify_headers(buffer, size, &tag->object, error_func)) + if (verify_headers(buffer, size, &tag->object, options)) goto done; if (!skip_prefix(buffer, "object ", &buffer)) { - ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'object' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line"); goto done; } if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') { - ret = error_func(&tag->object, FSCK_ERROR, "invalid 'object' line format - bad sha1"); - goto done; + ret = report(options, &tag->object, FSCK_MSG_BAD_OBJECT_SHA1, "invalid 'object' line format - bad sha1"); + if (ret) + goto done; } buffer += 41; if (!skip_prefix(buffer, "type ", &buffer)) { - ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'type' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line"); goto done; } eol = strchr(buffer, '\n'); if (!eol) { - ret = error_func(&tag->object, FSCK_ERROR, "invalid format - unexpected end after 'type' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_TYPE, "invalid format - unexpected end after 'type' line"); goto done; } if (type_from_string_gently(buffer, eol - buffer, 1) < 0) - ret = error_func(&tag->object, FSCK_ERROR, "invalid 'type' value"); + ret = report(options, &tag->object, FSCK_MSG_BAD_TYPE, "invalid 'type' value"); if (ret) goto done; buffer = eol + 1; if (!skip_prefix(buffer, "tag ", &buffer)) { - ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'tag' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_TAG_ENTRY, "invalid format - expected 'tag' line"); goto done; } eol = strchr(buffer, '\n'); if (!eol) { - ret = error_func(&tag->object, FSCK_ERROR, "invalid format - unexpected end after 'type' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_TAG, "invalid format - unexpected end after 'type' line"); goto done; } strbuf_addf(&sb, "refs/tags/%.*s", (int)(eol - buffer), buffer); - if (check_refname_format(sb.buf, 0)) - error_func(&tag->object, FSCK_WARN, "invalid 'tag' name: %.*s", + if (check_refname_format(sb.buf, 0)) { + ret = report(options, &tag->object, FSCK_MSG_BAD_TAG_NAME, + "invalid 'tag' name: %.*s", (int)(eol - buffer), buffer); + if (ret) + goto done; + } buffer = eol + 1; - if (!skip_prefix(buffer, "tagger ", &buffer)) + if (!skip_prefix(buffer, "tagger ", &buffer)) { /* early tags do not contain 'tagger' lines; warn only */ - error_func(&tag->object, FSCK_WARN, "invalid format - expected 'tagger' line"); + ret = report(options, &tag->object, FSCK_MSG_MISSING_TAGGER_ENTRY, "invalid format - expected 'tagger' line"); + if (ret) + goto done; + } else - ret = fsck_ident(&buffer, &tag->object, error_func); + ret = fsck_ident(&buffer, &tag->object, options); done: strbuf_release(&sb); @@ -452,49 +775,43 @@ done: } static int fsck_tag(struct tag *tag, const char *data, - unsigned long size, fsck_error error_func) + unsigned long size, struct fsck_options *options) { struct object *tagged = tag->tagged; if (!tagged) - return error_func(&tag->object, FSCK_ERROR, "could not load tagged object"); + return report(options, &tag->object, FSCK_MSG_BAD_TAG_OBJECT, "could not load tagged object"); - return fsck_tag_buffer(tag, data, size, error_func); + return fsck_tag_buffer(tag, data, size, options); } int fsck_object(struct object *obj, void *data, unsigned long size, - int strict, fsck_error error_func) + struct fsck_options *options) { if (!obj) - return error_func(obj, FSCK_ERROR, "no valid object to fsck"); + return report(options, obj, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck"); if (obj->type == OBJ_BLOB) return 0; if (obj->type == OBJ_TREE) - return fsck_tree((struct tree *) obj, strict, error_func); + return fsck_tree((struct tree *) obj, options); if (obj->type == OBJ_COMMIT) return fsck_commit((struct commit *) obj, (const char *) data, - size, error_func); + size, options); if (obj->type == OBJ_TAG) return fsck_tag((struct tag *) obj, (const char *) data, - size, error_func); + size, options); - return error_func(obj, FSCK_ERROR, "unknown type '%d' (internal fsck error)", + return report(options, obj, FSCK_MSG_UNKNOWN_TYPE, "unknown type '%d' (internal fsck error)", obj->type); } -int fsck_error_function(struct object *obj, int type, const char *fmt, ...) +int fsck_error_function(struct object *obj, int msg_type, const char *message) { - va_list ap; - struct strbuf sb = STRBUF_INIT; - - strbuf_addf(&sb, "object %s:", sha1_to_hex(obj->sha1)); - - va_start(ap, fmt); - strbuf_vaddf(&sb, fmt, ap); - va_end(ap); - - error("%s", sb.buf); - strbuf_release(&sb); + if (msg_type == FSCK_WARN) { + warning("object %s: %s", sha1_to_hex(obj->sha1), message); + return 0; + } + error("object %s: %s", sha1_to_hex(obj->sha1), message); return 1; } @@ -3,6 +3,14 @@ #define FSCK_ERROR 1 #define FSCK_WARN 2 +#define FSCK_IGNORE 3 + +struct fsck_options; + +void fsck_set_msg_type(struct fsck_options *options, + const char *msg_id, const char *msg_type); +void fsck_set_msg_types(struct fsck_options *options, const char *values); +int is_valid_msg_type(const char *msg_id, const char *msg_type); /* * callback function for fsck_walk @@ -12,13 +20,23 @@ * <0 error signaled and abort * >0 error signaled and do not abort */ -typedef int (*fsck_walk_func)(struct object *obj, int type, void *data); +typedef int (*fsck_walk_func)(struct object *obj, int type, void *data, struct fsck_options *options); /* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */ -typedef int (*fsck_error)(struct object *obj, int type, const char *err, ...); +typedef int (*fsck_error)(struct object *obj, int type, const char *message); + +int fsck_error_function(struct object *obj, int type, const char *message); + +struct fsck_options { + fsck_walk_func walk; + fsck_error error_func; + unsigned strict:1; + int *msg_type; + struct sha1_array *skiplist; +}; -__attribute__((format (printf, 3, 4))) -int fsck_error_function(struct object *obj, int type, const char *fmt, ...); +#define FSCK_OPTIONS_DEFAULT { NULL, fsck_error_function, 0, NULL } +#define FSCK_OPTIONS_STRICT { NULL, fsck_error_function, 1, NULL } /* descend in all linked child objects * the return value is: @@ -27,9 +45,9 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...); * >0 return value of the first signaled error >0 (in the case of no other errors) * 0 everything OK */ -int fsck_walk(struct object *obj, fsck_walk_func walk, void *data); +int fsck_walk(struct object *obj, void *data, struct fsck_options *options); /* If NULL is passed for data, we assume the object is local and read it. */ int fsck_object(struct object *obj, void *data, unsigned long size, - int strict, fsck_error error_func); + struct fsck_options *options); #endif diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index cfb32b624..956673b8a 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -231,8 +231,8 @@ test_expect_success 'tag with incorrect tag name & missing tagger' ' git fsck --tags 2>out && cat >expect <<-EOF && - warning in tag $tag: invalid '\''tag'\'' name: wrong name format - warning in tag $tag: invalid format - expected '\''tagger'\'' line + warning in tag $tag: badTagName: invalid '\''tag'\'' name: wrong name format + warning in tag $tag: missingTaggerEntry: invalid format - expected '\''tagger'\'' line EOF test_cmp expect out ' @@ -287,6 +287,17 @@ test_expect_success 'rev-list --verify-objects with bad sha1' ' grep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out ' +test_expect_success 'force fsck to ignore double author' ' + git cat-file commit HEAD >basis && + sed "s/^author .*/&,&/" <basis | tr , \\n >multiple-authors && + new=$(git hash-object -t commit -w --stdin <multiple-authors) && + test_when_finished "remove_object $new" && + git update-ref refs/heads/bogus "$new" && + test_when_finished "git update-ref -d refs/heads/bogus" && + test_must_fail git fsck && + git -c fsck.multipleAuthors=ignore fsck +' + _bz='\0' _bz5="$_bz$_bz$_bz$_bz$_bz" _bz20="$_bz5$_bz5$_bz5$_bz5" @@ -420,4 +431,26 @@ test_expect_success 'fsck notices ref pointing to missing tag' ' test_must_fail git -C missing fsck ' +test_expect_success 'fsck --connectivity-only' ' + rm -rf connectivity-only && + git init connectivity-only && + ( + cd connectivity-only && + touch empty && + git add empty && + test_commit empty && + empty=.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 && + rm -f $empty && + echo invalid >$empty && + test_must_fail git fsck --strict && + git fsck --strict --connectivity-only && + tree=$(git rev-parse HEAD:) && + suffix=${tree#??} && + tree=.git/objects/${tree%$suffix}/$suffix && + rm -f $tree && + echo invalid >$tree && + test_must_fail git fsck --strict --connectivity-only + ) +' + test_done diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index 61bc8da56..3dc5ec4dd 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -259,7 +259,7 @@ EOF thirtyeight=${tag#??} && rm -f .git/objects/${tag%$thirtyeight}/$thirtyeight && git index-pack --strict tag-test-${pack1}.pack 2>err && - grep "^error:.* expected .tagger. line" err + grep "^warning:.* expected .tagger. line" err ' test_done diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 69ee13c8b..44f3d5fb2 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -115,4 +115,55 @@ test_expect_success 'push with transfer.fsckobjects' ' test_cmp exp act ' +cat >bogus-commit <<\EOF +tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 +author Bugs Bunny 1234567890 +0000 +committer Bugs Bunny <bugs@bun.ni> 1234567890 +0000 + +This commit object intentionally broken +EOF + +test_expect_success 'push with receive.fsck.skipList' ' + commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && + git push . $commit:refs/heads/bogus && + rm -rf dst && + git init dst && + git --git-dir=dst/.git config receive.fsckObjects true && + test_must_fail git push --porcelain dst bogus && + git --git-dir=dst/.git config receive.fsck.skipList SKIP && + echo $commit >dst/.git/SKIP && + git push --porcelain dst bogus +' + +test_expect_success 'push with receive.fsck.missingEmail=warn' ' + commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && + git push . $commit:refs/heads/bogus && + rm -rf dst && + git init dst && + git --git-dir=dst/.git config receive.fsckobjects true && + test_must_fail git push --porcelain dst bogus && + git --git-dir=dst/.git config \ + receive.fsck.missingEmail warn && + git push --porcelain dst bogus >act 2>&1 && + grep "missingEmail" act && + git --git-dir=dst/.git branch -D bogus && + git --git-dir=dst/.git config --add \ + receive.fsck.missingEmail ignore && + git --git-dir=dst/.git config --add \ + receive.fsck.badDate warn && + git push --porcelain dst bogus >act 2>&1 && + test_must_fail grep "missingEmail" act +' + +test_expect_success \ + 'receive.fsck.unterminatedHeader=warn triggers error' ' + rm -rf dst && + git init dst && + git --git-dir=dst/.git config receive.fsckobjects true && + git --git-dir=dst/.git config \ + receive.fsck.unterminatedheader warn && + test_must_fail git push --porcelain dst HEAD >act 2>&1 && + grep "Cannot demote unterminatedheader" act +' + test_done |