At the moment, the git-fsck's integrity checks are targeted toward the
end user, i.e. the error messages are really just messages, intended for
human consumption.

Under certain circumstances, some of those errors should be allowed to
be turned into mere warnings, though, because the cost of fixing the
issues might well be larger than the cost of carrying those flawed
objects. For example, when an already-public repository contains a
commit object with two authors for years, it does not make sense to
force the maintainer to rewrite the history, affecting all contributors
negatively by forcing them to update.

This branch introduces an internal fsck API to be able to turn some of
the errors into warnings, and to make it easier to call the fsck
machinery from elsewhere in general.

I am proud to report that this work has been sponsored by GitHub.

Changes vs v3: address Junio's concern regarding repeated patterns,
heed Peff's advice to avoid that pesky clang warning re: unsigned enums,
use the fsck.skiplist setting in builtin/fsck.c (not
receive.fsck.skiplist), and switch to fsck.severity to address Michael's
concerns that letting fsck.(error|warn|ignore)'s comma-separated lists
possibly overriding each other partially; interdiff below the diffstat.

Johannes Schindelin (19):
  fsck: Introduce fsck options
  fsck: Introduce identifiers for fsck messages
  fsck: Provide a function to parse fsck message IDs
  fsck: Offer a function to demote fsck errors to warnings
  fsck: Allow demoting errors to warnings
  fsck: Report the ID of the error/warning
  fsck: Make fsck_ident() warn-friendly
  fsck: Make fsck_commit() warn-friendly
  fsck: Handle multiple authors in commits specially
  fsck: Make fsck_tag() warn-friendly
  fsck: Add a simple test for receive.fsck.severity
  fsck: Disallow demoting grave fsck errors to warnings
  fsck: Optionally ignore specific fsck issues completely
  fsck: Allow upgrading fsck warnings to errors
  fsck: Document the new receive.fsck.severity options.
  fsck: Support demoting errors to warnings
  fsck: Introduce `git fsck --quick`
  fsck: git receive-pack: support excluding objects from fsck'ing
  fsck: support ignoring objects in `git fsck` via fsck.skiplist

 Documentation/config.txt        |  41 ++++
 Documentation/git-fsck.txt      |   7 +-
 builtin/fsck.c                  |  73 ++++--
 builtin/index-pack.c            |  13 +-
 builtin/receive-pack.c          |  21 +-
 builtin/unpack-objects.c        |  16 +-
 fsck.c                          | 532 +++++++++++++++++++++++++++++++---------
 fsck.h                          |  27 +-
 t/t1450-fsck.sh                 |  37 ++-
 t/t5302-pack-index.sh           |   2 +-
 t/t5504-fetch-receive-strict.sh |  49 ++++
 11 files changed, 656 insertions(+), 162 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 644411a..93c43d5 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1208,17 +1208,14 @@ filter.<driver>.smudge::
        object to a worktree file upon checkout.  See
        linkgit:gitattributes[5] for details.
 
-fsck.error::
-fsck.warn::
-fsck.ignore::
-       The `fsck.error`, `fsck.warn` and `fsck.ignore` settings specify
-       comma-separated lists of fsck message IDs which should trigger
-       fsck to error out, to print the message and continue, or to ignore
-       said messages, respectively.
+fsck.severity::
+       A comma-separated lists of of the form `<id>=<level>` where `<id>`
+       denotes a fsck message ID such as `missing-email` and `<level>` is
+       one of `error`, `warn` and `ignore`.
 +
-For convenience, fsck prefixes the error/warning with the name of the option,
+For convenience, fsck prefixes the error/warning with the message ID,
 e.g.  "missing-email: invalid author/committer line - missing email" means
-that setting `fsck.ignore = missing-email` will hide that issue.
+that setting `fsck.severity = missing-email=ignore` will hide that issue.
 +
 This feature is intended to support working with legacy repositories
 which cannot be repaired without disruptive changes.
@@ -2152,28 +2149,15 @@ receive.fsckObjects::
        Defaults to false. If not set, the value of `transfer.fsckObjects`
        is used instead.
 
-receive.fsck.error::
-receive.fsck.warn::
-receive.fsck.ignore::
+receive.fsck.severity::
        When `receive.fsckObjects` is set to true, errors can be switched
