Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pre-commit for 
openSUSE:Factory checked in at 2024-10-25 19:19:47
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pre-commit (Old)
 and      /work/SRC/openSUSE:Factory/.python-pre-commit.new.2020 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pre-commit"

Fri Oct 25 19:19:47 2024 rev:22 rq:1218114 version:4.0.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pre-commit/python-pre-commit.changes      
2024-08-06 09:08:32.406225019 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-pre-commit.new.2020/python-pre-commit.changes
    2024-10-25 19:20:39.221157451 +0200
@@ -1,0 +2,16 @@
+Thu Oct 24 19:23:34 UTC 2024 - Dirk Müller <dmuel...@suse.com>
+
+- update to 4.0.1:
+  * Fix `pre-commit migrate-config` for unquoted deprecated
+    stages names with purelib `pyyaml`.
+  * Improve `pre-commit migrate-config` to handle more yaml
+    formats.
+  * Handle `stages` deprecation in `pre-commit migrate-config`.
+  * Upgrade `ruby-build`.
+  * Add "sensible regex" warnings to `repo: meta`.
+  * Add warnings for deprecated `stages` (`commit` -> `pre-
+    commit`, `push` -> `pre-push`, `merge-commit` -> `pre-merge-commit`).
+  * `language: python_venv` has been removed -- use `language:
+    python` instead.
+
+-------------------------------------------------------------------

Old:
----
  pre-commit-3.8.0.tar.gz

New:
----
  pre-commit-4.0.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pre-commit.spec ++++++
--- /var/tmp/diff_new_pack.ocM3e6/_old  2024-10-25 19:20:39.685176811 +0200
+++ /var/tmp/diff_new_pack.ocM3e6/_new  2024-10-25 19:20:39.689176978 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-pre-commit
-Version:        3.8.0
+Version:        4.0.1
 Release:        0
 Summary:        Multi-language pre-commit hooks
 License:        MIT

++++++ pre-commit-3.8.0.tar.gz -> pre-commit-4.0.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/.pre-commit-config.yaml 
new/pre-commit-4.0.1/.pre-commit-config.yaml
--- old/pre-commit-3.8.0/.pre-commit-config.yaml        2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/.pre-commit-config.yaml        2024-10-08 
18:08:49.000000000 +0200
@@ -1,6 +1,6 @@
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.6.0
+    rev: v5.0.0
     hooks:
     -   id: trailing-whitespace
     -   id: end-of-file-fixer
@@ -24,7 +24,7 @@
     hooks:
     -   id: add-trailing-comma
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v3.16.0
+    rev: v3.17.0
     hooks:
     -   id: pyupgrade
         args: [--py39-plus]
@@ -33,11 +33,11 @@
     hooks:
     -   id: autopep8
 -   repo: https://github.com/PyCQA/flake8
-    rev: 7.1.0
+    rev: 7.1.1
     hooks:
     -   id: flake8
 -   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.11.0
+    rev: v1.11.2
     hooks:
     -   id: mypy
         additional_dependencies: [types-pyyaml]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/CHANGELOG.md 
new/pre-commit-4.0.1/CHANGELOG.md
--- old/pre-commit-3.8.0/CHANGELOG.md   2024-07-28 21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/CHANGELOG.md   2024-10-08 18:08:49.000000000 +0200
@@ -1,3 +1,37 @@
+4.0.1 - 2024-10-08
+==================
+
+### Fixes
+- Fix `pre-commit migrate-config` for unquoted deprecated stages names with
+  purelib `pyyaml`.
+    - #3324 PR by @asottile.
+    - pre-commit-ci/issues#234 issue by @lorenzwalthert.
+
+4.0.0 - 2024-10-05
+==================
+
+### Features
+- Improve `pre-commit migrate-config` to handle more yaml formats.
+    - #3301 PR by @asottile.
+- Handle `stages` deprecation in `pre-commit migrate-config`.
+    - #3302 PR by @asottile.
+    - #2732 issue by @asottile.
+- Upgrade `ruby-build`.
+    - #3199 PR by @ThisGuyCodes.
+- Add "sensible regex" warnings to `repo: meta`.
+    - #3311 PR by @asottile.
+- Add warnings for deprecated `stages` (`commit` -> `pre-commit`, `push` ->
+  `pre-push`, `merge-commit` -> `pre-merge-commit`).
+    - #3312 PR by @asottile.
+    - #3313 PR by @asottile.
+    - #3315 PR by @asottile.
+    - #2732 issue by @asottile.
+
+### Migrating
+- `language: python_venv` has been removed -- use `language: python` instead.
+    - #3320 PR by @asottile.
+    - #2734 issue by @asottile.
+
 3.8.0 - 2024-07-28
 ==================
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/all_languages.py 
new/pre-commit-4.0.1/pre_commit/all_languages.py
--- old/pre-commit-3.8.0/pre_commit/all_languages.py    2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/all_languages.py    2024-10-08 
18:08:49.000000000 +0200
@@ -44,7 +44,5 @@
     'script': script,
     'swift': swift,
     'system': system,
-    # TODO: fully deprecate `python_venv`
-    'python_venv': python,
 }
 language_names = sorted(languages)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/clientlib.py 
new/pre-commit-4.0.1/pre_commit/clientlib.py
--- old/pre-commit-3.8.0/pre_commit/clientlib.py        2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/clientlib.py        2024-10-08 
18:08:49.000000000 +0200
@@ -2,6 +2,7 @@
 
 import functools
 import logging
+import os.path
 import re
 import shlex
 import sys
@@ -70,6 +71,43 @@
     return _STAGES.get(stage, stage)
 
 
