We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add statistics information for individual checks (especially time of execution)
I would love to have an option to add some statistics to be displayed for each check. This could be very useful for running pre-commit in CI. I had a case recently of a check that run accidentally a bit longer than expected (it was building unnecessary docker image) and I have not realised that it is taking longer - because we had other checks after and I could only see the total execution time.

Also some other useful information that we might see is how many parallel processes were run and how many files were passed as parameters. This might be really useful especially for people who do not understand that pre-commit runs in parallel by default  - this can have some undesireable side effects if you forget to set "require_serial" to True when you need. And seeing that in the output of CI might immediately show that something is wrong. For now the output is a bit of "black-box".

An option to see some additional information (`--add-statistics`) might be super-useful.

Example output I imagine (maybe a bit better aligned):

```
Check if image build is needed...........................................Passed [23.5s, 1 process, no-files]  
Add licence for all JINJA template files.................................Passed [2.25s, 8 processes, 48 files]
Check Shell scripts syntax correctness...................................Passed [6.00s, 8 processes, 10 files]
Lint dockerfile..........................................................Passed [2.20s, 3 processes, 3 files]
Run mypy.................................................................Passed [12.00s, 8 processes, 1456 files]
Run pylint for main sources.............................................Skipped 
Run pylint for tests....................................................Skipped
Run flake8...............................................................Passed [24.05s, 8 processes, 1456 files]
```

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

