We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Run hooks on files in specific dir, rather than using `--all-files`
I am able to successfully use `pre-commit run --all-files` to run hooks on all my files.

Now, I'm trying to run the hooks just on files in a particular directory. I think I'm not understanding the docs and I can't find an example to work from.

Here's what the docs say:
> `--files [FILES [FILES ...]]`: specific filenames to run hooks on.

I've tried the following variations:
`pre-commit run --files web/modules/custom`
`pre-commit run --files web/modules/custom/*`
`pre-commit run --files [web/modules/custom]`
`pre-commit run --files [web/modules/custom/*]`
`pre-commit run --files [FILES [web/modules/custom]`
`pre-commit run --files [FILES [web/modules/custom/*]`

I feel really dumb having to ask, but can someone please point me in the right direction?

--- 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/commands/run.py`
Content:
```
1 from __future__ import unicode_literals
2 
3 import logging
4 import os
5 import re
6 import subprocess
7 import sys
8 
9 from identify.identify import tags_from_path
10 
11 from pre_commit import color
12 from pre_commit import git
13 from pre_commit import output
14 from pre_commit.clientlib import load_config
15 from pre_commit.output import get_hook_message
16 from pre_commit.repository import all_hooks
17 from pre_commit.repository import install_hook_envs
18 from pre_commit.staged_files_only import staged_files_only
19 from pre_commit.util import cmd_output_b
20 from pre_commit.util import noop_context
21 
22 
23 logger = logging.getLogger('pre_commit')
24 
25 
26 def filter_by_include_exclude(names, include, exclude):
27     include_re, exclude_re = re.compile(include), re.compile(exclude)
28     return [
29         filename for filename in names
30         if include_re.search(filename)
31         if not exclude_re.search(filename)
32     ]
33 
34 
35 class Classifier(object):
36     def __init__(self, filenames):
37         self.filenames = [f for f in filenames if os.path.lexists(f)]
38         self._types_cache = {}
39 
40     def _types_for_file(self, filename):
41         try:
42             return self._types_cache[filename]
43         except KeyError:
44             ret = self._types_cache[filename] = tags_from_path(filename)
45             return ret
46 
47     def by_types(self, names, types, exclude_types):
48         types, exclude_types = frozenset(types), frozenset(exclude_types)
49         ret = []
50         for filename in names:
51             tags = self._types_for_file(filename)
52             if tags >= types and not tags & exclude_types:
53                 ret.append(filename)
54         return ret
55 
56     def filenames_for_hook(self, hook):
57         names = self.filenames
58         names = filter_by_include_exclude(names, hook.files, hook.exclude)
59         names = self.by_types(names, hook.types, hook.exclude_types)
60         return names
61 
62 
63 def _get_skips(environ):
64     skips = environ.get('SKIP', '')
65     return {skip.strip() for skip in skips.split(',') if skip.strip()}
66 
67 
68 def _hook_msg_start(hook, verbose):
69     return '{}{}'.format('[{}] '.format(hook.id) if verbose else '', hook.name)
70 
71 
72 SKIPPED = 'Skipped'
73 NO_FILES = '(no files to check)'
74 
75 
76 def _run_single_hook(classifier, hook, args, skips, cols, use_color):
77     filenames = classifier.filenames_for_hook(hook)
78 
79     if hook.language == 'pcre':
80         logger.warning(
81             '`{}` (from {}) uses the deprecated pcre language.\n'
82             'The pcre language is scheduled for removal in pre-commit 2.x.\n'
83             'The pygrep language is a more portable (and usually drop-in) '
84             'replacement.'.format(hook.id, hook.src),
85         )
86 
87     if hook.id in skips or hook.alias in skips:
88         output.write(
89             get_hook_message(
90                 _hook_msg_start(hook, args.verbose),
91                 end_msg=SKIPPED,
92                 end_color=color.YELLOW,
93                 use_color=args.color,
94                 cols=cols,
95             ),
96         )
97         return 0
98     elif not filenames and not hook.always_run:
99         output.write(
100             get_hook_message(
101                 _hook_msg_start(hook, args.verbose),
102                 postfix=NO_FILES,
103                 end_msg=SKIPPED,
104                 end_color=color.TURQUOISE,
105                 use_color=args.color,
106                 cols=cols,
107             ),
108         )
109         return 0
110 
111     # Print the hook and the dots first in case the hook takes hella long to
112     # run.
113     output.write(
114         get_hook_message(
115             _hook_msg_start(hook, args.verbose), end_len=6, cols=cols,
116         ),
117     )
118     sys.stdout.flush()
119 
120     diff_before = cmd_output_b('git', 'diff', '--no-ext-diff', retcode=None)
121     filenames = tuple(filenames) if hook.pass_filenames else ()
122     retcode, out = hook.run(filenames, use_color)
123     diff_after = cmd_output_b('git', 'diff', '--no-ext-diff', retcode=None)
124 
125     file_modifications = diff_before != diff_after
126 
127     # If the hook makes changes, fail the commit
128     if file_modifications:
129         retcode = 1
130 
131     if retcode:
132         retcode = 1
133         print_color = color.RED
134         pass_fail = 'Failed'
135     else:
136         retcode = 0
137         print_color = color.GREEN
138         pass_fail = 'Passed'
139 
140     output.write_line(color.format_color(pass_fail, print_color, args.color))
141 
142     if (
143             (out or file_modifications) and
144             (retcode or args.verbose or hook.verbose)
145     ):
146         output.write_line('hookid: {}\n'.format(hook.id))
147 
148         # Print a message if failing due to file modifications
149         if file_modifications:
150             output.write('Files were modified by this hook.')
151 
152             if out:
153                 output.write_line(' Additional output:')
154 
155             output.write_line()
156 
157         if out.strip():
158             output.write_line(out.strip(), logfile_name=hook.log_file)
159         output.write_line()
160 
161     return retcode
162 
163 
164 def _compute_cols(hooks, verbose):
165     """Compute the number of columns to display hook messages.  The widest
166     that will be displayed is in the no files skipped case:
167 
168         Hook name...(no files to check) Skipped
169 
170     or in the verbose case
171 
172         Hook name [hookid]...(no files to check) Skipped
173     """
174     if hooks:
175         name_len = max(len(_hook_msg_start(hook, verbose)) for hook in hooks)
176     else:
177         name_len = 0
178 
179     cols = name_len + 3 + len(NO_FILES) + 1 + len(SKIPPED)
180     return max(cols, 80)
181 
182 
183 def _all_filenames(args):
184     if args.origin and args.source:
185         return git.get_changed_files(args.origin, args.source)
186     elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}:
187         return (args.commit_msg_filename,)
188     elif args.files:
189         return args.files
190     elif args.all_files:
191         return git.get_all_files()
192     elif git.is_in_merge_conflict():
193         return git.get_conflicted_files()
194     else:
195         return git.get_staged_files()
196 
197 
198 def _run_hooks(config, hooks, args, environ):
199     """Actually run the hooks."""
200     skips = _get_skips(environ)
201     cols = _compute_cols(hooks, args.verbose)
202     filenames = _all_filenames(args)
203     filenames = filter_by_include_exclude(filenames, '', config['exclude'])
204     classifier = Classifier(filenames)
205     retval = 0
206     for hook in hooks:
207         retval |= _run_single_hook(
208             classifier, hook, args, skips, cols, args.color,
209         )
210         if retval and config['fail_fast']:
211             break
212     if retval and args.show_diff_on_failure and git.has_diff():
213         if args.all_files:
214             output.write_line(
215                 'pre-commit hook(s) made changes.\n'
216                 'If you are seeing this message in CI, '
217                 'reproduce locally with: `pre-commit run --all-files`.\n'
218                 'To run `pre-commit` as part of git workflow, use '
219                 '`pre-commit install`.',
220             )
221         output.write_line('All changes made by hooks:')
222         # args.color is a boolean.
223         # See user_color function in color.py
224         subprocess.call((
225             'git', '--no-pager', 'diff', '--no-ext-diff',
226             '--color={}'.format({True: 'always', False: 'never'}[args.color]),
227         ))
228 
229     return retval
230 
231 
232 def _has_unmerged_paths():
233     _, stdout, _ = cmd_output_b('git', 'ls-files', '--unmerged')
234     return bool(stdout.strip())
235 
236 
237 def _has_unstaged_config(config_file):
238     retcode, _, _ = cmd_output_b(
239         'git', 'diff', '--no-ext-diff', '--exit-code', config_file,
240         retcode=None,
241     )
242     # be explicit, other git errors don't mean it has an unstaged config.
243     return retcode == 1
244 
245 
246 def run(config_file, store, args, environ=os.environ):
247     no_stash = args.all_files or bool(args.files)
248 
249     # Check if we have unresolved merge conflict files and fail fast.
250     if _has_unmerged_paths():
251         logger.error('Unmerged files.  Resolve before committing.')
252         return 1
253     if bool(args.source) != bool(args.origin):
254         logger.error('Specify both --origin and --source.')
255         return 1
256     if _has_unstaged_config(config_file) and not no_stash:
257         logger.error(
258             'Your pre-commit configuration is unstaged.\n'
259             '`git add {}` to fix this.'.format(config_file),
260         )
261         return 1
262 
263     # Expose origin / source as environment variables for hooks to consume
264     if args.origin and args.source:
265         environ['PRE_COMMIT_ORIGIN'] = args.origin
266         environ['PRE_COMMIT_SOURCE'] = args.source
267 
268     if no_stash:
269         ctx = noop_context()
270     else:
271         ctx = staged_files_only(store.directory)
272 
273     with ctx:
274         config = load_config(config_file)
275         hooks = [
276             hook
277             for hook in all_hooks(config, store)
278             if not args.hook or hook.id == args.hook or hook.alias == args.hook
279             if args.hook_stage in hook.stages
280         ]
281 
282         if args.hook and not hooks:
283             output.write_line(
284                 'No hook with id `{}` in stage `{}`'.format(
285                     args.hook, args.hook_stage,
286                 ),
287             )
288             return 1
289 
290         install_hook_envs(hooks, store)
291 
292         return _run_hooks(config, hooks, args, environ)
293
```
--- 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()

```

diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -34,6 +34,12 @@
 
 class Classifier(object):
     def __init__(self, filenames):
+        # on windows we normalize all filenames to use forward slashes
+        # this makes it easier to filter using the `files:` regex
+        # this also makes improperly quoted shell-based hooks work better
+        # see #1173
+        if os.altsep == '/' and os.sep == '\\':
+            filenames = (f.replace(os.sep, os.altsep) for f in filenames)
         self.filenames = [f for f in filenames if os.path.lexists(f)]
         self._types_cache = {}

{"golden_diff": "diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py\n--- a/pre_commit/commands/run.py\n+++ b/pre_commit/commands/run.py\n@@ -34,6 +34,12 @@\n \n class Classifier(object):\n     def __init__(self, filenames):\n+        # on windows we normalize all filenames to use forward slashes\n+        # this makes it easier to filter using the `files:` regex\n+        # this also makes improperly quoted shell-based hooks work better\n+        # see #1173\n+        if os.altsep == '/' and os.sep == '\\\\':\n+            filenames = (f.replace(os.sep, os.altsep) for f in filenames)\n         self.filenames = [f for f in filenames if os.path.lexists(f)]\n         self._types_cache = {}\n", "issue": "Run hooks on files in specific dir, rather than using `--all-files`\nI am able to successfully use `pre-commit run --all-files` to run hooks on all my files.\r\n\r\nNow, I'm trying to run the hooks just on files in a particular directory. I think I'm not understanding the docs and I can't find an example to work from.\r\n\r\nHere's what the docs say:\r\n> `--files [FILES [FILES ...]]`: specific filenames to run hooks on.\r\n\r\nI've tried the following variations:\r\n`pre-commit run --files web/modules/custom`\r\n`pre-commit run --files web/modules/custom/*`\r\n`pre-commit run --files [web/modules/custom]`\r\n`pre-commit run --files [web/modules/custom/*]`\r\n`pre-commit run --files [FILES [web/modules/custom]`\r\n`pre-commit run --files [FILES [web/modules/custom/*]`\r\n\r\nI feel really dumb having to ask, but can someone please point me in the right direction?\n", "before_files": [{"content": "from __future__ import unicode_literals\n\nimport logging\nimport os\nimport re\nimport subprocess\nimport sys\n\nfrom identify.identify import tags_from_path\n\nfrom pre_commit import color\nfrom pre_commit import git\nfrom pre_commit import output\nfrom pre_commit.clientlib import load_config\nfrom pre_commit.output import get_hook_message\nfrom pre_commit.repository import all_hooks\nfrom pre_commit.repository import install_hook_envs\nfrom pre_commit.staged_files_only import staged_files_only\nfrom pre_commit.util import cmd_output_b\nfrom pre_commit.util import noop_context\n\n\nlogger = logging.getLogger('pre_commit')\n\n\ndef filter_by_include_exclude(names, include, exclude):\n    include_re, exclude_re = re.compile(include), re.compile(exclude)\n    return [\n        filename for filename in names\n        if include_re.search(filename)\n        if not exclude_re.search(filename)\n    ]\n\n\nclass Classifier(object):\n    def __init__(self, filenames):\n        self.filenames = [f for f in filenames if os.path.lexists(f)]\n        self._types_cache = {}\n\n    def _types_for_file(self, filename):\n        try:\n            return self._types_cache[filename]\n        except KeyError:\n            ret = self._types_cache[filename] = tags_from_path(filename)\n            return ret\n\n    def by_types(self, names, types, exclude_types):\n        types, exclude_types = frozenset(types), frozenset(exclude_types)\n        ret = []\n        for filename in names:\n            tags = self._types_for_file(filename)\n            if tags >= types and not tags & exclude_types:\n                ret.append(filename)\n        return ret\n\n    def filenames_for_hook(self, hook):\n        names = self.filenames\n        names = filter_by_include_exclude(names, hook.files, hook.exclude)\n        names = self.by_types(names, hook.types, hook.exclude_types)\n        return names\n\n\ndef _get_skips(environ):\n    skips = environ.get('SKIP', '')\n    return {skip.strip() for skip in skips.split(',') if skip.strip()}\n\n\ndef _hook_msg_start(hook, verbose):\n    return '{}{}'.format('[{}] '.format(hook.id) if verbose else '', hook.name)\n\n\nSKIPPED = 'Skipped'\nNO_FILES = '(no files to check)'\n\n\ndef _run_single_hook(classifier, hook, args, skips, cols, use_color):\n    filenames = classifier.filenames_for_hook(hook)\n\n    if hook.language == 'pcre':\n        logger.warning(\n            '`{}` (from {}) uses the deprecated pcre language.\\n'\n            'The pcre language is scheduled for removal in pre-commit 2.x.\\n'\n            'The pygrep language is a more portable (and usually drop-in) '\n            'replacement.'.format(hook.id, hook.src),\n        )\n\n    if hook.id in skips or hook.alias in skips:\n        output.write(\n            get_hook_message(\n                _hook_msg_start(hook, args.verbose),\n                end_msg=SKIPPED,\n                end_color=color.YELLOW,\n                use_color=args.color,\n                cols=cols,\n            ),\n        )\n        return 0\n    elif not filenames and not hook.always_run:\n        output.write(\n            get_hook_message(\n                _hook_msg_start(hook, args.verbose),\n                postfix=NO_FILES,\n                end_msg=SKIPPED,\n                end_color=color.TURQUOISE,\n                use_color=args.color,\n                cols=cols,\n            ),\n        )\n        return 0\n\n    # Print the hook and the dots first in case the hook takes hella long to\n    # run.\n    output.write(\n        get_hook_message(\n            _hook_msg_start(hook, args.verbose), end_len=6, cols=cols,\n        ),\n    )\n    sys.stdout.flush()\n\n    diff_before = cmd_output_b('git', 'diff', '--no-ext-diff', retcode=None)\n    filenames = tuple(filenames) if hook.pass_filenames else ()\n    retcode, out = hook.run(filenames, use_color)\n    diff_after = cmd_output_b('git', 'diff', '--no-ext-diff', retcode=None)\n\n    file_modifications = diff_before != diff_after\n\n    # If the hook makes changes, fail the commit\n    if file_modifications:\n        retcode = 1\n\n    if retcode:\n        retcode = 1\n        print_color = color.RED\n        pass_fail = 'Failed'\n    else:\n        retcode = 0\n        print_color = color.GREEN\n        pass_fail = 'Passed'\n\n    output.write_line(color.format_color(pass_fail, print_color, args.color))\n\n    if (\n            (out or file_modifications) and\n            (retcode or args.verbose or hook.verbose)\n    ):\n        output.write_line('hookid: {}\\n'.format(hook.id))\n\n        # Print a message if failing due to file modifications\n        if file_modifications:\n            output.write('Files were modified by this hook.')\n\n            if out:\n                output.write_line(' Additional output:')\n\n            output.write_line()\n\n        if out.strip():\n            output.write_line(out.strip(), logfile_name=hook.log_file)\n        output.write_line()\n\n    return retcode\n\n\ndef _compute_cols(hooks, verbose):\n    \"\"\"Compute the number of columns to display hook messages.  The widest\n    that will be displayed is in the no files skipped case:\n\n        Hook name...(no files to check) Skipped\n\n    or in the verbose case\n\n        Hook name [hookid]...(no files to check) Skipped\n    \"\"\"\n    if hooks:\n        name_len = max(len(_hook_msg_start(hook, verbose)) for hook in hooks)\n    else:\n        name_len = 0\n\n    cols = name_len + 3 + len(NO_FILES) + 1 + len(SKIPPED)\n    return max(cols, 80)\n\n\ndef _all_filenames(args):\n    if args.origin and args.source:\n        return git.get_changed_files(args.origin, args.source)\n    elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}:\n        return (args.commit_msg_filename,)\n    elif args.files:\n        return args.files\n    elif args.all_files:\n        return git.get_all_files()\n    elif git.is_in_merge_conflict():\n        return git.get_conflicted_files()\n    else:\n        return git.get_staged_files()\n\n\ndef _run_hooks(config, hooks, args, environ):\n    \"\"\"Actually run the hooks.\"\"\"\n    skips = _get_skips(environ)\n    cols = _compute_cols(hooks, args.verbose)\n    filenames = _all_filenames(args)\n    filenames = filter_by_include_exclude(filenames, '', config['exclude'])\n    classifier = Classifier(filenames)\n    retval = 0\n    for hook in hooks:\n        retval |= _run_single_hook(\n            classifier, hook, args, skips, cols, args.color,\n        )\n        if retval and config['fail_fast']:\n            break\n    if retval and args.show_diff_on_failure and git.has_diff():\n        if args.all_files:\n            output.write_line(\n                'pre-commit hook(s) made changes.\\n'\n                'If you are seeing this message in CI, '\n                'reproduce locally with: `pre-commit run --all-files`.\\n'\n                'To run `pre-commit` as part of git workflow, use '\n                '`pre-commit install`.',\n            )\n        output.write_line('All changes made by hooks:')\n        # args.color is a boolean.\n        # See user_color function in color.py\n        subprocess.call((\n            'git', '--no-pager', 'diff', '--no-ext-diff',\n            '--color={}'.format({True: 'always', False: 'never'}[args.color]),\n        ))\n\n    return retval\n\n\ndef _has_unmerged_paths():\n    _, stdout, _ = cmd_output_b('git', 'ls-files', '--unmerged')\n    return bool(stdout.strip())\n\n\ndef _has_unstaged_config(config_file):\n    retcode, _, _ = cmd_output_b(\n        'git', 'diff', '--no-ext-diff', '--exit-code', config_file,\n        retcode=None,\n    )\n    # be explicit, other git errors don't mean it has an unstaged config.\n    return retcode == 1\n\n\ndef run(config_file, store, args, environ=os.environ):\n    no_stash = args.all_files or bool(args.files)\n\n    # Check if we have unresolved merge conflict files and fail fast.\n    if _has_unmerged_paths():\n        logger.error('Unmerged files.  Resolve before committing.')\n        return 1\n    if bool(args.source) != bool(args.origin):\n        logger.error('Specify both --origin and --source.')\n        return 1\n    if _has_unstaged_config(config_file) and not no_stash:\n        logger.error(\n            'Your pre-commit configuration is unstaged.\\n'\n            '`git add {}` to fix this.'.format(config_file),\n        )\n        return 1\n\n    # Expose origin / source as environment variables for hooks to consume\n    if args.origin and args.source:\n        environ['PRE_COMMIT_ORIGIN'] = args.origin\n        environ['PRE_COMMIT_SOURCE'] = args.source\n\n    if no_stash:\n        ctx = noop_context()\n    else:\n        ctx = staged_files_only(store.directory)\n\n    with ctx:\n        config = load_config(config_file)\n        hooks = [\n            hook\n            for hook in all_hooks(config, store)\n            if not args.hook or hook.id == args.hook or hook.alias == args.hook\n            if args.hook_stage in hook.stages\n        ]\n\n        if args.hook and not hooks:\n            output.write_line(\n                'No hook with id `{}` in stage `{}`'.format(\n                    args.hook, args.hook_stage,\n                ),\n            )\n            return 1\n\n        install_hook_envs(hooks, store)\n\n        return _run_hooks(config, hooks, args, environ)\n", "path": "pre_commit/commands/run.py"}], "after_files": [{"content": "from __future__ import unicode_literals\n\nimport logging\nimport os\nimport re\nimport subprocess\nimport sys\n\nfrom identify.identify import tags_from_path\n\nfrom pre_commit import color\nfrom pre_commit import git\nfrom pre_commit import output\nfrom pre_commit.clientlib import load_config\nfrom pre_commit.output import get_hook_message\nfrom pre_commit.repository import all_hooks\nfrom pre_commit.repository import install_hook_envs\nfrom pre_commit.staged_files_only import staged_files_only\nfrom pre_commit.util import cmd_output_b\nfrom pre_commit.util import noop_context\n\n\nlogger = logging.getLogger('pre_commit')\n\n\ndef filter_by_include_exclude(names, include, exclude):\n    include_re, exclude_re = re.compile(include), re.compile(exclude)\n    return [\n        filename for filename in names\n        if include_re.search(filename)\n        if not exclude_re.search(filename)\n    ]\n\n\nclass Classifier(object):\n    def __init__(self, filenames):\n        # on windows we normalize all filenames to use forward slashes\n        # this makes it easier to filter using the `files:` regex\n        # this also makes improperly quoted shell-based hooks work better\n        # see #1173\n        if os.altsep == '/' and os.sep == '\\\\':\n            filenames = (f.replace(os.sep, os.altsep) for f in filenames)\n        self.filenames = [f for f in filenames if os.path.lexists(f)]\n        self._types_cache = {}\n\n    def _types_for_file(self, filename):\n        try:\n            return self._types_cache[filename]\n        except KeyError:\n            ret = self._types_cache[filename] = tags_from_path(filename)\n            return ret\n\n    def by_types(self, names, types, exclude_types):\n        types, exclude_types = frozenset(types), frozenset(exclude_types)\n        ret = []\n        for filename in names:\n            tags = self._types_for_file(filename)\n            if tags >= types and not tags & exclude_types:\n                ret.append(filename)\n        return ret\n\n    def filenames_for_hook(self, hook):\n        names = self.filenames\n        names = filter_by_include_exclude(names, hook.files, hook.exclude)\n        names = self.by_types(names, hook.types, hook.exclude_types)\n        return names\n\n\ndef _get_skips(environ):\n    skips = environ.get('SKIP', '')\n    return {skip.strip() for skip in skips.split(',') if skip.strip()}\n\n\ndef _hook_msg_start(hook, verbose):\n    return '{}{}'.format('[{}] '.format(hook.id) if verbose else '', hook.name)\n\n\nSKIPPED = 'Skipped'\nNO_FILES = '(no files to check)'\n\n\ndef _run_single_hook(classifier, hook, args, skips, cols, use_color):\n    filenames = classifier.filenames_for_hook(hook)\n\n    if hook.language == 'pcre':\n        logger.warning(\n            '`{}` (from {}) uses the deprecated pcre language.\\n'\n            'The pcre language is scheduled for removal in pre-commit 2.x.\\n'\n            'The pygrep language is a more portable (and usually drop-in) '\n            'replacement.'.format(hook.id, hook.src),\n        )\n\n    if hook.id in skips or hook.alias in skips:\n        output.write(\n            get_hook_message(\n                _hook_msg_start(hook, args.verbose),\n                end_msg=SKIPPED,\n                end_color=color.YELLOW,\n                use_color=args.color,\n                cols=cols,\n            ),\n        )\n        return 0\n    elif not filenames and not hook.always_run:\n        output.write(\n            get_hook_message(\n                _hook_msg_start(hook, args.verbose),\n                postfix=NO_FILES,\n                end_msg=SKIPPED,\n                end_color=color.TURQUOISE,\n                use_color=args.color,\n                cols=cols,\n            ),\n        )\n        return 0\n\n    # Print the hook and the dots first in case the hook takes hella long to\n    # run.\n    output.write(\n        get_hook_message(\n            _hook_msg_start(hook, args.verbose), end_len=6, cols=cols,\n        ),\n    )\n    sys.stdout.flush()\n\n    diff_before = cmd_output_b('git', 'diff', '--no-ext-diff', retcode=None)\n    filenames = tuple(filenames) if hook.pass_filenames else ()\n    retcode, out = hook.run(filenames, use_color)\n    diff_after = cmd_output_b('git', 'diff', '--no-ext-diff', retcode=None)\n\n    file_modifications = diff_before != diff_after\n\n    # If the hook makes changes, fail the commit\n    if file_modifications:\n        retcode = 1\n\n    if retcode:\n        retcode = 1\n        print_color = color.RED\n        pass_fail = 'Failed'\n    else:\n        retcode = 0\n        print_color = color.GREEN\n        pass_fail = 'Passed'\n\n    output.write_line(color.format_color(pass_fail, print_color, args.color))\n\n    if (\n            (out or file_modifications) and\n            (retcode or args.verbose or hook.verbose)\n    ):\n        output.write_line('hookid: {}\\n'.format(hook.id))\n\n        # Print a message if failing due to file modifications\n        if file_modifications:\n            output.write('Files were modified by this hook.')\n\n            if out:\n                output.write_line(' Additional output:')\n\n            output.write_line()\n\n        if out.strip():\n            output.write_line(out.strip(), logfile_name=hook.log_file)\n        output.write_line()\n\n    return retcode\n\n\ndef _compute_cols(hooks, verbose):\n    \"\"\"Compute the number of columns to display hook messages.  The widest\n    that will be displayed is in the no files skipped case:\n\n        Hook name...(no files to check) Skipped\n\n    or in the verbose case\n\n        Hook name [hookid]...(no files to check) Skipped\n    \"\"\"\n    if hooks:\n        name_len = max(len(_hook_msg_start(hook, verbose)) for hook in hooks)\n    else:\n        name_len = 0\n\n    cols = name_len + 3 + len(NO_FILES) + 1 + len(SKIPPED)\n    return max(cols, 80)\n\n\ndef _all_filenames(args):\n    if args.origin and args.source:\n        return git.get_changed_files(args.origin, args.source)\n    elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}:\n        return (args.commit_msg_filename,)\n    elif args.files:\n        return args.files\n    elif args.all_files:\n        return git.get_all_files()\n    elif git.is_in_merge_conflict():\n        return git.get_conflicted_files()\n    else:\n        return git.get_staged_files()\n\n\ndef _run_hooks(config, hooks, args, environ):\n    \"\"\"Actually run the hooks.\"\"\"\n    skips = _get_skips(environ)\n    cols = _compute_cols(hooks, args.verbose)\n    filenames = _all_filenames(args)\n    filenames = filter_by_include_exclude(filenames, '', config['exclude'])\n    classifier = Classifier(filenames)\n    retval = 0\n    for hook in hooks:\n        retval |= _run_single_hook(\n            classifier, hook, args, skips, cols, args.color,\n        )\n        if retval and config['fail_fast']:\n            break\n    if retval and args.show_diff_on_failure and git.has_diff():\n        if args.all_files:\n            output.write_line(\n                'pre-commit hook(s) made changes.\\n'\n                'If you are seeing this message in CI, '\n                'reproduce locally with: `pre-commit run --all-files`.\\n'\n                'To run `pre-commit` as part of git workflow, use '\n                '`pre-commit install`.',\n            )\n        output.write_line('All changes made by hooks:')\n        # args.color is a boolean.\n        # See user_color function in color.py\n        subprocess.call((\n            'git', '--no-pager', 'diff', '--no-ext-diff',\n            '--color={}'.format({True: 'always', False: 'never'}[args.color]),\n        ))\n\n    return retval\n\n\ndef _has_unmerged_paths():\n    _, stdout, _ = cmd_output_b('git', 'ls-files', '--unmerged')\n    return bool(stdout.strip())\n\n\ndef _has_unstaged_config(config_file):\n    retcode, _, _ = cmd_output_b(\n        'git', 'diff', '--no-ext-diff', '--exit-code', config_file,\n        retcode=None,\n    )\n    # be explicit, other git errors don't mean it has an unstaged config.\n    return retcode == 1\n\n\ndef run(config_file, store, args, environ=os.environ):\n    no_stash = args.all_files or bool(args.files)\n\n    # Check if we have unresolved merge conflict files and fail fast.\n    if _has_unmerged_paths():\n        logger.error('Unmerged files.  Resolve before committing.')\n        return 1\n    if bool(args.source) != bool(args.origin):\n        logger.error('Specify both --origin and --source.')\n        return 1\n    if _has_unstaged_config(config_file) and not no_stash:\n        logger.error(\n            'Your pre-commit configuration is unstaged.\\n'\n            '`git add {}` to fix this.'.format(config_file),\n        )\n        return 1\n\n    # Expose origin / source as environment variables for hooks to consume\n    if args.origin and args.source:\n        environ['PRE_COMMIT_ORIGIN'] = args.origin\n        environ['PRE_COMMIT_SOURCE'] = args.source\n\n    if no_stash:\n        ctx = noop_context()\n    else:\n        ctx = staged_files_only(store.directory)\n\n    with ctx:\n        config = load_config(config_file)\n        hooks = [\n            hook\n            for hook in all_hooks(config, store)\n            if not args.hook or hook.id == args.hook or hook.alias == args.hook\n            if args.hook_stage in hook.stages\n        ]\n\n        if args.hook and not hooks:\n            output.write_line(\n                'No hook with id `{}` in stage `{}`'.format(\n                    args.hook, args.hook_stage,\n                ),\n            )\n            return 1\n\n        install_hook_envs(hooks, store)\n\n        return _run_hooks(config, hooks, args, environ)\n", "path": "pre_commit/commands/run.py"}]}