+MINIMAL_MANIFEST_SCHEMA = cfgv.Array(
+    cfgv.Map(
+        'Hook', 'id',
+        cfgv.Required('id', cfgv.check_string),
+        cfgv.Optional('stages', cfgv.check_array(cfgv.check_string), []),
+    ),
+)
+
+
+def warn_for_stages_on_repo_init(repo: str, directory: str) -> None:
+    try:
+        manifest = cfgv.load_from_filename(
+            os.path.join(directory, C.MANIFEST_FILE),
+            schema=MINIMAL_MANIFEST_SCHEMA,
+            load_strategy=yaml_load,
+            exc_tp=InvalidManifestError,
+        )
+    except InvalidManifestError:
+        return  # they'll get a better error message when it actually loads!
+
+    legacy_stages = {}  # sorted set
+    for hook in manifest:
+        for stage in hook.get('stages', ()):
+            if stage in _STAGES:
+                legacy_stages[stage] = True
+
+    if legacy_stages:
+        logger.warning(
+            f'repo `{repo}` uses deprecated stage names '
+            f'({", ".join(legacy_stages)}) which will be removed in a '
+            f'future version.  '
+            f'Hint: often `pre-commit autoupdate --repo {shlex.quote(repo)}` '
+            f'will fix this.  '
+            f'if it does not -- consider reporting an issue to that repo.',
+        )
+
+
 class StagesMigrationNoDefault(NamedTuple):
     key: str
     default: Sequence[str]
@@ -99,6 +137,58 @@
         super().apply_default(dct)
 
 
+class DeprecatedStagesWarning(NamedTuple):
+    key: str
+
+    def check(self, dct: dict[str, Any]) -> None:
+        if self.key not in dct:
+            return
+
+        val = dct[self.key]
+        cfgv.check_array(cfgv.check_any)(val)
+
+        legacy_stages = [stage for stage in val if stage in _STAGES]
+        if legacy_stages:
+            logger.warning(
+                f'hook id `{dct["id"]}` uses deprecated stage names '
+                f'({", ".join(legacy_stages)}) which will be removed in a '
+                f'future version.  '
+                f'run: `pre-commit migrate-config` to automatically fix this.',
+            )
+
+    def apply_default(self, dct: dict[str, Any]) -> None:
+        pass
+
+    def remove_default(self, dct: dict[str, Any]) -> None:
+        raise NotImplementedError
+
+
+class DeprecatedDefaultStagesWarning(NamedTuple):
+    key: str
+
+    def check(self, dct: dict[str, Any]) -> None:
+        if self.key not in dct:
+            return
+
+        val = dct[self.key]
+        cfgv.check_array(cfgv.check_any)(val)
+
+        legacy_stages = [stage for stage in val if stage in _STAGES]
+        if legacy_stages:
+            logger.warning(
+                f'top-level `default_stages` uses deprecated stage names '
+                f'({", ".join(legacy_stages)}) which will be removed in a '
+                f'future version.  '
+                f'run: `pre-commit migrate-config` to automatically fix this.',
+            )
+
+    def apply_default(self, dct: dict[str, Any]) -> None:
+        pass
+
+    def remove_default(self, dct: dict[str, Any]) -> None:
+        raise NotImplementedError
+
+
 MANIFEST_HOOK_DICT = cfgv.Map(
     'Hook', 'id',
 
@@ -267,6 +357,12 @@
             raise cfgv.ValidationError(f'{self.key!r} cannot be overridden')
 
 
+_COMMON_HOOK_WARNINGS = (
+    OptionalSensibleRegexAtHook('files', cfgv.check_string),
+    OptionalSensibleRegexAtHook('exclude', cfgv.check_string),
+    DeprecatedStagesWarning('stages'),
+)
+
 META_HOOK_DICT = cfgv.Map(
     'Hook', 'id',
     cfgv.Required('id', cfgv.check_string),
@@ -289,6 +385,7 @@
         item
         for item in MANIFEST_HOOK_DICT.items
     ),
+    *_COMMON_HOOK_WARNINGS,
 )
 CONFIG_HOOK_DICT = cfgv.Map(
     'Hook', 'id',
@@ -306,16 +403,13 @@
         if item.key != 'stages'
     ),
     StagesMigrationNoDefault('stages', []),
-    OptionalSensibleRegexAtHook('files', cfgv.check_string),
-    OptionalSensibleRegexAtHook('exclude', cfgv.check_string),
+    *_COMMON_HOOK_WARNINGS,
 )
 LOCAL_HOOK_DICT = cfgv.Map(
     'Hook', 'id',
 
     *MANIFEST_HOOK_DICT.items,
-
-    OptionalSensibleRegexAtHook('files', cfgv.check_string),
-    OptionalSensibleRegexAtHook('exclude', cfgv.check_string),
+    *_COMMON_HOOK_WARNINGS,
 )
 CONFIG_REPO_DICT = cfgv.Map(
     'Repository', 'repo',
@@ -368,6 +462,7 @@
         'default_language_version', DEFAULT_LANGUAGE_VERSION, {},
     ),
     StagesMigration('default_stages', STAGES),
+    DeprecatedDefaultStagesWarning('default_stages'),
     cfgv.Optional('files', check_string_regex, ''),
     cfgv.Optional('exclude', check_string_regex, '^$'),
     cfgv.Optional('fail_fast', cfgv.check_bool, False),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pre-commit-3.8.0/pre_commit/commands/migrate_config.py 
new/pre-commit-4.0.1/pre_commit/commands/migrate_config.py
--- old/pre-commit-3.8.0/pre_commit/commands/migrate_config.py  2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/commands/migrate_config.py  2024-10-08 
18:08:49.000000000 +0200
@@ -1,13 +1,21 @@
 from __future__ import annotations
 
-import re
+import functools
+import itertools
 import textwrap
+from typing import Callable
 
 import cfgv
 import yaml
+from yaml.nodes import ScalarNode
 
 from pre_commit.clientlib import InvalidConfigError
+from pre_commit.yaml import yaml_compose
 from pre_commit.yaml import yaml_load
+from pre_commit.yaml_rewrite import MappingKey
+from pre_commit.yaml_rewrite import MappingValue
+from pre_commit.yaml_rewrite import match
+from pre_commit.yaml_rewrite import SequenceItem
 
 
 def _is_header_line(line: str) -> bool:
@@ -38,16 +46,69 @@
     return contents
 
 
