prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Bug: base manifest value for 'exclude' is always ignored
  I stumbled upon this bug while working on #226: the culprit is [`Repository.hooks`](https://github.com/pre-commit/pre-commit/blob/master/pre_commit/repository.py#L48).
  
  A quick fix for this would be to simply remove the default value from `pre_commit/clientlib/validate_config.py`, but the root cause is that any default value defined for a field in this file will make the corresponding manifest field useless.
  
  Basically here is what happens in `Repository.hooks`:
  - all the hooks defined in the current repository are enumerated
  - at this stage, a `hook` is a dict closely matching the Yaml the config file content, **plus** default values for fields not defined in the Yaml but having a JSON schema 'default'
  - when doing the dict merge, **every** (key,value) pair in `hook` overrides the corresponding manifest entry. This includes default config value like `exclude: '$^'` overriding a base manifest value like `exclude: '.bak$'`
  
  Hence I suggest either adding a test ensuring there will never be any 'default' defined in `CONFIG_JSON_SCHEMA`, or improving the merge logic.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `pre_commit/clientlib/validate_config.py`
  Content:
  ```
  1 from __future__ import unicode_literals
  2 
  3 from pre_commit.clientlib.validate_base import get_run_function
  4 from pre_commit.clientlib.validate_base import get_validator
  5 from pre_commit.clientlib.validate_base import is_regex_valid
  6 from pre_commit.errors import FatalError
  7 
  8 
  9 _LOCAL_HOOKS_MAGIC_REPO_STRING = 'local'
  10 
  11 
  12 def is_local_hooks(repo_entry):
  13     return repo_entry['repo'] == _LOCAL_HOOKS_MAGIC_REPO_STRING
  14 
  15 
  16 class InvalidConfigError(FatalError):
  17     pass
  18 
  19 
  20 CONFIG_JSON_SCHEMA = {
  21     'type': 'array',
  22     'minItems': 1,
  23     'items': {
  24         'type': 'object',
  25         'properties': {
  26             'repo': {'type': 'string'},
  27             'sha': {'type': 'string'},
  28             'hooks': {
  29                 'type': 'array',
  30                 'minItems': 1,
  31                 'items': {
  32                     'type': 'object',
  33                     'properties': {
  34                         'id': {'type': 'string'},
  35                         'files': {'type': 'string'},
  36                         'exclude': {'type': 'string', 'default': '^$'},
  37                         'language_version': {'type': 'string'},
  38                         'args': {
  39                             'type': 'array',
  40                             'items': {'type': 'string'},
  41                         },
  42                     },
  43                     'required': ['id'],
  44                 }
  45             }
  46         },
  47         'required': ['repo', 'hooks'],
  48     }
  49 }
  50 
  51 
  52 def try_regex(repo, hook, value, field_name):
  53     if not is_regex_valid(value):
  54         raise InvalidConfigError(
  55             'Invalid {0} regex at {1}, {2}: {3}'.format(
  56                 field_name, repo, hook, value,
  57             )
  58         )
  59 
  60 
  61 def validate_config_extra(config):
  62     for repo in config:
  63         if is_local_hooks(repo):
  64             if 'sha' in repo:
  65                 raise InvalidConfigError(
  66                     '"sha" property provided for local hooks'
  67                 )
  68         elif 'sha' not in repo:
  69             raise InvalidConfigError(
  70                 'Missing "sha" field for repository {0}'.format(repo['repo'])
  71             )
  72         for hook in repo['hooks']:
  73             try_regex(repo, hook['id'], hook.get('files', ''), 'files')
  74             try_regex(repo, hook['id'], hook['exclude'], 'exclude')
  75 
  76 
  77 load_config = get_validator(
  78     CONFIG_JSON_SCHEMA,
  79     InvalidConfigError,
  80     additional_validation_strategy=validate_config_extra,
  81 )
  82 
  83 
  84 run = get_run_function('Config filenames.', load_config, InvalidConfigError)
  85 
  86 
  87 if __name__ == '__main__':
  88     exit(run())
  89
  ```
  
  Path: `pre_commit/clientlib/validate_manifest.py`
  Content:
  ```
  1 from __future__ import unicode_literals
  2 
  3 from pre_commit.clientlib.validate_base import get_run_function
  4 from pre_commit.clientlib.validate_base import get_validator
  5 from pre_commit.clientlib.validate_base import is_regex_valid
  6 from pre_commit.languages.all import all_languages
  7 
  8 
  9 class InvalidManifestError(ValueError):
  10     pass
  11 
  12 
  13 MANIFEST_JSON_SCHEMA = {
  14     'type': 'array',
  15     'minItems': 1,
  16     'items': {
  17         'type': 'object',
  18         'properties': {
  19             'id': {'type': 'string'},
  20             'name': {'type': 'string'},
  21             'description': {'type': 'string', 'default': ''},
  22             'entry': {'type': 'string'},
  23             'language': {'type': 'string'},
  24             'language_version': {'type': 'string', 'default': 'default'},
  25             'files': {'type': 'string'},
  26             'expected_return_value': {'type': 'number', 'default': 0},
  27             'args': {
  28                 'type': 'array',
  29                 'default': [],
  30                 'items': {
  31                     'type': 'string',
  32                 },
  33             },
  34         },
  35         'required': ['id', 'name', 'entry', 'language', 'files'],
  36     },
  37 }
  38 
  39 
  40 def validate_languages(hook_config):
  41     if hook_config['language'] not in all_languages:
  42         raise InvalidManifestError(
  43             'Expected language {0} for {1} to be one of {2!r}'.format(
  44                 hook_config['id'],
  45                 hook_config['language'],
  46                 all_languages,
  47             )
  48         )
  49 
  50 
  51 def validate_files(hook_config):
  52     if not is_regex_valid(hook_config['files']):
  53         raise InvalidManifestError(
  54             'Invalid files regex at {0}: {1}'.format(
  55                 hook_config['id'],
  56                 hook_config['files'],
  57             )
  58         )
  59 
  60 
  61 def additional_manifest_check(obj):
  62     for hook_config in obj:
  63         validate_languages(hook_config)
  64         validate_files(hook_config)
  65 
  66 
  67 load_manifest = get_validator(
  68     MANIFEST_JSON_SCHEMA,
  69     InvalidManifestError,
  70     additional_manifest_check,
  71 )
  72 
  73 
  74 run = get_run_function(
  75     'Manifest filenames.',
  76     load_manifest,
  77     InvalidManifestError,
  78 )
  79 
  80 
  81 if __name__ == '__main__':
  82     exit(run())
  83
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/pre_commit/clientlib/validate_config.py b/pre_commit/clientlib/validate_config.py
  --- a/pre_commit/clientlib/validate_config.py
  +++ b/pre_commit/clientlib/validate_config.py
  @@ -33,7 +33,7 @@
                       'properties': {
                           'id': {'type': 'string'},
                           'files': {'type': 'string'},
  -                        'exclude': {'type': 'string', 'default': '^$'},
  +                        'exclude': {'type': 'string'},
                           'language_version': {'type': 'string'},
                           'args': {
                               'type': 'array',
  @@ -71,7 +71,7 @@
               )
           for hook in repo['hooks']:
               try_regex(repo, hook['id'], hook.get('files', ''), 'files')
  -            try_regex(repo, hook['id'], hook['exclude'], 'exclude')
  +            try_regex(repo, hook['id'], hook.get('exclude', ''), 'exclude')
   
   
   load_config = get_validator(
  diff --git a/pre_commit/clientlib/validate_manifest.py b/pre_commit/clientlib/validate_manifest.py
  --- a/pre_commit/clientlib/validate_manifest.py
  +++ b/pre_commit/clientlib/validate_manifest.py
  @@ -20,6 +20,7 @@
               'name': {'type': 'string'},
               'description': {'type': 'string', 'default': ''},
               'entry': {'type': 'string'},
  +            'exclude': {'type': 'string', 'default': '^$'},
               'language': {'type': 'string'},
               'language_version': {'type': 'string', 'default': 'default'},
               'files': {'type': 'string'},
  @@ -52,8 +53,14 @@
       if not is_regex_valid(hook_config['files']):
           raise InvalidManifestError(
               'Invalid files regex at {0}: {1}'.format(
  -                hook_config['id'],
  -                hook_config['files'],
  +                hook_config['id'], hook_config['files'],
  +            )
  +        )
  +
  +    if not is_regex_valid(hook_config.get('exclude', '')):
  +        raise InvalidManifestError(
  +            'Invalid exclude regex at {0}: {1}'.format(
  +                hook_config['id'], hook_config['exclude'],
               )
           )
  
verification_info: {"golden_diff": "diff --git a/pre_commit/clientlib/validate_config.py b/pre_commit/clientlib/validate_config.py\n--- a/pre_commit/clientlib/validate_config.py\n+++ b/pre_commit/clientlib/validate_config.py\n@@ -33,7 +33,7 @@\n                     'properties': {\n                         'id': {'type': 'string'},\n                         'files': {'type': 'string'},\n-                        'exclude': {'type': 'string', 'default': '^$'},\n+                        'exclude': {'type': 'string'},\n                         'language_version': {'type': 'string'},\n                         'args': {\n                             'type': 'array',\n@@ -71,7 +71,7 @@\n             )\n         for hook in repo['hooks']:\n             try_regex(repo, hook['id'], hook.get('files', ''), 'files')\n-            try_regex(repo, hook['id'], hook['exclude'], 'exclude')\n+            try_regex(repo, hook['id'], hook.get('exclude', ''), 'exclude')\n \n \n load_config = get_validator(\ndiff --git a/pre_commit/clientlib/validate_manifest.py b/pre_commit/clientlib/validate_manifest.py\n--- a/pre_commit/clientlib/validate_manifest.py\n+++ b/pre_commit/clientlib/validate_manifest.py\n@@ -20,6 +20,7 @@\n             'name': {'type': 'string'},\n             'description': {'type': 'string', 'default': ''},\n             'entry': {'type': 'string'},\n+            'exclude': {'type': 'string', 'default': '^$'},\n             'language': {'type': 'string'},\n             'language_version': {'type': 'string', 'default': 'default'},\n             'files': {'type': 'string'},\n@@ -52,8 +53,14 @@\n     if not is_regex_valid(hook_config['files']):\n         raise InvalidManifestError(\n             'Invalid files regex at {0}: {1}'.format(\n-                hook_config['id'],\n-                hook_config['files'],\n+                hook_config['id'], hook_config['files'],\n+            )\n+        )\n+\n+    if not is_regex_valid(hook_config.get('exclude', '')):\n+        raise InvalidManifestError(\n+            'Invalid exclude regex at {0}: {1}'.format(\n+                hook_config['id'], hook_config['exclude'],\n             )\n         )\n", "issue": "Bug: base manifest value for 'exclude' is always ignored\nI stumbled upon this bug while working on #226: the culprit is [`Repository.hooks`](https://github.com/pre-commit/pre-commit/blob/master/pre_commit/repository.py#L48).\n\nA quick fix for this would be to simply remove the default value from `pre_commit/clientlib/validate_config.py`, but the root cause is that any default value defined for a field in this file will make the corresponding manifest field useless.\n\nBasically here is what happens in `Repository.hooks`:\n- all the hooks defined in the current repository are enumerated\n- at this stage, a `hook` is a dict closely matching the Yaml the config file content, **plus** default values for fields not defined in the Yaml but having a JSON schema 'default'\n- when doing the dict merge, **every** (key,value) pair in `hook` overrides the corresponding manifest entry. This includes default config value like `exclude: '$^'` overriding a base manifest value like `exclude: '.bak$'`\n\nHence I suggest either adding a test ensuring there will never be any 'default' defined in `CONFIG_JSON_SCHEMA`, or improving the merge logic.\n\n", "before_files": [{"content": "from __future__ import unicode_literals\n\nfrom pre_commit.clientlib.validate_base import get_run_function\nfrom pre_commit.clientlib.validate_base import get_validator\nfrom pre_commit.clientlib.validate_base import is_regex_valid\nfrom pre_commit.errors import FatalError\n\n\n_LOCAL_HOOKS_MAGIC_REPO_STRING = 'local'\n\n\ndef is_local_hooks(repo_entry):\n    return repo_entry['repo'] == _LOCAL_HOOKS_MAGIC_REPO_STRING\n\n\nclass InvalidConfigError(FatalError):\n    pass\n\n\nCONFIG_JSON_SCHEMA = {\n    'type': 'array',\n    'minItems': 1,\n    'items': {\n        'type': 'object',\n        'properties': {\n            'repo': {'type': 'string'},\n            'sha': {'type': 'string'},\n            'hooks': {\n                'type': 'array',\n                'minItems': 1,\n                'items': {\n                    'type': 'object',\n                    'properties': {\n                        'id': {'type': 'string'},\n                        'files': {'type': 'string'},\n                        'exclude': {'type': 'string', 'default': '^$'},\n                        'language_version': {'type': 'string'},\n                        'args': {\n                            'type': 'array',\n                            'items': {'type': 'string'},\n                        },\n                    },\n                    'required': ['id'],\n                }\n            }\n        },\n        'required': ['repo', 'hooks'],\n    }\n}\n\n\ndef try_regex(repo, hook, value, field_name):\n    if not is_regex_valid(value):\n        raise InvalidConfigError(\n            'Invalid {0} regex at {1}, {2}: {3}'.format(\n                field_name, repo, hook, value,\n            )\n        )\n\n\ndef validate_config_extra(config):\n    for repo in config:\n        if is_local_hooks(repo):\n            if 'sha' in repo:\n                raise InvalidConfigError(\n                    '\"sha\" property provided for local hooks'\n                )\n        elif 'sha' not in repo:\n            raise InvalidConfigError(\n                'Missing \"sha\" field for repository {0}'.format(repo['repo'])\n            )\n        for hook in repo['hooks']:\n            try_regex(repo, hook['id'], hook.get('files', ''), 'files')\n            try_regex(repo, hook['id'], hook['exclude'], 'exclude')\n\n\nload_config = get_validator(\n    CONFIG_JSON_SCHEMA,\n    InvalidConfigError,\n    additional_validation_strategy=validate_config_extra,\n)\n\n\nrun = get_run_function('Config filenames.', load_config, InvalidConfigError)\n\n\nif __name__ == '__main__':\n    exit(run())\n", "path": "pre_commit/clientlib/validate_config.py"}, {"content": "from __future__ import unicode_literals\n\nfrom pre_commit.clientlib.validate_base import get_run_function\nfrom pre_commit.clientlib.validate_base import get_validator\nfrom pre_commit.clientlib.validate_base import is_regex_valid\nfrom pre_commit.languages.all import all_languages\n\n\nclass InvalidManifestError(ValueError):\n    pass\n\n\nMANIFEST_JSON_SCHEMA = {\n    'type': 'array',\n    'minItems': 1,\n    'items': {\n        'type': 'object',\n        'properties': {\n            'id': {'type': 'string'},\n            'name': {'type': 'string'},\n            'description': {'type': 'string', 'default': ''},\n            'entry': {'type': 'string'},\n            'language': {'type': 'string'},\n            'language_version': {'type': 'string', 'default': 'default'},\n            'files': {'type': 'string'},\n            'expected_return_value': {'type': 'number', 'default': 0},\n            'args': {\n                'type': 'array',\n                'default': [],\n                'items': {\n                    'type': 'string',\n                },\n            },\n        },\n        'required': ['id', 'name', 'entry', 'language', 'files'],\n    },\n}\n\n\ndef validate_languages(hook_config):\n    if hook_config['language'] not in all_languages:\n        raise InvalidManifestError(\n            'Expected language {0} for {1} to be one of {2!r}'.format(\n                hook_config['id'],\n                hook_config['language'],\n                all_languages,\n            )\n        )\n\n\ndef validate_files(hook_config):\n    if not is_regex_valid(hook_config['files']):\n        raise InvalidManifestError(\n            'Invalid files regex at {0}: {1}'.format(\n                hook_config['id'],\n                hook_config['files'],\n            )\n        )\n\n\ndef additional_manifest_check(obj):\n    for hook_config in obj:\n        validate_languages(hook_config)\n        validate_files(hook_config)\n\n\nload_manifest = get_validator(\n    MANIFEST_JSON_SCHEMA,\n    InvalidManifestError,\n    additional_manifest_check,\n)\n\n\nrun = get_run_function(\n    'Manifest filenames.',\n    load_manifest,\n    InvalidManifestError,\n)\n\n\nif __name__ == '__main__':\n    exit(run())\n", "path": "pre_commit/clientlib/validate_manifest.py"}], "after_files": [{"content": "from __future__ import unicode_literals\n\nfrom pre_commit.clientlib.validate_base import get_run_function\nfrom pre_commit.clientlib.validate_base import get_validator\nfrom pre_commit.clientlib.validate_base import is_regex_valid\nfrom pre_commit.errors import FatalError\n\n\n_LOCAL_HOOKS_MAGIC_REPO_STRING = 'local'\n\n\ndef is_local_hooks(repo_entry):\n    return repo_entry['repo'] == _LOCAL_HOOKS_MAGIC_REPO_STRING\n\n\nclass InvalidConfigError(FatalError):\n    pass\n\n\nCONFIG_JSON_SCHEMA = {\n    'type': 'array',\n    'minItems': 1,\n    'items': {\n        'type': 'object',\n        'properties': {\n            'repo': {'type': 'string'},\n            'sha': {'type': 'string'},\n            'hooks': {\n                'type': 'array',\n                'minItems': 1,\n                'items': {\n                    'type': 'object',\n                    'properties': {\n                        'id': {'type': 'string'},\n                        'files': {'type': 'string'},\n                        'exclude': {'type': 'string'},\n                        'language_version': {'type': 'string'},\n                        'args': {\n                            'type': 'array',\n                            'items': {'type': 'string'},\n                        },\n                    },\n                    'required': ['id'],\n                }\n            }\n        },\n        'required': ['repo', 'hooks'],\n    }\n}\n\n\ndef try_regex(repo, hook, value, field_name):\n    if not is_regex_valid(value):\n        raise InvalidConfigError(\n            'Invalid {0} regex at {1}, {2}: {3}'.format(\n                field_name, repo, hook, value,\n            )\n        )\n\n\ndef validate_config_extra(config):\n    for repo in config:\n        if is_local_hooks(repo):\n            if 'sha' in repo:\n                raise InvalidConfigError(\n                    '\"sha\" property provided for local hooks'\n                )\n        elif 'sha' not in repo:\n            raise InvalidConfigError(\n                'Missing \"sha\" field for repository {0}'.format(repo['repo'])\n            )\n        for hook in repo['hooks']:\n            try_regex(repo, hook['id'], hook.get('files', ''), 'files')\n            try_regex(repo, hook['id'], hook.get('exclude', ''), 'exclude')\n\n\nload_config = get_validator(\n    CONFIG_JSON_SCHEMA,\n    InvalidConfigError,\n    additional_validation_strategy=validate_config_extra,\n)\n\n\nrun = get_run_function('Config filenames.', load_config, InvalidConfigError)\n\n\nif __name__ == '__main__':\n    exit(run())\n", "path": "pre_commit/clientlib/validate_config.py"}, {"content": "from __future__ import unicode_literals\n\nfrom pre_commit.clientlib.validate_base import get_run_function\nfrom pre_commit.clientlib.validate_base import get_validator\nfrom pre_commit.clientlib.validate_base import is_regex_valid\nfrom pre_commit.languages.all import all_languages\n\n\nclass InvalidManifestError(ValueError):\n    pass\n\n\nMANIFEST_JSON_SCHEMA = {\n    'type': 'array',\n    'minItems': 1,\n    'items': {\n        'type': 'object',\n        'properties': {\n            'id': {'type': 'string'},\n            'name': {'type': 'string'},\n            'description': {'type': 'string', 'default': ''},\n            'entry': {'type': 'string'},\n            'exclude': {'type': 'string', 'default': '^$'},\n            'language': {'type': 'string'},\n            'language_version': {'type': 'string', 'default': 'default'},\n            'files': {'type': 'string'},\n            'expected_return_value': {'type': 'number', 'default': 0},\n            'args': {\n                'type': 'array',\n                'default': [],\n                'items': {\n                    'type': 'string',\n                },\n            },\n        },\n        'required': ['id', 'name', 'entry', 'language', 'files'],\n    },\n}\n\n\ndef validate_languages(hook_config):\n    if hook_config['language'] not in all_languages:\n        raise InvalidManifestError(\n            'Expected language {0} for {1} to be one of {2!r}'.format(\n                hook_config['id'],\n                hook_config['language'],\n                all_languages,\n            )\n        )\n\n\ndef validate_files(hook_config):\n    if not is_regex_valid(hook_config['files']):\n        raise InvalidManifestError(\n            'Invalid files regex at {0}: {1}'.format(\n                hook_config['id'], hook_config['files'],\n            )\n        )\n\n    if not is_regex_valid(hook_config.get('exclude', '')):\n        raise InvalidManifestError(\n            'Invalid exclude regex at {0}: {1}'.format(\n                hook_config['id'], hook_config['exclude'],\n            )\n        )\n\n\ndef additional_manifest_check(obj):\n    for hook_config in obj:\n        validate_languages(hook_config)\n        validate_files(hook_config)\n\n\nload_manifest = get_validator(\n    MANIFEST_JSON_SCHEMA,\n    InvalidManifestError,\n    additional_manifest_check,\n)\n\n\nrun = get_run_function(\n    'Manifest filenames.',\n    load_manifest,\n    InvalidManifestError,\n)\n\n\nif __name__ == '__main__':\n    exit(run())\n", "path": "pre_commit/clientlib/validate_manifest.py"}]}