{"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@@ -4,6 +4,7 @@\n import os\n import re\n import subprocess\n+import time\n \n from identify.identify import tags_from_path\n \n@@ -99,6 +100,7 @@\n                 cols=cols,\n             ),\n         )\n+        duration = None\n         retcode = 0\n         files_modified = False\n         out = b''\n@@ -113,6 +115,7 @@\n                 cols=cols,\n             ),\n         )\n+        duration = None\n         retcode = 0\n         files_modified = False\n         out = b''\n@@ -123,7 +126,9 @@\n         diff_cmd = ('git', 'diff', '--no-ext-diff')\n         diff_before = cmd_output_b(*diff_cmd, retcode=None)\n         filenames = tuple(filenames) if hook.pass_filenames else ()\n+        time_before = time.time()\n         retcode, out = hook.run(filenames, use_color)\n+        duration = round(time.time() - time_before, 2) or 0\n         diff_after = cmd_output_b(*diff_cmd, retcode=None)\n \n         # if the hook makes changes, fail the commit\n@@ -141,6 +146,9 @@\n     if verbose or hook.verbose or retcode or files_modified:\n         _subtle_line('- hook id: {}'.format(hook.id), use_color)\n \n+        if (verbose or hook.verbose) and duration is not None:\n+            _subtle_line('- duration: {}s'.format(duration), use_color)\n+\n         if retcode:\n             _subtle_line('- exit code: {}'.format(retcode), use_color)\n", "issue": "Add statistics information for individual checks (especially time of execution)\nI would love to have an option to add some statistics to be displayed for each check. This could be very useful for running pre-commit in CI. I had a case recently of a check that run accidentally a bit longer than expected (it was building unnecessary docker image) and I have not realised that it is taking longer - because we had other checks after and I could only see the total execution time.\r\n\r\nAlso some other useful information that we might see is how many parallel processes were run and how many files were passed as parameters. This might be really useful especially for people who do not understand that pre-commit runs in parallel by default  - this can have some undesireable side effects if you forget to set \"require_serial\" to True when you need. And seeing that in the output of CI might immediately show that something is wrong. For now the output is a bit of \"black-box\".\r\n\r\nAn option to see some additional information (`--add-statistics`) might be super-useful.\r\n\r\nExample output I imagine (maybe a bit better aligned):\r\n\r\n```\r\nCheck if image build is needed...........................................Passed [23.5s, 1 process, no-files]  \r\nAdd licence for all JINJA template files.................................Passed [2.25s, 8 processes, 48 files]\r\nCheck Shell scripts syntax correctness...................................Passed [6.00s, 8 processes, 10 files]\r\nLint dockerfile..........................................................Passed [2.20s, 3 processes, 3 files]\r\nRun mypy.................................................................Passed [12.00s, 8 processes, 1456 files]\r\nRun pylint for main sources.............................................Skipped \r\nRun pylint for tests....................................................Skipped\r\nRun flake8...............................................................Passed [24.05s, 8 processes, 1456 files]\r\n```\n", "before_files": [{"content": "from __future__ import unicode_literals\n\nimport logging\nimport os\nimport re\nimport subprocess\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\nSKIPPED = 'Skipped'\nNO_FILES = '(no files to check)'\n\n\ndef _subtle_line(s, use_color):\n    output.write_line(color.format_color(s, color.SUBTLE, use_color))\n\n\ndef _run_single_hook(classifier, hook, skips, cols, verbose, 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.name,\n                end_msg=SKIPPED,\n                end_color=color.YELLOW,\n                use_color=use_color,\n                cols=cols,\n            ),\n        )\n        retcode = 0\n        files_modified = False\n        out = b''\n    elif not filenames and not hook.always_run:\n        output.write(\n            get_hook_message(\n                hook.name,\n                postfix=NO_FILES,\n                end_msg=SKIPPED,\n                end_color=color.TURQUOISE,\n                use_color=use_color,\n                cols=cols,\n            ),\n        )\n        retcode = 0\n        files_modified = False\n        out = b''\n    else:\n        # print hook and dots first in case the hook takes a while to run\n        output.write(get_hook_message(hook.name, end_len=6, cols=cols))\n\n        diff_cmd = ('git', 'diff', '--no-ext-diff')\n        diff_before = cmd_output_b(*diff_cmd, 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(*diff_cmd, retcode=None)\n\n        # if the hook makes changes, fail the commit\n        files_modified = diff_before != diff_after\n\n        if retcode or files_modified:\n            print_color = color.RED\n            status = 'Failed'\n        else:\n            print_color = color.GREEN\n            status = 'Passed'\n\n        output.write_line(color.format_color(status, print_color, use_color))\n\n    if verbose or hook.verbose or retcode or files_modified:\n        _subtle_line('- hook id: {}'.format(hook.id), use_color)\n\n        if retcode:\n            _subtle_line('- exit code: {}'.format(retcode), use_color)\n\n        # Print a message if failing due to file modifications\n        if files_modified:\n            _subtle_line('- files were modified by this hook', use_color)\n\n        if out.strip():\n            output.write_line()\n            output.write_line(out.strip(), logfile_name=hook.log_file)\n            output.write_line()\n\n    return files_modified or bool(retcode)\n\n\ndef _compute_cols(hooks):\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    if hooks:\n        name_len = max(len(hook.name) 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)\n    filenames = _all_filenames(args)\n    filenames = filter_by_include_exclude(\n        filenames, config['files'], config['exclude'],\n    )\n    classifier = Classifier(filenames)\n    retval = 0\n    for hook in hooks:\n        retval |= _run_single_hook(\n            classifier, hook, skips, cols,\n            verbose=args.verbose, use_color=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 time\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\nSKIPPED = 'Skipped'\nNO_FILES = '(no files to check)'\n\n\ndef _subtle_line(s, use_color):\n    output.write_line(color.format_color(s, color.SUBTLE, use_color))\n\n\ndef _run_single_hook(classifier, hook, skips, cols, verbose, 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.name,\n                end_msg=SKIPPED,\n                end_color=color.YELLOW,\n                use_color=use_color,\n                cols=cols,\n            ),\n        )\n        duration = None\n        retcode = 0\n        files_modified = False\n        out = b''\n    elif not filenames and not hook.always_run:\n        output.write(\n            get_hook_message(\n                hook.name,\n                postfix=NO_FILES,\n                end_msg=SKIPPED,\n                end_color=color.TURQUOISE,\n                use_color=use_color,\n                cols=cols,\n            ),\n        )\n        duration = None\n        retcode = 0\n        files_modified = False\n        out = b''\n    else:\n        # print hook and dots first in case the hook takes a while to run\n        output.write(get_hook_message(hook.name, end_len=6, cols=cols))\n\n        diff_cmd = ('git', 'diff', '--no-ext-diff')\n        diff_before = cmd_output_b(*diff_cmd, retcode=None)\n        filenames = tuple(filenames) if hook.pass_filenames else ()\n        time_before = time.time()\n        retcode, out = hook.run(filenames, use_color)\n        duration = round(time.time() - time_before, 2) or 0\n        diff_after = cmd_output_b(*diff_cmd, retcode=None)\n\n        # if the hook makes changes, fail the commit\n        files_modified = diff_before != diff_after\n\n        if retcode or files_modified:\n            print_color = color.RED\n            status = 'Failed'\n        else:\n            print_color = color.GREEN\n            status = 'Passed'\n\n        output.write_line(color.format_color(status, print_color, use_color))\n\n    if verbose or hook.verbose or retcode or files_modified:\n        _subtle_line('- hook id: {}'.format(hook.id), use_color)\n\n        if (verbose or hook.verbose) and duration is not None:\n            _subtle_line('- duration: {}s'.format(duration), use_color)\n\n        if retcode:\n            _subtle_line('- exit code: {}'.format(retcode), use_color)\n\n        # Print a message if failing due to file modifications\n        if files_modified:\n            _subtle_line('- files were modified by this hook', use_color)\n\n        if out.strip():\n            output.write_line()\n            output.write_line(out.strip(), logfile_name=hook.log_file)\n            output.write_line()\n\n    return files_modified or bool(retcode)\n\n\ndef _compute_cols(hooks):\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    if hooks:\n        name_len = max(len(hook.name) 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)\n    filenames = _all_filenames(args)\n    filenames = filter_by_include_exclude(\n        filenames, config['files'], config['exclude'],\n    )\n    classifier = Classifier(filenames)\n    retval = 0\n    for hook in hooks:\n        retval |= _run_single_hook(\n            classifier, hook, skips, cols,\n            verbose=args.verbose, use_color=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"}]}