-def _migrate_sha_to_rev(contents: str) -> str:
-    return re.sub(r'(\n\s+)sha:', r'\1rev:', contents)
+def _preserve_style(n: ScalarNode, *, s: str) -> str:
+    style = n.style or ''
+    return f'{style}{s}{style}'
 
 
-def _migrate_python_venv(contents: str) -> str:
-    return re.sub(
-        r'(\n\s+)language: python_venv\b',
-        r'\1language: python',
-        contents,
+def _fix_stage(n: ScalarNode) -> str:
+    return _preserve_style(n, s=f'pre-{n.value}')
+
+
+def _migrate_composed(contents: str) -> str:
+    tree = yaml_compose(contents)
+    rewrites: list[tuple[ScalarNode, Callable[[ScalarNode], str]]] = []
+
+    # sha -> rev
+    sha_to_rev_replace = functools.partial(_preserve_style, s='rev')
+    sha_to_rev_matcher = (
+        MappingValue('repos'),
+        SequenceItem(),
+        MappingKey('sha'),
+    )
+    for node in match(tree, sha_to_rev_matcher):
+        rewrites.append((node, sha_to_rev_replace))
+
+    # python_venv -> python
+    language_matcher = (
+        MappingValue('repos'),
+        SequenceItem(),
+        MappingValue('hooks'),
+        SequenceItem(),
+        MappingValue('language'),
+    )
+    python_venv_replace = functools.partial(_preserve_style, s='python')
+    for node in match(tree, language_matcher):
+        if node.value == 'python_venv':
+            rewrites.append((node, python_venv_replace))
+
+    # stages rewrites
+    default_stages_matcher = (MappingValue('default_stages'), SequenceItem())
+    default_stages_match = match(tree, default_stages_matcher)
+    hook_stages_matcher = (
+        MappingValue('repos'),
+        SequenceItem(),
+        MappingValue('hooks'),
+        SequenceItem(),
+        MappingValue('stages'),
+        SequenceItem(),
     )
+    hook_stages_match = match(tree, hook_stages_matcher)
+    for node in itertools.chain(default_stages_match, hook_stages_match):
+        if node.value in {'commit', 'push', 'merge-commit'}:
+            rewrites.append((node, _fix_stage))
+
+    rewrites.sort(reverse=True, key=lambda nf: nf[0].start_mark.index)
+
+    src_parts = []
+    end: int | None = None
+    for node, func in rewrites:
+        src_parts.append(contents[node.end_mark.index:end])
+        src_parts.append(func(node))
+        end = node.start_mark.index
+    src_parts.append(contents[:end])
+    src_parts.reverse()
+    return ''.join(src_parts)
 
 
 def migrate_config(config_file: str, quiet: bool = False) -> int:
@@ -62,8 +123,7 @@
                 raise cfgv.ValidationError(str(e))
 
     contents = _migrate_map(contents)
-    contents = _migrate_sha_to_rev(contents)
-    contents = _migrate_python_venv(contents)
+    contents = _migrate_composed(contents)
 
     if contents != orig_contents:
         with open(config_file, 'w') as f:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/commands/run.py 
new/pre-commit-4.0.1/pre_commit/commands/run.py
--- old/pre-commit-3.8.0/pre_commit/commands/run.py     2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/commands/run.py     2024-10-08 
18:08:49.000000000 +0200
@@ -61,7 +61,7 @@
         names: Iterable[str],
         include: str,
         exclude: str,
-) -> Generator[str, None, None]:
+) -> Generator[str]:
     include_re, exclude_re = re.compile(include), re.compile(exclude)
     return (
         filename for filename in names
@@ -84,7 +84,7 @@
             types: Iterable[str],
             types_or: Iterable[str],
             exclude_types: Iterable[str],
-    ) -> Generator[str, None, None]:
+    ) -> Generator[str]:
         types = frozenset(types)
         types_or = frozenset(types_or)
         exclude_types = frozenset(exclude_types)
@@ -97,7 +97,7 @@
             ):
                 yield filename
 