-       to warnings and vice versa by configuring the `receive.fsck.*`
-       settings. These settings contain comma-separated lists of fsck
-       message IDs. For convenience, fsck prefixes the error/warning with
-       the message ID, e.g. "missing-email: invalid
+       to warnings and vice versa by configuring the `receive.fsck.severity`
+       setting. These settings contain comma-separated lists of the form
+       `<id>=<level>` where the `<id>` is the fsck message ID and the level
+       is one of `error`, `warn` or `ignore`. For convenience, fsck prefixes
+       the error/warning with the message ID, e.g. "missing-email: invalid
        author/committer line - missing email" means that setting
-       `receive.fsck.ignore = missing-email` will hide that issue.
-+
---
-error;;
-       a comma-separated list of fsck message IDs that should be
-       trigger fsck to error out.
-warn;;
-       a comma-separated list of fsck message IDs that should be
-       displayed, but fsck should continue to error out.
-ignore;;
-       a comma-separated list of fsck message IDs that should be
-       ignored completely.
---
+       `receive.fsck.severity = missing-email=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
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 7eb4ff8..81570d8 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -49,7 +49,12 @@ static int show_dangling = 1;
 
 static int fsck_config(const char *var, const char *value, void *cb)
 {
-       if (strcmp(var, "receive.fsck.skiplist") == 0) {
+       if (strcmp(var, "fsck.severity") == 0) {
+               fsck_set_severity(&fsck_obj_options, value);
+               return 0;
+       }
+
+       if (strcmp(var, "fsck.skiplist") == 0) {
                const char *path = is_absolute_path(value) ?
                        value : git_path("%s", value);
                struct strbuf sb = STRBUF_INIT;
@@ -59,14 +64,6 @@ static int fsck_config(const char *var, const char *value, 
void *cb)
                return 0;
        }
 
-       if (skip_prefix(var, "fsck.", &var)) {
-               struct strbuf sb = STRBUF_INIT;
-               strbuf_addf(&sb, "%s=%s", var, value);
-               fsck_set_severity(&fsck_obj_options, sb.buf);
-               strbuf_release(&sb);
-               return 0;
-       }
-
        return git_default_config(var, value, cb);
 }
 
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 8e6d1a1..f454e65 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -116,18 +116,17 @@ static int receive_pack_config(const char *var, const 
char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "receive.fsck.severity") == 0) {
+               strbuf_addf(&fsck_severity, "%c%s",
+                       fsck_severity.len ? ',' : '=', value);
+               return 0;
+       }
+
        if (strcmp(var, "receive.fsck.skiplist") == 0) {
                const char *path = is_absolute_path(value) ?
                        value : git_path("%s", value);
-               if (fsck_severity.len)
-                       strbuf_addch(&fsck_severity, ',');
-               strbuf_addf(&fsck_severity, "skiplist=%s", path);
-               return 0;
-       }
-
-       if (skip_prefix(var, "receive.fsck.", &var)) {
-               strbuf_addf(&fsck_severity, "%s%s=%s",
-                       fsck_severity.len ? "," : "", var, value);
+               strbuf_addf(&fsck_severity, "%cskiplist=%s",
+                       fsck_severity.len ? ',' : '=', path);
                return 0;
        }
 
@@ -1487,8 +1486,7 @@ static const char *unpack(int err_fd, struct shallow_info 
*si)
                if (quiet)
                        argv_array_push(&child.args, "-q");
                if (fsck_objects)
-                       argv_array_pushf(&child.args, "--strict%s%s",
-                               fsck_severity.len ? "=" : "",
+                       argv_array_pushf(&child.args, "--strict%s",
                                fsck_severity.buf);
                child.no_stdout = 1;
                child.err = err_fd;
@@ -1507,8 +1505,7 @@ 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_pushf(&child.args, "--strict%s%s",
-                               fsck_severity.len ? "=" : "",
+                       argv_array_pushf(&child.args, "--strict%s",
                                fsck_severity.buf);
                if (fix_thin)
                        argv_array_push(&child.args, "--fix-thin");
diff --git a/fsck.c b/fsck.c
index 15cb8bd..046af02 100644
--- a/fsck.c
+++ b/fsck.c
@@ -107,7 +107,9 @@ static int fsck_msg_severity(enum fsck_msg_id msg_id,
 {
        int severity;
 
-       if (options->msg_severity && msg_id >= 0 && msg_id < FSCK_MSG_MAX)
+       assert(msg_id >= 0 && msg_id < FSCK_MSG_MAX);
+
+       if (options->msg_severity)
                severity = options->msg_severity[msg_id];
        else {
                severity = msg_id_info[msg_id].severity;
@@ -184,35 +186,40 @@ void fsck_set_severity(struct fsck_options *options, 
const char *mode)
                }
 
                for (equal = 0; equal < len; equal++)
-                       if (mode[equal] == '=')
+                       if (mode[equal] == '=' || mode[equal] == ':')
                                break;
 
-               if (equal < len) {
-                       if (!substrcmp(mode, equal, "error"))
+               if (!substrcmp(mode, equal, "skiplist")) {
+                       char *path = xstrndup(mode + equal + 1,
+                               len - equal - 1);
+
+                       if (equal == len)
+                               die("skiplist requires a path");
+                       init_skiplist(options, path);
+                       free(path);
+                       mode += len;
+                       continue;
+               }
+
+               msg_id = parse_msg_id(mode, equal);
+
+               if (equal == len)
+                       severity = FSCK_ERROR;
+               else {
+                       const char *p = mode + equal + 1;
+                       int len2 = len - equal - 1;
+
+                       if (!substrcmp(p, len2, "error"))
                                severity = FSCK_ERROR;
-                       else if (!substrcmp(mode, equal, "warn"))
+                       else if (!substrcmp(p, len2, "warn"))
                                severity = FSCK_WARN;
-                       else if (!substrcmp(mode, equal, "ignore"))
+                       else if (!substrcmp(p, len2, "ignore"))
                                severity = FSCK_IGNORE;
-                       else if (!substrcmp(mode, equal, "skiplist")) {
-                               char *path = xstrndup(mode + equal + 1,
-                                       len - equal - 1);
-
-                               if (equal == len)
-                                       die("skiplist requires a path");
-                               init_skiplist(options, path);
-                               free(path);
-                               mode += len;
-                               continue;
-                       }
                        else
                                die("Unknown fsck message severity: '%.*s'",
-                                       equal, mode);
-                       mode += equal + 1;
-                       len -= equal + 1;
+                                       len2, p);
                }
 
-               msg_id = parse_msg_id(mode, len);
                if (severity != FSCK_ERROR &&
                                msg_id_info[msg_id].severity == FSCK_FATAL)
                        die("Cannot demote %.*s", len, mode);
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 1c624a3..b32afaf 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -295,7 +295,7 @@ test_expect_success 'force fsck to ignore double author' '
        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.ignore=multiple-authors fsck
+       git -c fsck.severity=multiple-authors=ignore fsck
 '
 
 _bz='\0'
diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh
index d367bb2..7881e17 100755
--- a/t/t5504-fetch-receive-strict.sh
+++ b/t/t5504-fetch-receive-strict.sh
@@ -135,28 +135,31 @@ test_expect_success 'push with receive.fsck.skiplist' '
        git push --porcelain dst bogus
 '
 
-test_expect_success 'push with receive.fsck.warn = missing-email' '
+test_expect_success 'push with receive.fsck.severity = missing-email=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.warn missing-email &&
+       git --git-dir=dst/.git config \
+               receive.fsck.severity missing-email=warn &&
        git push --porcelain dst bogus >act 2>&1 &&
        grep "missing-email" act &&
        git --git-dir=dst/.git branch -D bogus &&
-       git  --git-dir=dst/.git config receive.fsck.ignore missing-email &&
-       git  --git-dir=dst/.git config receive.fsck.warn bad-date &&
+       git  --git-dir=dst/.git config --add \
+               receive.fsck.severity missing-email=ignore,bad-date=warn &&
        git push --porcelain dst bogus >act 2>&1 &&
        test_must_fail grep "missing-email" act
 '
 
-test_expect_success 'receive.fsck.warn = unterminated-header triggers error' '
+test_expect_success \
+       'receive.fsck.severity = unterminated-header=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.warn unterminated-header &&
+       git --git-dir=dst/.git config \
+               receive.fsck.severity unterminated-header=warn &&
        test_must_fail git push --porcelain dst HEAD >act 2>&1 &&
        grep "Cannot demote unterminated-header" act
 '
-- 
2.2.0.33.gc18b867


--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to