We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
deprecate `python_venv` language
this has been an alias to `python` for a very long time but it cannot be removed without a deprecation period

this is going to need a long deprecation period since it's sorta subtle and usually not the user's fault and will need hook authors to (potentially) make updates

the plan is to do the following:

1. introduce the following in a minor release
    - migrate-config will autofix `.pre-commit-config.yaml` usages of `language: python_venv` (there isn't an equivalent `migrate-manifest` -- though users outnumber hook authors by several orders of magnitude)
1. introduce the following in a minor release
    - a warning is shown for configuration using the `language: python_venv`
    - a warning is shown for repos using `language: python_venv` (do this at install time so it only shows once as to not be super annoying for users who have no control)
    - a recommendation for hook authors to also set `minimum_pre_commit_version` to this version
1. a long time passes (typically my deprecation period has been 12-18+ months)
1. introduce the following in a major release
        - removal of the `python_venv` alias


--- 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/repository.py`
Content:
```
1 from __future__ import annotations
2 
3 import json
4 import logging
5 import os
6 from typing import Any
7 from typing import Sequence
8 
9 import pre_commit.constants as C
10 from pre_commit.clientlib import load_manifest
11 from pre_commit.clientlib import LOCAL
12 from pre_commit.clientlib import META
13 from pre_commit.clientlib import parse_version
14 from pre_commit.hook import Hook
15 from pre_commit.languages.all import languages
16 from pre_commit.languages.helpers import environment_dir
17 from pre_commit.prefix import Prefix
18 from pre_commit.store import Store
19 from pre_commit.util import clean_path_on_failure
20 from pre_commit.util import rmtree
21 
22 
23 logger = logging.getLogger('pre_commit')
24 
25 
26 def _state_filename_v1(venv: str) -> str:
27     return os.path.join(venv, '.install_state_v1')
28 
29 
30 def _state_filename_v2(venv: str) -> str:
31     return os.path.join(venv, '.install_state_v2')
32 
33 
34 def _state(additional_deps: Sequence[str]) -> object:
35     return {'additional_dependencies': sorted(additional_deps)}
36 
37 
38 def _read_state(venv: str) -> object | None:
39     filename = _state_filename_v1(venv)
40     if not os.path.exists(filename):
41         return None
42     else:
43         with open(filename) as f:
44             return json.load(f)
45 
46 
47 def _hook_installed(hook: Hook) -> bool:
48     lang = languages[hook.language]
49     if lang.ENVIRONMENT_DIR is None:
50         return True
51 
52     venv = environment_dir(
53         hook.prefix,
54         lang.ENVIRONMENT_DIR,
55         hook.language_version,
56     )
57     return (
58         (
59             os.path.exists(_state_filename_v2(venv)) or
60             _read_state(venv) == _state(hook.additional_dependencies)
61         ) and
62         not lang.health_check(hook.prefix, hook.language_version)
63     )
64 
65 
66 def _hook_install(hook: Hook) -> None:
67     logger.info(f'Installing environment for {hook.src}.')
68     logger.info('Once installed this environment will be reused.')
69     logger.info('This may take a few minutes...')
70 
71     lang = languages[hook.language]
72     assert lang.ENVIRONMENT_DIR is not None
73 
74     venv = environment_dir(
75         hook.prefix,
76         lang.ENVIRONMENT_DIR,
77         hook.language_version,
78     )
79 
80     # There's potentially incomplete cleanup from previous runs
81     # Clean it up!
82     if os.path.exists(venv):
83         rmtree(venv)
84 
85     with clean_path_on_failure(venv):
86         lang.install_environment(
87             hook.prefix, hook.language_version, hook.additional_dependencies,
88         )
89         health_error = lang.health_check(hook.prefix, hook.language_version)
90         if health_error:
91             raise AssertionError(
92                 f'BUG: expected environment for {hook.language} to be healthy '
93                 f'immediately after install, please open an issue describing '
94                 f'your environment\n\n'
95                 f'more info:\n\n{health_error}',
96             )
97 
98         # TODO: remove v1 state writing, no longer needed after pre-commit 3.0
99         # Write our state to indicate we're installed
100         state_filename = _state_filename_v1(venv)
101         staging = f'{state_filename}staging'
102         with open(staging, 'w') as state_file:
103             state_file.write(json.dumps(_state(hook.additional_dependencies)))
104         # Move the file into place atomically to indicate we've installed
105         os.replace(staging, state_filename)
106 
107         open(_state_filename_v2(venv), 'a+').close()
108 
109 
110 def _hook(
111         *hook_dicts: dict[str, Any],
112         root_config: dict[str, Any],
113 ) -> dict[str, Any]:
114     ret, rest = dict(hook_dicts[0]), hook_dicts[1:]
115     for dct in rest:
116         ret.update(dct)
117 
118     version = ret['minimum_pre_commit_version']
119     if parse_version(version) > parse_version(C.VERSION):
120         logger.error(
121             f'The hook `{ret["id"]}` requires pre-commit version {version} '
122             f'but version {C.VERSION} is installed.  '
123             f'Perhaps run `pip install --upgrade pre-commit`.',
124         )
125         exit(1)
126 
127     lang = ret['language']
128     if ret['language_version'] == C.DEFAULT:
129         ret['language_version'] = root_config['default_language_version'][lang]
130     if ret['language_version'] == C.DEFAULT:
131         ret['language_version'] = languages[lang].get_default_version()
132 
133     if not ret['stages']:
134         ret['stages'] = root_config['default_stages']
135 
136     if languages[lang].ENVIRONMENT_DIR is None:
137         if ret['language_version'] != C.DEFAULT:
138             logger.error(
139                 f'The hook `{ret["id"]}` specifies `language_version` but is '
140                 f'using language `{lang}` which does not install an '
141                 f'environment.  '
142                 f'Perhaps you meant to use a specific language?',
143             )
144             exit(1)
145         if ret['additional_dependencies']:
146             logger.error(
147                 f'The hook `{ret["id"]}` specifies `additional_dependencies` '
148                 f'but is using language `{lang}` which does not install an '
149                 f'environment.  '
150                 f'Perhaps you meant to use a specific language?',
151             )
152             exit(1)
153 
154     return ret
155 
156 
157 def _non_cloned_repository_hooks(
158         repo_config: dict[str, Any],
159         store: Store,
160         root_config: dict[str, Any],
161 ) -> tuple[Hook, ...]:
162     def _prefix(language_name: str, deps: Sequence[str]) -> Prefix:
163         language = languages[language_name]
164         # pygrep / script / system / docker_image do not have
165         # environments so they work out of the current directory
166         if language.ENVIRONMENT_DIR is None:
167             return Prefix(os.getcwd())
168         else:
169             return Prefix(store.make_local(deps))
170 
171     return tuple(
172         Hook.create(
173             repo_config['repo'],
174             _prefix(hook['language'], hook['additional_dependencies']),
175             _hook(hook, root_config=root_config),
176         )
177         for hook in repo_config['hooks']
178     )
179 
180 
181 def _cloned_repository_hooks(
182         repo_config: dict[str, Any],
183         store: Store,
184         root_config: dict[str, Any],
185 ) -> tuple[Hook, ...]:
186     repo, rev = repo_config['repo'], repo_config['rev']
187     manifest_path = os.path.join(store.clone(repo, rev), C.MANIFEST_FILE)
188     by_id = {hook['id']: hook for hook in load_manifest(manifest_path)}
189 
190     for hook in repo_config['hooks']:
191         if hook['id'] not in by_id:
192             logger.error(
193                 f'`{hook["id"]}` is not present in repository {repo}.  '
194                 f'Typo? Perhaps it is introduced in a newer version?  '
195                 f'Often `pre-commit autoupdate` fixes this.',
196             )
197             exit(1)
198 
199     hook_dcts = [
200         _hook(by_id[hook['id']], hook, root_config=root_config)
201         for hook in repo_config['hooks']
202     ]
203     return tuple(
204         Hook.create(
205             repo_config['repo'],
206             Prefix(store.clone(repo, rev, hook['additional_dependencies'])),
207             hook,
208         )
209         for hook in hook_dcts
210     )
211 
212 
213 def _repository_hooks(
214         repo_config: dict[str, Any],
215         store: Store,
216         root_config: dict[str, Any],
217 ) -> tuple[Hook, ...]:
218     if repo_config['repo'] in {LOCAL, META}:
219         return _non_cloned_repository_hooks(repo_config, store, root_config)
220     else:
221         return _cloned_repository_hooks(repo_config, store, root_config)
222 
223 
224 def install_hook_envs(hooks: Sequence[Hook], store: Store) -> None:
225     def _need_installed() -> list[Hook]:
226         seen: set[tuple[Prefix, str, str, tuple[str, ...]]] = set()
227         ret = []
228         for hook in hooks:
229             if hook.install_key not in seen and not _hook_installed(hook):
230                 ret.append(hook)
231             seen.add(hook.install_key)
232         return ret
233 
234     if not _need_installed():
235         return
236     with store.exclusive_lock():
237         # Another process may have already completed this work
238         for hook in _need_installed():
239             _hook_install(hook)
240 
241 
242 def all_hooks(root_config: dict[str, Any], store: Store) -> tuple[Hook, ...]:
243     return tuple(
244         hook
245         for repo in root_config['repos']
246         for hook in _repository_hooks(repo, store, root_config)
247     )
248
```

Path: `pre_commit/commands/migrate_config.py`
Content:
```
1 from __future__ import annotations
2 
3 import re
4 import textwrap
5 
6 import cfgv
7 import yaml
8 
9 from pre_commit.clientlib import InvalidConfigError
10 from pre_commit.yaml import yaml_load
11 
12 
13 def _is_header_line(line: str) -> bool:
14     return line.startswith(('#', '---')) or not line.strip()
15 
16 
17 def _migrate_map(contents: str) -> str:
18     if isinstance(yaml_load(contents), list):
19         # Find the first non-header line
20         lines = contents.splitlines(True)
21         i = 0
22         # Only loop on non empty configuration file
23         while i < len(lines) and _is_header_line(lines[i]):
24             i += 1
25 
26         header = ''.join(lines[:i])
27         rest = ''.join(lines[i:])
28 
29         # If they are using the "default" flow style of yaml, this operation
30         # will yield a valid configuration
31         try:
32             trial_contents = f'{header}repos:\n{rest}'
33             yaml_load(trial_contents)
34             contents = trial_contents
35         except yaml.YAMLError:
36             contents = f'{header}repos:\n{textwrap.indent(rest, " " * 4)}'
37 
38     return contents
39 
40 
41 def _migrate_sha_to_rev(contents: str) -> str:
42     return re.sub(r'(\n\s+)sha:', r'\1rev:', contents)
43 
44 
45 def migrate_config(config_file: str, quiet: bool = False) -> int:
46     with open(config_file) as f:
47         orig_contents = contents = f.read()
48 
49     with cfgv.reraise_as(InvalidConfigError):
50         with cfgv.validate_context(f'File {config_file}'):
51             try:
52                 yaml_load(orig_contents)
53             except Exception as e:
54                 raise cfgv.ValidationError(str(e))
55 
56     contents = _migrate_map(contents)
57     contents = _migrate_sha_to_rev(contents)
58 
59     if contents != orig_contents:
60         with open(config_file, 'w') as f:
61             f.write(contents)
62 
63         print('Configuration has been migrated.')
64     elif not quiet:
65         print('Configuration is already migrated.')
66     return 0
67
```
--- 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/migrate_config.py b/pre_commit/commands/migrate_config.py
--- a/pre_commit/commands/migrate_config.py
+++ b/pre_commit/commands/migrate_config.py
@@ -42,6 +42,14 @@
     return re.sub(r'(\n\s+)sha:', r'\1rev:', contents)
 
 
+def _migrate_python_venv(contents: str) -> str:
+    return re.sub(
+        r'(\n\s+)language: python_venv\b',
+        r'\1language: python',
+        contents,
+    )
+
+
 def migrate_config(config_file: str, quiet: bool = False) -> int:
     with open(config_file) as f:
         orig_contents = contents = f.read()
@@ -55,6 +63,7 @@
 
     contents = _migrate_map(contents)
     contents = _migrate_sha_to_rev(contents)
+    contents = _migrate_python_venv(contents)
 
     if contents != orig_contents:
         with open(config_file, 'w') as f:
diff --git a/pre_commit/repository.py b/pre_commit/repository.py
--- a/pre_commit/repository.py
+++ b/pre_commit/repository.py
@@ -3,6 +3,7 @@
 import json
 import logging
 import os
+import shlex
 from typing import Any
 from typing import Sequence
 
@@ -68,6 +69,14 @@
     logger.info('Once installed this environment will be reused.')
     logger.info('This may take a few minutes...')
 
+    if hook.language == 'python_venv':
+        logger.warning(
+            f'`repo: {hook.src}` uses deprecated `language: python_venv`.  '
+            f'This is an alias for `language: python`.  '
+            f'Often `pre-commit autoupdate --repo {shlex.quote(hook.src)}` '
+            f'will fix this.',
+        )
+
     lang = languages[hook.language]
     assert lang.ENVIRONMENT_DIR is not None

{"golden_diff": "diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py\n--- a/pre_commit/commands/migrate_config.py\n+++ b/pre_commit/commands/migrate_config.py\n@@ -42,6 +42,14 @@\n     return re.sub(r'(\\n\\s+)sha:', r'\\1rev:', contents)\n \n \n+def _migrate_python_venv(contents: str) -> str:\n+    return re.sub(\n+        r'(\\n\\s+)language: python_venv\\b',\n+        r'\\1language: python',\n+        contents,\n+    )\n+\n+\n def migrate_config(config_file: str, quiet: bool = False) -> int:\n     with open(config_file) as f:\n         orig_contents = contents = f.read()\n@@ -55,6 +63,7 @@\n \n     contents = _migrate_map(contents)\n     contents = _migrate_sha_to_rev(contents)\n+    contents = _migrate_python_venv(contents)\n \n     if contents != orig_contents:\n         with open(config_file, 'w') as f:\ndiff --git a/pre_commit/repository.py b/pre_commit/repository.py\n--- a/pre_commit/repository.py\n+++ b/pre_commit/repository.py\n@@ -3,6 +3,7 @@\n import json\n import logging\n import os\n+import shlex\n from typing import Any\n from typing import Sequence\n \n@@ -68,6 +69,14 @@\n     logger.info('Once installed this environment will be reused.')\n     logger.info('This may take a few minutes...')\n \n+    if hook.language == 'python_venv':\n+        logger.warning(\n+            f'`repo: {hook.src}` uses deprecated `language: python_venv`.  '\n+            f'This is an alias for `language: python`.  '\n+            f'Often `pre-commit autoupdate --repo {shlex.quote(hook.src)}` '\n+            f'will fix this.',\n+        )\n+\n     lang = languages[hook.language]\n     assert lang.ENVIRONMENT_DIR is not None\n", "issue": "deprecate `python_venv` language\nthis has been an alias to `python` for a very long time but it cannot be removed without a deprecation period\r\n\r\nthis is going to need a long deprecation period since it's sorta subtle and usually not the user's fault and will need hook authors to (potentially) make updates\r\n\r\nthe plan is to do the following:\r\n\r\n1. introduce the following in a minor release\r\n    - migrate-config will autofix `.pre-commit-config.yaml` usages of `language: python_venv` (there isn't an equivalent `migrate-manifest` -- though users outnumber hook authors by several orders of magnitude)\r\n1. introduce the following in a minor release\r\n    - a warning is shown for configuration using the `language: python_venv`\r\n    - a warning is shown for repos using `language: python_venv` (do this at install time so it only shows once as to not be super annoying for users who have no control)\r\n    - a recommendation for hook authors to also set `minimum_pre_commit_version` to this version\r\n1. a long time passes (typically my deprecation period has been 12-18+ months)\r\n1. introduce the following in a major release\r\n        - removal of the `python_venv` alias\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport json\nimport logging\nimport os\nfrom typing import Any\nfrom typing import Sequence\n\nimport pre_commit.constants as C\nfrom pre_commit.clientlib import load_manifest\nfrom pre_commit.clientlib import LOCAL\nfrom pre_commit.clientlib import META\nfrom pre_commit.clientlib import parse_version\nfrom pre_commit.hook import Hook\nfrom pre_commit.languages.all import languages\nfrom pre_commit.languages.helpers import environment_dir\nfrom pre_commit.prefix import Prefix\nfrom pre_commit.store import Store\nfrom pre_commit.util import clean_path_on_failure\nfrom pre_commit.util import rmtree\n\n\nlogger = logging.getLogger('pre_commit')\n\n\ndef _state_filename_v1(venv: str) -> str:\n    return os.path.join(venv, '.install_state_v1')\n\n\ndef _state_filename_v2(venv: str) -> str:\n    return os.path.join(venv, '.install_state_v2')\n\n\ndef _state(additional_deps: Sequence[str]) -> object:\n    return {'additional_dependencies': sorted(additional_deps)}\n\n\ndef _read_state(venv: str) -> object | None:\n    filename = _state_filename_v1(venv)\n    if not os.path.exists(filename):\n        return None\n    else:\n        with open(filename) as f:\n            return json.load(f)\n\n\ndef _hook_installed(hook: Hook) -> bool:\n    lang = languages[hook.language]\n    if lang.ENVIRONMENT_DIR is None:\n        return True\n\n    venv = environment_dir(\n        hook.prefix,\n        lang.ENVIRONMENT_DIR,\n        hook.language_version,\n    )\n    return (\n        (\n            os.path.exists(_state_filename_v2(venv)) or\n            _read_state(venv) == _state(hook.additional_dependencies)\n        ) and\n        not lang.health_check(hook.prefix, hook.language_version)\n    )\n\n\ndef _hook_install(hook: Hook) -> None:\n    logger.info(f'Installing environment for {hook.src}.')\n    logger.info('Once installed this environment will be reused.')\n    logger.info('This may take a few minutes...')\n\n    lang = languages[hook.language]\n    assert lang.ENVIRONMENT_DIR is not None\n\n    venv = environment_dir(\n        hook.prefix,\n        lang.ENVIRONMENT_DIR,\n        hook.language_version,\n    )\n\n    # There's potentially incomplete cleanup from previous runs\n    # Clean it up!\n    if os.path.exists(venv):\n        rmtree(venv)\n\n    with clean_path_on_failure(venv):\n        lang.install_environment(\n            hook.prefix, hook.language_version, hook.additional_dependencies,\n        )\n        health_error = lang.health_check(hook.prefix, hook.language_version)\n        if health_error:\n            raise AssertionError(\n                f'BUG: expected environment for {hook.language} to be healthy '\n                f'immediately after install, please open an issue describing '\n                f'your environment\\n\\n'\n                f'more info:\\n\\n{health_error}',\n            )\n\n        # TODO: remove v1 state writing, no longer needed after pre-commit 3.0\n        # Write our state to indicate we're installed\n        state_filename = _state_filename_v1(venv)\n        staging = f'{state_filename}staging'\n        with open(staging, 'w') as state_file:\n            state_file.write(json.dumps(_state(hook.additional_dependencies)))\n        # Move the file into place atomically to indicate we've installed\n        os.replace(staging, state_filename)\n\n        open(_state_filename_v2(venv), 'a+').close()\n\n\ndef _hook(\n        *hook_dicts: dict[str, Any],\n        root_config: dict[str, Any],\n) -> dict[str, Any]:\n    ret, rest = dict(hook_dicts[0]), hook_dicts[1:]\n    for dct in rest:\n        ret.update(dct)\n\n    version = ret['minimum_pre_commit_version']\n    if parse_version(version) > parse_version(C.VERSION):\n        logger.error(\n            f'The hook `{ret[\"id\"]}` requires pre-commit version {version} '\n            f'but version {C.VERSION} is installed.  '\n            f'Perhaps run `pip install --upgrade pre-commit`.',\n        )\n        exit(1)\n\n    lang = ret['language']\n    if ret['language_version'] == C.DEFAULT:\n        ret['language_version'] = root_config['default_language_version'][lang]\n    if ret['language_version'] == C.DEFAULT:\n        ret['language_version'] = languages[lang].get_default_version()\n\n    if not ret['stages']:\n        ret['stages'] = root_config['default_stages']\n\n    if languages[lang].ENVIRONMENT_DIR is None:\n        if ret['language_version'] != C.DEFAULT:\n            logger.error(\n                f'The hook `{ret[\"id\"]}` specifies `language_version` but is '\n                f'using language `{lang}` which does not install an '\n                f'environment.  '\n                f'Perhaps you meant to use a specific language?',\n            )\n            exit(1)\n        if ret['additional_dependencies']:\n            logger.error(\n                f'The hook `{ret[\"id\"]}` specifies `additional_dependencies` '\n                f'but is using language `{lang}` which does not install an '\n                f'environment.  '\n                f'Perhaps you meant to use a specific language?',\n            )\n            exit(1)\n\n    return ret\n\n\ndef _non_cloned_repository_hooks(\n        repo_config: dict[str, Any],\n        store: Store,\n        root_config: dict[str, Any],\n) -> tuple[Hook, ...]:\n    def _prefix(language_name: str, deps: Sequence[str]) -> Prefix:\n        language = languages[language_name]\n        # pygrep / script / system / docker_image do not have\n        # environments so they work out of the current directory\n        if language.ENVIRONMENT_DIR is None:\n            return Prefix(os.getcwd())\n        else:\n            return Prefix(store.make_local(deps))\n\n    return tuple(\n        Hook.create(\n            repo_config['repo'],\n            _prefix(hook['language'], hook['additional_dependencies']),\n            _hook(hook, root_config=root_config),\n        )\n        for hook in repo_config['hooks']\n    )\n\n\ndef _cloned_repository_hooks(\n        repo_config: dict[str, Any],\n        store: Store,\n        root_config: dict[str, Any],\n) -> tuple[Hook, ...]:\n    repo, rev = repo_config['repo'], repo_config['rev']\n    manifest_path = os.path.join(store.clone(repo, rev), C.MANIFEST_FILE)\n    by_id = {hook['id']: hook for hook in load_manifest(manifest_path)}\n\n    for hook in repo_config['hooks']:\n        if hook['id'] not in by_id:\n            logger.error(\n                f'`{hook[\"id\"]}` is not present in repository {repo}.  '\n                f'Typo? Perhaps it is introduced in a newer version?  '\n                f'Often `pre-commit autoupdate` fixes this.',\n            )\n            exit(1)\n\n    hook_dcts = [\n        _hook(by_id[hook['id']], hook, root_config=root_config)\n        for hook in repo_config['hooks']\n    ]\n    return tuple(\n        Hook.create(\n            repo_config['repo'],\n            Prefix(store.clone(repo, rev, hook['additional_dependencies'])),\n            hook,\n        )\n        for hook in hook_dcts\n    )\n\n\ndef _repository_hooks(\n        repo_config: dict[str, Any],\n        store: Store,\n        root_config: dict[str, Any],\n) -> tuple[Hook, ...]:\n    if repo_config['repo'] in {LOCAL, META}:\n        return _non_cloned_repository_hooks(repo_config, store, root_config)\n    else:\n        return _cloned_repository_hooks(repo_config, store, root_config)\n\n\ndef install_hook_envs(hooks: Sequence[Hook], store: Store) -> None:\n    def _need_installed() -> list[Hook]:\n        seen: set[tuple[Prefix, str, str, tuple[str, ...]]] = set()\n        ret = []\n        for hook in hooks:\n            if hook.install_key not in seen and not _hook_installed(hook):\n                ret.append(hook)\n            seen.add(hook.install_key)\n        return ret\n\n    if not _need_installed():\n        return\n    with store.exclusive_lock():\n        # Another process may have already completed this work\n        for hook in _need_installed():\n            _hook_install(hook)\n\n\ndef all_hooks(root_config: dict[str, Any], store: Store) -> tuple[Hook, ...]:\n    return tuple(\n        hook\n        for repo in root_config['repos']\n        for hook in _repository_hooks(repo, store, root_config)\n    )\n", "path": "pre_commit/repository.py"}, {"content": "from __future__ import annotations\n\nimport re\nimport textwrap\n\nimport cfgv\nimport yaml\n\nfrom pre_commit.clientlib import InvalidConfigError\nfrom pre_commit.yaml import yaml_load\n\n\ndef _is_header_line(line: str) -> bool:\n    return line.startswith(('#', '---')) or not line.strip()\n\n\ndef _migrate_map(contents: str) -> str:\n    if isinstance(yaml_load(contents), list):\n        # Find the first non-header line\n        lines = contents.splitlines(True)\n        i = 0\n        # Only loop on non empty configuration file\n        while i < len(lines) and _is_header_line(lines[i]):\n            i += 1\n\n        header = ''.join(lines[:i])\n        rest = ''.join(lines[i:])\n\n        # If they are using the \"default\" flow style of yaml, this operation\n        # will yield a valid configuration\n        try:\n            trial_contents = f'{header}repos:\\n{rest}'\n            yaml_load(trial_contents)\n            contents = trial_contents\n        except yaml.YAMLError:\n            contents = f'{header}repos:\\n{textwrap.indent(rest, \" \" * 4)}'\n\n    return contents\n\n\ndef _migrate_sha_to_rev(contents: str) -> str:\n    return re.sub(r'(\\n\\s+)sha:', r'\\1rev:', contents)\n\n\ndef migrate_config(config_file: str, quiet: bool = False) -> int:\n    with open(config_file) as f:\n        orig_contents = contents = f.read()\n\n    with cfgv.reraise_as(InvalidConfigError):\n        with cfgv.validate_context(f'File {config_file}'):\n            try:\n                yaml_load(orig_contents)\n            except Exception as e:\n                raise cfgv.ValidationError(str(e))\n\n    contents = _migrate_map(contents)\n    contents = _migrate_sha_to_rev(contents)\n\n    if contents != orig_contents:\n        with open(config_file, 'w') as f:\n            f.write(contents)\n\n        print('Configuration has been migrated.')\n    elif not quiet:\n        print('Configuration is already migrated.')\n    return 0\n", "path": "pre_commit/commands/migrate_config.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport json\nimport logging\nimport os\nimport shlex\nfrom typing import Any\nfrom typing import Sequence\n\nimport pre_commit.constants as C\nfrom pre_commit.clientlib import load_manifest\nfrom pre_commit.clientlib import LOCAL\nfrom pre_commit.clientlib import META\nfrom pre_commit.clientlib import parse_version\nfrom pre_commit.hook import Hook\nfrom pre_commit.languages.all import languages\nfrom pre_commit.languages.helpers import environment_dir\nfrom pre_commit.prefix import Prefix\nfrom pre_commit.store import Store\nfrom pre_commit.util import clean_path_on_failure\nfrom pre_commit.util import rmtree\n\n\nlogger = logging.getLogger('pre_commit')\n\n\ndef _state_filename_v1(venv: str) -> str:\n    return os.path.join(venv, '.install_state_v1')\n\n\ndef _state_filename_v2(venv: str) -> str:\n    return os.path.join(venv, '.install_state_v2')\n\n\ndef _state(additional_deps: Sequence[str]) -> object:\n    return {'additional_dependencies': sorted(additional_deps)}\n\n\ndef _read_state(venv: str) -> object | None:\n    filename = _state_filename_v1(venv)\n    if not os.path.exists(filename):\n        return None\n    else:\n        with open(filename) as f:\n            return json.load(f)\n\n\ndef _hook_installed(hook: Hook) -> bool:\n    lang = languages[hook.language]\n    if lang.ENVIRONMENT_DIR is None:\n        return True\n\n    venv = environment_dir(\n        hook.prefix,\n        lang.ENVIRONMENT_DIR,\n        hook.language_version,\n    )\n    return (\n        (\n            os.path.exists(_state_filename_v2(venv)) or\n            _read_state(venv) == _state(hook.additional_dependencies)\n        ) and\n        not lang.health_check(hook.prefix, hook.language_version)\n    )\n\n\ndef _hook_install(hook: Hook) -> None:\n    logger.info(f'Installing environment for {hook.src}.')\n    logger.info('Once installed this environment will be reused.')\n    logger.info('This may take a few minutes...')\n\n    if hook.language == 'python_venv':\n        logger.warning(\n            f'`repo: {hook.src}` uses deprecated `language: python_venv`.  '\n            f'This is an alias for `language: python`.  '\n            f'Often `pre-commit autoupdate --repo {shlex.quote(hook.src)}` '\n            f'will fix this.',\n        )\n\n    lang = languages[hook.language]\n    assert lang.ENVIRONMENT_DIR is not None\n\n    venv = environment_dir(\n        hook.prefix,\n        lang.ENVIRONMENT_DIR,\n        hook.language_version,\n    )\n\n    # There's potentially incomplete cleanup from previous runs\n    # Clean it up!\n    if os.path.exists(venv):\n        rmtree(venv)\n\n    with clean_path_on_failure(venv):\n        lang.install_environment(\n            hook.prefix, hook.language_version, hook.additional_dependencies,\n        )\n        health_error = lang.health_check(hook.prefix, hook.language_version)\n        if health_error:\n            raise AssertionError(\n                f'BUG: expected environment for {hook.language} to be healthy '\n                f'immediately after install, please open an issue describing '\n                f'your environment\\n\\n'\n                f'more info:\\n\\n{health_error}',\n            )\n\n        # TODO: remove v1 state writing, no longer needed after pre-commit 3.0\n        # Write our state to indicate we're installed\n        state_filename = _state_filename_v1(venv)\n        staging = f'{state_filename}staging'\n        with open(staging, 'w') as state_file:\n            state_file.write(json.dumps(_state(hook.additional_dependencies)))\n        # Move the file into place atomically to indicate we've installed\n        os.replace(staging, state_filename)\n\n        open(_state_filename_v2(venv), 'a+').close()\n\n\ndef _hook(\n        *hook_dicts: dict[str, Any],\n        root_config: dict[str, Any],\n) -> dict[str, Any]:\n    ret, rest = dict(hook_dicts[0]), hook_dicts[1:]\n    for dct in rest:\n        ret.update(dct)\n\n    version = ret['minimum_pre_commit_version']\n    if parse_version(version) > parse_version(C.VERSION):\n        logger.error(\n            f'The hook `{ret[\"id\"]}` requires pre-commit version {version} '\n            f'but version {C.VERSION} is installed.  '\n            f'Perhaps run `pip install --upgrade pre-commit`.',\n        )\n        exit(1)\n\n    lang = ret['language']\n    if ret['language_version'] == C.DEFAULT:\n        ret['language_version'] = root_config['default_language_version'][lang]\n    if ret['language_version'] == C.DEFAULT:\n        ret['language_version'] = languages[lang].get_default_version()\n\n    if not ret['stages']:\n        ret['stages'] = root_config['default_stages']\n\n    if languages[lang].ENVIRONMENT_DIR is None:\n        if ret['language_version'] != C.DEFAULT:\n            logger.error(\n                f'The hook `{ret[\"id\"]}` specifies `language_version` but is '\n                f'using language `{lang}` which does not install an '\n                f'environment.  '\n                f'Perhaps you meant to use a specific language?',\n            )\n            exit(1)\n        if ret['additional_dependencies']:\n            logger.error(\n                f'The hook `{ret[\"id\"]}` specifies `additional_dependencies` '\n                f'but is using language `{lang}` which does not install an '\n                f'environment.  '\n                f'Perhaps you meant to use a specific language?',\n            )\n            exit(1)\n\n    return ret\n\n\ndef _non_cloned_repository_hooks(\n        repo_config: dict[str, Any],\n        store: Store,\n        root_config: dict[str, Any],\n) -> tuple[Hook, ...]:\n    def _prefix(language_name: str, deps: Sequence[str]) -> Prefix:\n        language = languages[language_name]\n        # pygrep / script / system / docker_image do not have\n        # environments so they work out of the current directory\n        if language.ENVIRONMENT_DIR is None:\n            return Prefix(os.getcwd())\n        else:\n            return Prefix(store.make_local(deps))\n\n    return tuple(\n        Hook.create(\n            repo_config['repo'],\n            _prefix(hook['language'], hook['additional_dependencies']),\n            _hook(hook, root_config=root_config),\n        )\n        for hook in repo_config['hooks']\n    )\n\n\ndef _cloned_repository_hooks(\n        repo_config: dict[str, Any],\n        store: Store,\n        root_config: dict[str, Any],\n) -> tuple[Hook, ...]:\n    repo, rev = repo_config['repo'], repo_config['rev']\n    manifest_path = os.path.join(store.clone(repo, rev), C.MANIFEST_FILE)\n    by_id = {hook['id']: hook for hook in load_manifest(manifest_path)}\n\n    for hook in repo_config['hooks']:\n        if hook['id'] not in by_id:\n            logger.error(\n                f'`{hook[\"id\"]}` is not present in repository {repo}.  '\n                f'Typo? Perhaps it is introduced in a newer version?  '\n                f'Often `pre-commit autoupdate` fixes this.',\n            )\n            exit(1)\n\n    hook_dcts = [\n        _hook(by_id[hook['id']], hook, root_config=root_config)\n        for hook in repo_config['hooks']\n    ]\n    return tuple(\n        Hook.create(\n            repo_config['repo'],\n            Prefix(store.clone(repo, rev, hook['additional_dependencies'])),\n            hook,\n        )\n        for hook in hook_dcts\n    )\n\n\ndef _repository_hooks(\n        repo_config: dict[str, Any],\n        store: Store,\n        root_config: dict[str, Any],\n) -> tuple[Hook, ...]:\n    if repo_config['repo'] in {LOCAL, META}:\n        return _non_cloned_repository_hooks(repo_config, store, root_config)\n    else:\n        return _cloned_repository_hooks(repo_config, store, root_config)\n\n\ndef install_hook_envs(hooks: Sequence[Hook], store: Store) -> None:\n    def _need_installed() -> list[Hook]:\n        seen: set[tuple[Prefix, str, str, tuple[str, ...]]] = set()\n        ret = []\n        for hook in hooks:\n            if hook.install_key not in seen and not _hook_installed(hook):\n                ret.append(hook)\n            seen.add(hook.install_key)\n        return ret\n\n    if not _need_installed():\n        return\n    with store.exclusive_lock():\n        # Another process may have already completed this work\n        for hook in _need_installed():\n            _hook_install(hook)\n\n\ndef all_hooks(root_config: dict[str, Any], store: Store) -> tuple[Hook, ...]:\n    return tuple(\n        hook\n        for repo in root_config['repos']\n        for hook in _repository_hooks(repo, store, root_config)\n    )\n", "path": "pre_commit/repository.py"}, {"content": "from __future__ import annotations\n\nimport re\nimport textwrap\n\nimport cfgv\nimport yaml\n\nfrom pre_commit.clientlib import InvalidConfigError\nfrom pre_commit.yaml import yaml_load\n\n\ndef _is_header_line(line: str) -> bool:\n    return line.startswith(('#', '---')) or not line.strip()\n\n\ndef _migrate_map(contents: str) -> str:\n    if isinstance(yaml_load(contents), list):\n        # Find the first non-header line\n        lines = contents.splitlines(True)\n        i = 0\n        # Only loop on non empty configuration file\n        while i < len(lines) and _is_header_line(lines[i]):\n            i += 1\n\n        header = ''.join(lines[:i])\n        rest = ''.join(lines[i:])\n\n        # If they are using the \"default\" flow style of yaml, this operation\n        # will yield a valid configuration\n        try:\n            trial_contents = f'{header}repos:\\n{rest}'\n            yaml_load(trial_contents)\n            contents = trial_contents\n        except yaml.YAMLError:\n            contents = f'{header}repos:\\n{textwrap.indent(rest, \" \" * 4)}'\n\n    return contents\n\n\ndef _migrate_sha_to_rev(contents: str) -> str:\n    return re.sub(r'(\\n\\s+)sha:', r'\\1rev:', contents)\n\n\ndef _migrate_python_venv(contents: str) -> str:\n    return re.sub(\n        r'(\\n\\s+)language: python_venv\\b',\n        r'\\1language: python',\n        contents,\n    )\n\n\ndef migrate_config(config_file: str, quiet: bool = False) -> int:\n    with open(config_file) as f:\n        orig_contents = contents = f.read()\n\n    with cfgv.reraise_as(InvalidConfigError):\n        with cfgv.validate_context(f'File {config_file}'):\n            try:\n                yaml_load(orig_contents)\n            except Exception as e:\n                raise cfgv.ValidationError(str(e))\n\n    contents = _migrate_map(contents)\n    contents = _migrate_sha_to_rev(contents)\n    contents = _migrate_python_venv(contents)\n\n    if contents != orig_contents:\n        with open(config_file, 'w') as f:\n            f.write(contents)\n\n        print('Configuration has been migrated.')\n    elif not quiet:\n        print('Configuration is already migrated.')\n    return 0\n", "path": "pre_commit/commands/migrate_config.py"}]}