-    def filenames_for_hook(self, hook: Hook) -> Generator[str, None, None]:
+    def filenames_for_hook(self, hook: Hook) -> Generator[str]:
         return self.by_types(
             filter_by_include_exclude(
                 self.filenames,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/envcontext.py 
new/pre-commit-4.0.1/pre_commit/envcontext.py
--- old/pre-commit-3.8.0/pre_commit/envcontext.py       2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/envcontext.py       2024-10-08 
18:08:49.000000000 +0200
@@ -33,7 +33,7 @@
 def envcontext(
         patch: PatchesT,
         _env: MutableMapping[str, str] | None = None,
-) -> Generator[None, None, None]:
+) -> Generator[None]:
     """In this context, `os.environ` is modified according to `patch`.
 
     `patch` is an iterable of 2-tuples (key, value):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/error_handler.py 
new/pre-commit-4.0.1/pre_commit/error_handler.py
--- old/pre-commit-3.8.0/pre_commit/error_handler.py    2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/error_handler.py    2024-10-08 
18:08:49.000000000 +0200
@@ -68,7 +68,7 @@
 
 
 @contextlib.contextmanager
-def error_handler() -> Generator[None, None, None]:
+def error_handler() -> Generator[None]:
     try:
         yield
     except (Exception, KeyboardInterrupt) as e:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/file_lock.py 
new/pre-commit-4.0.1/pre_commit/file_lock.py
--- old/pre-commit-3.8.0/pre_commit/file_lock.py        2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/file_lock.py        2024-10-08 
18:08:49.000000000 +0200
@@ -20,7 +20,7 @@
     def _locked(
             fileno: int,
             blocked_cb: Callable[[], None],
-    ) -> Generator[None, None, None]:
+    ) -> Generator[None]:
         try:
             msvcrt.locking(fileno, msvcrt.LK_NBLCK, _region)
         except OSError:
@@ -53,7 +53,7 @@
     def _locked(
             fileno: int,
             blocked_cb: Callable[[], None],
-    ) -> Generator[None, None, None]:
+    ) -> Generator[None]:
         try:
             fcntl.flock(fileno, fcntl.LOCK_EX | fcntl.LOCK_NB)
         except OSError:  # pragma: no cover (tests are single-threaded)
@@ -69,7 +69,7 @@
 def lock(
         path: str,
         blocked_cb: Callable[[], None],
-) -> Generator[None, None, None]:
+) -> Generator[None]:
     with open(path, 'a+') as f:
         with _locked(f.fileno(), blocked_cb):
             yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/lang_base.py 
new/pre-commit-4.0.1/pre_commit/lang_base.py
--- old/pre-commit-3.8.0/pre_commit/lang_base.py        2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/lang_base.py        2024-10-08 
18:08:49.000000000 +0200
@@ -127,7 +127,7 @@
 
 
 @contextlib.contextmanager
-def no_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def no_env(prefix: Prefix, version: str) -> Generator[None]:
     yield
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/conda.py 
new/pre-commit-4.0.1/pre_commit/languages/conda.py
--- old/pre-commit-3.8.0/pre_commit/languages/conda.py  2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/conda.py  2024-10-08 
18:08:49.000000000 +0200
@@ -41,7 +41,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/coursier.py 
new/pre-commit-4.0.1/pre_commit/languages/coursier.py
--- old/pre-commit-3.8.0/pre_commit/languages/coursier.py       2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/coursier.py       2024-10-08 
18:08:49.000000000 +0200
@@ -70,7 +70,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/dart.py 
new/pre-commit-4.0.1/pre_commit/languages/dart.py
--- old/pre-commit-3.8.0/pre_commit/languages/dart.py   2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/dart.py   2024-10-08 
18:08:49.000000000 +0200
@@ -29,7 +29,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/dotnet.py 
new/pre-commit-4.0.1/pre_commit/languages/dotnet.py
--- old/pre-commit-3.8.0/pre_commit/languages/dotnet.py 2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/dotnet.py 2024-10-08 
18:08:49.000000000 +0200
@@ -30,14 +30,14 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
 
 
 @contextlib.contextmanager
-def _nuget_config_no_sources() -> Generator[str, None, None]:
+def _nuget_config_no_sources() -> Generator[str]:
     with tempfile.TemporaryDirectory() as tmpdir:
         nuget_config = os.path.join(tmpdir, 'nuget.config')
         with open(nuget_config, 'w') as f:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/golang.py 
new/pre-commit-4.0.1/pre_commit/languages/golang.py
--- old/pre-commit-3.8.0/pre_commit/languages/golang.py 2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/golang.py 2024-10-08 
18:08:49.000000000 +0200
@@ -121,7 +121,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir, version)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/haskell.py 
new/pre-commit-4.0.1/pre_commit/languages/haskell.py
--- old/pre-commit-3.8.0/pre_commit/languages/haskell.py        2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/haskell.py        2024-10-08 
18:08:49.000000000 +0200
@@ -24,7 +24,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/lua.py 
new/pre-commit-4.0.1/pre_commit/languages/lua.py
--- old/pre-commit-3.8.0/pre_commit/languages/lua.py    2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/lua.py    2024-10-08 
18:08:49.000000000 +0200
@@ -44,7 +44,7 @@
 
 
 @contextlib.contextmanager  # pragma: win32 no cover
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/node.py 
new/pre-commit-4.0.1/pre_commit/languages/node.py
--- old/pre-commit-3.8.0/pre_commit/languages/node.py   2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/node.py   2024-10-08 
18:08:49.000000000 +0200
@@ -59,7 +59,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/perl.py 
new/pre-commit-4.0.1/pre_commit/languages/perl.py
--- old/pre-commit-3.8.0/pre_commit/languages/perl.py   2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/perl.py   2024-10-08 
18:08:49.000000000 +0200
@@ -33,7 +33,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/python.py 
new/pre-commit-4.0.1/pre_commit/languages/python.py
--- old/pre-commit-3.8.0/pre_commit/languages/python.py 2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/python.py 2024-10-08 
18:08:49.000000000 +0200
@@ -152,7 +152,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/r.py 
new/pre-commit-4.0.1/pre_commit/languages/r.py
--- old/pre-commit-3.8.0/pre_commit/languages/r.py      2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/r.py      2024-10-08 
18:08:49.000000000 +0200
@@ -85,7 +85,7 @@
 
 
 @contextlib.contextmanager
-def _r_code_in_tempfile(code: str) -> Generator[str, None, None]:
+def _r_code_in_tempfile(code: str) -> Generator[str]:
     """
     To avoid quoting and escaping issues, avoid `Rscript [options] -e {expr}`
     but use `Rscript [options] path/to/file_with_expr.R`
@@ -105,7 +105,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/ruby.py 
new/pre-commit-4.0.1/pre_commit/languages/ruby.py
--- old/pre-commit-3.8.0/pre_commit/languages/ruby.py   2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/ruby.py   2024-10-08 
18:08:49.000000000 +0200
@@ -73,7 +73,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir, version)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/rust.py 
new/pre-commit-4.0.1/pre_commit/languages/rust.py
--- old/pre-commit-3.8.0/pre_commit/languages/rust.py   2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/rust.py   2024-10-08 
18:08:49.000000000 +0200
@@ -61,7 +61,7 @@
 
 
 @contextlib.contextmanager
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir, version)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/languages/swift.py 
new/pre-commit-4.0.1/pre_commit/languages/swift.py
--- old/pre-commit-3.8.0/pre_commit/languages/swift.py  2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/languages/swift.py  2024-10-08 
18:08:49.000000000 +0200
@@ -27,7 +27,7 @@
 
 
 @contextlib.contextmanager  # pragma: win32 no cover
-def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
+def in_env(prefix: Prefix, version: str) -> Generator[None]:
     envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
     with envcontext(get_env_patch(envdir)):
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/logging_handler.py 
new/pre-commit-4.0.1/pre_commit/logging_handler.py
--- old/pre-commit-3.8.0/pre_commit/logging_handler.py  2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/logging_handler.py  2024-10-08 
18:08:49.000000000 +0200
@@ -32,7 +32,7 @@
 
 
 @contextlib.contextmanager
-def logging_handler(use_color: bool) -> Generator[None, None, None]:
+def logging_handler(use_color: bool) -> Generator[None]:
     handler = LoggingHandler(use_color)
     logger.addHandler(handler)
     logger.setLevel(logging.INFO)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/repository.py 
new/pre-commit-4.0.1/pre_commit/repository.py
--- old/pre-commit-3.8.0/pre_commit/repository.py       2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/repository.py       2024-10-08 
18:08:49.000000000 +0200
@@ -3,7 +3,6 @@
 import json
 import logging
 import os
-import shlex
 from collections.abc import Sequence
 from typing import Any
 
@@ -68,14 +67,6 @@
     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
 
Binary files old/pre-commit-3.8.0/pre_commit/resources/rbenv.tar.gz and 
new/pre-commit-4.0.1/pre_commit/resources/rbenv.tar.gz differ
Binary files old/pre-commit-3.8.0/pre_commit/resources/ruby-build.tar.gz and 
new/pre-commit-4.0.1/pre_commit/resources/ruby-build.tar.gz differ
Binary files old/pre-commit-3.8.0/pre_commit/resources/ruby-download.tar.gz and 
new/pre-commit-4.0.1/pre_commit/resources/ruby-download.tar.gz differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/staged_files_only.py 
new/pre-commit-4.0.1/pre_commit/staged_files_only.py
--- old/pre-commit-3.8.0/pre_commit/staged_files_only.py        2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/pre_commit/staged_files_only.py        2024-10-08 
18:08:49.000000000 +0200
@@ -33,7 +33,7 @@
 
 
 @contextlib.contextmanager
-def _intent_to_add_cleared() -> Generator[None, None, None]:
+def _intent_to_add_cleared() -> Generator[None]:
     intent_to_add = git.intent_to_add_files()
     if intent_to_add:
         logger.warning('Unstaged intent-to-add files detected.')
@@ -48,7 +48,7 @@
 
 
 @contextlib.contextmanager
-def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
+def _unstaged_changes_cleared(patch_dir: str) -> Generator[None]:
     tree = cmd_output('git', 'write-tree')[1].strip()
     diff_cmd = (
         'git', 'diff-index', '--ignore-submodules', '--binary',
@@ -105,7 +105,7 @@
 
 
 @contextlib.contextmanager
-def staged_files_only(patch_dir: str) -> Generator[None, None, None]:
+def staged_files_only(patch_dir: str) -> Generator[None]:
     """Clear any unstaged changes from the git working directory inside this
     context.
     """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/store.py 
new/pre-commit-4.0.1/pre_commit/store.py
--- old/pre-commit-3.8.0/pre_commit/store.py    2024-07-28 21:58:29.000000000 
+0200
+++ new/pre-commit-4.0.1/pre_commit/store.py    2024-10-08 18:08:49.000000000 
+0200
@@ -10,6 +10,7 @@
 from typing import Callable
 
 import pre_commit.constants as C
+from pre_commit import clientlib
 from pre_commit import file_lock
 from pre_commit import git
 from pre_commit.util import CalledProcessError
@@ -101,7 +102,7 @@
             os.replace(tmpfile, self.db_path)
 
     @contextlib.contextmanager
-    def exclusive_lock(self) -> Generator[None, None, None]:
+    def exclusive_lock(self) -> Generator[None]:
         def blocked_cb() -> None:  # pragma: no cover (tests are in-process)
             logger.info('Locking pre-commit directory')
 
@@ -112,7 +113,7 @@
     def connect(
             self,
             db_path: str | None = None,
-    ) -> Generator[sqlite3.Connection, None, None]:
+    ) -> Generator[sqlite3.Connection]:
         db_path = db_path or self.db_path
         # sqlite doesn't close its fd with its contextmanager >.<
         # contextlib.closing fixes this.
@@ -136,6 +137,7 @@
             deps: Sequence[str],
             make_strategy: Callable[[str], None],
     ) -> str:
+        original_repo = repo
         repo = self.db_repo_name(repo, deps)
 
         def _get_result() -> str | None:
@@ -168,6 +170,9 @@
                     'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)',
                     [repo, ref, directory],
                 )
+
+            clientlib.warn_for_stages_on_repo_init(original_repo, directory)
+
         return directory
 
     def _complete_clone(self, ref: str, git_cmd: Callable[..., None]) -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/util.py 
new/pre-commit-4.0.1/pre_commit/util.py
--- old/pre-commit-3.8.0/pre_commit/util.py     2024-07-28 21:58:29.000000000 
+0200
+++ new/pre-commit-4.0.1/pre_commit/util.py     2024-10-08 18:08:49.000000000 
+0200
@@ -25,7 +25,7 @@
 
 
 @contextlib.contextmanager
-def clean_path_on_failure(path: str) -> Generator[None, None, None]:
+def clean_path_on_failure(path: str) -> Generator[None]:
     """Cleans up the directory on an exceptional failure."""
     try:
         yield
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/xargs.py 
new/pre-commit-4.0.1/pre_commit/xargs.py
--- old/pre-commit-3.8.0/pre_commit/xargs.py    2024-07-28 21:58:29.000000000 
+0200
+++ new/pre-commit-4.0.1/pre_commit/xargs.py    2024-10-08 18:08:49.000000000 
+0200
@@ -120,7 +120,6 @@
 @contextlib.contextmanager
 def _thread_mapper(maxsize: int) -> Generator[
     Callable[[Callable[[TArg], TRet], Iterable[TArg]], Iterable[TRet]],
-    None, None,
 ]:
     if maxsize == 1:
         yield map
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/yaml.py 
new/pre-commit-4.0.1/pre_commit/yaml.py
--- old/pre-commit-3.8.0/pre_commit/yaml.py     2024-07-28 21:58:29.000000000 
+0200
+++ new/pre-commit-4.0.1/pre_commit/yaml.py     2024-10-08 18:08:49.000000000 
+0200
@@ -6,6 +6,7 @@
 import yaml
 
 Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader)
+yaml_compose = functools.partial(yaml.compose, Loader=Loader)
 yaml_load = functools.partial(yaml.load, Loader=Loader)
 Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/pre_commit/yaml_rewrite.py 
new/pre-commit-4.0.1/pre_commit/yaml_rewrite.py
--- old/pre-commit-3.8.0/pre_commit/yaml_rewrite.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/pre-commit-4.0.1/pre_commit/yaml_rewrite.py     2024-10-08 
18:08:49.000000000 +0200
@@ -0,0 +1,52 @@
+from __future__ import annotations
+
+from collections.abc import Generator
+from collections.abc import Iterable
+from typing import NamedTuple
+from typing import Protocol
+
+from yaml.nodes import MappingNode
+from yaml.nodes import Node
+from yaml.nodes import ScalarNode
+from yaml.nodes import SequenceNode
+
+
+class _Matcher(Protocol):
+    def match(self, n: Node) -> Generator[Node]: ...
+
+
+class MappingKey(NamedTuple):
+    k: str
+
+    def match(self, n: Node) -> Generator[Node]:
+        if isinstance(n, MappingNode):
+            for k, _ in n.value:
+                if k.value == self.k:
+                    yield k
+
+
+class MappingValue(NamedTuple):
+    k: str
+
+    def match(self, n: Node) -> Generator[Node]:
+        if isinstance(n, MappingNode):
+            for k, v in n.value:
+                if k.value == self.k:
+                    yield v
+
+
+class SequenceItem(NamedTuple):
+    def match(self, n: Node) -> Generator[Node]:
+        if isinstance(n, SequenceNode):
+            yield from n.value
+
+
+def _match(gen: Iterable[Node], m: _Matcher) -> Iterable[Node]:
+    return (n for src in gen for n in m.match(src))
+
+
+def match(n: Node, matcher: tuple[_Matcher, ...]) -> Generator[ScalarNode]:
+    gen: Iterable[Node] = (n,)
+    for m in matcher:
+        gen = _match(gen, m)
+    return (n for n in gen if isinstance(n, ScalarNode))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/setup.cfg 
new/pre-commit-4.0.1/setup.cfg
--- old/pre-commit-3.8.0/setup.cfg      2024-07-28 21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/setup.cfg      2024-10-08 18:08:49.000000000 +0200
@@ -1,6 +1,6 @@
 [metadata]
 name = pre_commit
-version = 3.8.0
+version = 4.0.1
 description = A framework for managing and maintaining multi-language 
pre-commit hooks.
 long_description = file: README.md
 long_description_content_type = text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/testing/make-archives 
new/pre-commit-4.0.1/testing/make-archives
--- old/pre-commit-3.8.0/testing/make-archives  2024-07-28 21:58:29.000000000 
+0200
+++ new/pre-commit-4.0.1/testing/make-archives  2024-10-08 18:08:49.000000000 
+0200
@@ -17,7 +17,7 @@
 
 REPOS = (
     ('rbenv', 'https://github.com/rbenv/rbenv', '38e1fbb'),
-    ('ruby-build', 'https://github.com/rbenv/ruby-build', '855b963'),
+    ('ruby-build', 'https://github.com/rbenv/ruby-build', 'ed384c8'),
     (
         'ruby-download',
         'https://github.com/garnieretienne/rvm-download',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/tests/all_languages_test.py 
new/pre-commit-4.0.1/tests/all_languages_test.py
--- old/pre-commit-3.8.0/tests/all_languages_test.py    2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/tests/all_languages_test.py    1970-01-01 
01:00:00.000000000 +0100
@@ -1,7 +0,0 @@
-from __future__ import annotations
-
-from pre_commit.all_languages import languages
-
-
-def test_python_venv_is_an_alias_to_python():
-    assert languages['python_venv'] is languages['python']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/tests/clientlib_test.py 
new/pre-commit-4.0.1/tests/clientlib_test.py
--- old/pre-commit-3.8.0/tests/clientlib_test.py        2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/tests/clientlib_test.py        2024-10-08 
18:08:49.000000000 +0200
@@ -256,6 +256,24 @@
     ]
 
 
+def test_validate_optional_sensible_regex_at_meta_hook(caplog):
+    config_obj = {
+        'repo': 'meta',
+        'hooks': [{'id': 'identity', 'files': 'dir/*.py'}],
+    }
+
+    cfgv.validate(config_obj, CONFIG_REPO_DICT)
+
+    assert caplog.record_tuples == [
+        (
+            'pre_commit',
+            logging.WARNING,
+            "The 'files' field in hook 'identity' is a regex, not a glob "
+            "-- matching '/*' probably isn't what you want here",
+        ),
+    ]
+
+
 @pytest.mark.parametrize(
     ('regex', 'warning'),
     (
@@ -291,6 +309,56 @@
     assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)]
 
 
+def test_warning_for_deprecated_stages(caplog):
+    config_obj = sample_local_config()
+    config_obj['hooks'][0]['stages'] = ['commit', 'push']
+
+    cfgv.validate(config_obj, CONFIG_REPO_DICT)
+
+    assert caplog.record_tuples == [
+        (
+            'pre_commit',
+            logging.WARNING,
+            'hook id `do_not_commit` uses deprecated stage names '
+            '(commit, push) which will be removed in a future version.  '
+            'run: `pre-commit migrate-config` to automatically fix this.',
+        ),
+    ]
+
+
+def test_no_warning_for_non_deprecated_stages(caplog):
+    config_obj = sample_local_config()
+    config_obj['hooks'][0]['stages'] = ['pre-commit', 'pre-push']
+
+    cfgv.validate(config_obj, CONFIG_REPO_DICT)
+
+    assert caplog.record_tuples == []
+
+
+def test_warning_for_deprecated_default_stages(caplog):
+    cfg = {'default_stages': ['commit', 'push'], 'repos': []}
+
+    cfgv.validate(cfg, CONFIG_SCHEMA)
+
+    assert caplog.record_tuples == [
+        (
+            'pre_commit',
+            logging.WARNING,
+            'top-level `default_stages` uses deprecated stage names '
+            '(commit, push) which will be removed in a future version.  '
+            'run: `pre-commit migrate-config` to automatically fix this.',
+        ),
+    ]
+
+
+def test_no_warning_for_non_deprecated_default_stages(caplog):
+    cfg = {'default_stages': ['pre-commit', 'pre-push'], 'repos': []}
+
+    cfgv.validate(cfg, CONFIG_SCHEMA)
+
+    assert caplog.record_tuples == []
+
+
 @pytest.mark.parametrize(
     'manifest_obj',
     (
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pre-commit-3.8.0/tests/commands/migrate_config_test.py 
new/pre-commit-4.0.1/tests/commands/migrate_config_test.py
--- old/pre-commit-3.8.0/tests/commands/migrate_config_test.py  2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/tests/commands/migrate_config_test.py  2024-10-08 
18:08:49.000000000 +0200
@@ -1,10 +1,26 @@
 from __future__ import annotations
 
+from unittest import mock
+
 import pytest
+import yaml
 
 import pre_commit.constants as C
 from pre_commit.clientlib import InvalidConfigError
 from pre_commit.commands.migrate_config import migrate_config
+from pre_commit.yaml import yaml_compose
+
+
+@pytest.fixture(autouse=True, params=['c', 'pure'])
+def switch_pyyaml_impl(request):
+    if request.param == 'c':
+        yield
+    else:
+        with mock.patch.dict(
+                yaml_compose.keywords,
+                {'Loader': yaml.SafeLoader},
+        ):
+            yield
 
 
 def test_migrate_config_normal_format(tmpdir, capsys):
@@ -134,6 +150,27 @@
     )
 
 
+def test_migrate_config_sha_to_rev_json(tmp_path):
+    contents = """\
+{"repos": [{
+    "repo": "https://github.com/pre-commit/pre-commit-hooks";,
+    "sha": "v1.2.0",
+    "hooks": []
+}]}
+"""
+    expected = """\
+{"repos": [{
+    "repo": "https://github.com/pre-commit/pre-commit-hooks";,
+    "rev": "v1.2.0",
+    "hooks": []
+}]}
+"""
+    cfg = tmp_path.joinpath('cfg.yaml')
+    cfg.write_text(contents)
+    assert not migrate_config(str(cfg))
+    assert cfg.read_text() == expected
+
+
 def test_migrate_config_language_python_venv(tmp_path):
     src = '''\
 repos:
@@ -163,6 +200,73 @@
 '''
     cfg = tmp_path.joinpath('cfg.yaml')
     cfg.write_text(src)
+    assert migrate_config(str(cfg)) == 0
+    assert cfg.read_text() == expected
+
+
+def test_migrate_config_quoted_python_venv(tmp_path):
+    src = '''\
+repos:
+-   repo: local
+    hooks:
+    -   id: example
+        name: example
+        entry: example
+        language: "python_venv"
+'''
+    expected = '''\
+repos:
+-   repo: local
+    hooks:
+    -   id: example
+        name: example
+        entry: example
+        language: "python"
+'''
+    cfg = tmp_path.joinpath('cfg.yaml')
+    cfg.write_text(src)
+    assert migrate_config(str(cfg)) == 0
+    assert cfg.read_text() == expected
+
+
+def test_migrate_config_default_stages(tmp_path):
+    src = '''\
+default_stages: [commit, push, merge-commit, commit-msg]
+repos: []
+'''
+    expected = '''\
+default_stages: [pre-commit, pre-push, pre-merge-commit, commit-msg]
+repos: []
+'''
+    cfg = tmp_path.joinpath('cfg.yaml')
+    cfg.write_text(src)
+    assert migrate_config(str(cfg)) == 0
+    assert cfg.read_text() == expected
+
+
+def test_migrate_config_hook_stages(tmp_path):
+    src = '''\
+repos:
+-   repo: local
+    hooks:
+    -   id: example
+        name: example
+        entry: example
+        language: system
+        stages: ["commit", "push", "merge-commit", "commit-msg"]
+'''
+    expected = '''\
+repos:
+-   repo: local
+    hooks:
+    -   id: example
+        name: example
+        entry: example
+        language: system
+        stages: ["pre-commit", "pre-push", "pre-merge-commit", "commit-msg"]
+'''
+    cfg = tmp_path.joinpath('cfg.yaml')
+    cfg.write_text(src)
     assert migrate_config(str(cfg)) == 0
     assert cfg.read_text() == expected
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/tests/conftest.py 
new/pre-commit-4.0.1/tests/conftest.py
--- old/pre-commit-3.8.0/tests/conftest.py      2024-07-28 21:58:29.000000000 
+0200
+++ new/pre-commit-4.0.1/tests/conftest.py      2024-10-08 18:08:49.000000000 
+0200
@@ -2,7 +2,6 @@
 
 import functools
 import io
-import logging
 import os.path
 from unittest import mock
 
@@ -203,12 +202,6 @@
     yield Store(os.path.join(tempdir_factory.get(), '.pre-commit'))
 
 
-@pytest.fixture
-def log_info_mock():
-    with mock.patch.object(logging.getLogger('pre_commit'), 'info') as mck:
-        yield mck
-
-
 class Fixture:
     def __init__(self, stream: io.BytesIO) -> None:
         self._stream = stream
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/tests/repository_test.py 
new/pre-commit-4.0.1/tests/repository_test.py
--- old/pre-commit-3.8.0/tests/repository_test.py       2024-07-28 
21:58:29.000000000 +0200
+++ new/pre-commit-4.0.1/tests/repository_test.py       2024-10-08 
18:08:49.000000000 +0200
@@ -80,24 +80,6 @@
     assert out == expected
 
 
-def test_python_venv_deprecation(store, caplog):
-    config = {
-        'repo': 'local',
-        'hooks': [{
-            'id': 'example',
-            'name': 'example',
-            'language': 'python_venv',
-            'entry': 'echo hi',
-        }],
-    }
-    _get_hook(config, store, 'example')
-    assert caplog.messages[-1] == (
-        '`repo: local` uses deprecated `language: python_venv`.  '
-        'This is an alias for `language: python`.  '
-        'Often `pre-commit autoupdate --repo local` will fix this.'
-    )
-
-
 def test_system_hook_with_spaces(tempdir_factory, store):
     _test_hook_repo(
         tempdir_factory, store, 'system_hook_with_spaces_repo',
@@ -240,16 +222,16 @@
     assert msg == 'Unexpected key(s) present on local => too-much: foo, hello'
 
 
-def test_reinstall(tempdir_factory, store, log_info_mock):
+def test_reinstall(tempdir_factory, store, caplog):
     path = make_repo(tempdir_factory, 'python_hooks_repo')
     config = make_config_from_repo(path)
     _get_hook(config, store, 'foo')
     # We print some logging during clone (1) + install (3)
-    assert log_info_mock.call_count == 4
-    log_info_mock.reset_mock()
+    assert len(caplog.record_tuples) == 4
+    caplog.clear()
     # Reinstall on another run should not trigger another install
     _get_hook(config, store, 'foo')
-    assert log_info_mock.call_count == 0
+    assert len(caplog.record_tuples) == 0
 
 
 def test_control_c_control_c_on_install(tempdir_factory, store):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/tests/store_test.py 
new/pre-commit-4.0.1/tests/store_test.py
--- old/pre-commit-3.8.0/tests/store_test.py    2024-07-28 21:58:29.000000000 
+0200
+++ new/pre-commit-4.0.1/tests/store_test.py    2024-10-08 18:08:49.000000000 
+0200
@@ -1,12 +1,15 @@
 from __future__ import annotations
 
+import logging
 import os.path
+import shlex
 import sqlite3
 import stat
 from unittest import mock
 
 import pytest
 
+import pre_commit.constants as C
 from pre_commit import git
 from pre_commit.store import _get_default_directory
 from pre_commit.store import _LOCAL_RESOURCES
@@ -65,7 +68,7 @@
             assert text_line in readme_contents
 
 
-def test_clone(store, tempdir_factory, log_info_mock):
+def test_clone(store, tempdir_factory, caplog):
     path = git_dir(tempdir_factory)
     with cwd(path):
         git_commit()
@@ -74,7 +77,7 @@
 
     ret = store.clone(path, rev)
     # Should have printed some stuff
-    assert log_info_mock.call_args_list[0][0][0].startswith(
+    assert caplog.record_tuples[0][-1].startswith(
         'Initializing environment for ',
     )
 
@@ -91,6 +94,72 @@
     assert store.select_all_repos() == [(path, rev, ret)]
 
 
+def test_warning_for_deprecated_stages_on_init(store, tempdir_factory, caplog):
+    manifest = '''\
+-   id: hook1
+    name: hook1
+    language: system
+    entry: echo hook1
+    stages: [commit, push]
+-   id: hook2
+    name: hook2
+    language: system
+    entry: echo hook2
+    stages: [push, merge-commit]
+'''
+
+    path = git_dir(tempdir_factory)
+    with open(os.path.join(path, C.MANIFEST_FILE), 'w') as f:
+        f.write(manifest)
+    cmd_output('git', 'add', '.', cwd=path)
+    git_commit(cwd=path)
+    rev = git.head_rev(path)
+
+    store.clone(path, rev)
+    assert caplog.record_tuples[1] == (
+        'pre_commit',
+        logging.WARNING,
+        f'repo `{path}` uses deprecated stage names '
+        f'(commit, push, merge-commit) which will be removed in a future '
+        f'version.  '
+        f'Hint: often `pre-commit autoupdate --repo {shlex.quote(path)}` '
+        f'will fix this.  '
+        f'if it does not -- consider reporting an issue to that repo.',
+    )
+
+    # should not re-warn
+    caplog.clear()
+    store.clone(path, rev)
+    assert caplog.record_tuples == []
+
+
+def test_no_warning_for_non_deprecated_stages_on_init(
+        store, tempdir_factory, caplog,
+):
+    manifest = '''\
+-   id: hook1
+    name: hook1
+    language: system
+    entry: echo hook1
+    stages: [pre-commit, pre-push]
+-   id: hook2
+    name: hook2
+    language: system
+    entry: echo hook2
+    stages: [pre-push, pre-merge-commit]
+'''
+
+    path = git_dir(tempdir_factory)
+    with open(os.path.join(path, C.MANIFEST_FILE), 'w') as f:
+        f.write(manifest)
+    cmd_output('git', 'add', '.', cwd=path)
+    git_commit(cwd=path)
+    rev = git.head_rev(path)
+
+    store.clone(path, rev)
+    assert logging.WARNING not in {tup[1] for tup in caplog.record_tuples}
+
+
 def test_clone_cleans_up_on_checkout_failure(store):
     with pytest.raises(Exception) as excinfo:
         # This raises an exception because you can't clone something that
@@ -118,7 +187,7 @@
 
 def test_clone_shallow_failure_fallback_to_complete(
     store, tempdir_factory,
-    log_info_mock,
+    caplog,
 ):
     path = git_dir(tempdir_factory)
     with cwd(path):
@@ -134,7 +203,7 @@
     ret = store.clone(path, rev)
 
     # Should have printed some stuff
-    assert log_info_mock.call_args_list[0][0][0].startswith(
+    assert caplog.record_tuples[0][-1].startswith(
         'Initializing environment for ',
     )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pre-commit-3.8.0/tests/yaml_rewrite_test.py 
new/pre-commit-4.0.1/tests/yaml_rewrite_test.py
--- old/pre-commit-3.8.0/tests/yaml_rewrite_test.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/pre-commit-4.0.1/tests/yaml_rewrite_test.py     2024-10-08 
18:08:49.000000000 +0200
@@ -0,0 +1,47 @@
+from __future__ import annotations
+
+import pytest
+
+from pre_commit.yaml import yaml_compose
+from pre_commit.yaml_rewrite import MappingKey
+from pre_commit.yaml_rewrite import MappingValue
+from pre_commit.yaml_rewrite import match
+from pre_commit.yaml_rewrite import SequenceItem
+
+
+def test_match_produces_scalar_values_only():
+    src = '''\
+-   name: foo
+-   name: [not, foo]  # not a scalar: should be skipped!
+-   name: bar
+'''
+    matcher = (SequenceItem(), MappingValue('name'))
+    ret = [n.value for n in match(yaml_compose(src), matcher)]
+    assert ret == ['foo', 'bar']
+
+
+@pytest.mark.parametrize('cls', (MappingKey, MappingValue))
+def test_mapping_not_a_map(cls):
+    m = cls('s')
+    assert list(m.match(yaml_compose('[foo]'))) == []
+
+
+def test_sequence_item_not_a_sequence():
+    assert list(SequenceItem().match(yaml_compose('s: val'))) == []
+
+
+def test_mapping_key():
+    m = MappingKey('s')
+    ret = [n.value for n in m.match(yaml_compose('s: val\nt: val2'))]
+    assert ret == ['s']
+
+
+def test_mapping_value():
+    m = MappingValue('s')
+    ret = [n.value for n in m.match(yaml_compose('s: val\nt: val2'))]
+    assert ret == ['val']
+
+
+def test_sequence_item():
+    ret = [n.value for n in SequenceItem().match(yaml_compose('[a, b, c]'))]
+    assert ret == ['a', 'b', 'c']

Reply via email to