We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
pre-push hook failed with "ValueError: too many values to unpack (expected 4)"
### describe your issue

I ran

```
git push --dry-run origin HEAD^"{/^[a-zA-Z]+: }":refs/for/main%wip
```

and expected the hook to run properly, but it failed with a somewhat subtle error:

```
An unexpected error has occurred: ValueError: too many values to unpack (expected 4)
Check the log at $HOME/.cache/pre-commit/pre-commit.log
```

It was more clear from the `pre-commit.log` file, though (see below). I reproduced the issue using HEAD (f9473e756decd141a9834994840c1cb124564c2a) as well.

### pre-commit --version

2.12.1

### .pre-commit-config.yaml

```yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.2.0
    hooks:
      - id: check-added-large-files
      - id: check-json
      - id: check-yaml
      - id: end-of-file-fixer
      - id: trailing-whitespace

  - repo: https://github.com/wwade/pre-commit-golang
    rev: 503834f5c0933fbdf9a55e92329c1957e48f6d0a
    hooks:
      - id: go-fmt
      - id: go-imports
      - id: go-cyclo
        args: [-over=15]
      - id: validate-toml
      - id: golangci-lint
      - id: go-unit-tests
      - id: go-mod-tidy
```


### ~/.cache/pre-commit/pre-commit.log (if present)

### version information

```
pre-commit version: 2.12.1
sys.version:
    3.8.10 (default, Mar 15 2022, 12:22:08) 
    [GCC 9.4.0]
sys.executable: /usr/bin/python3
os.name: posix
sys.platform: linux
```

### error information

```
An unexpected error has occurred: ValueError: too many values to unpack (expected 4)
```

```
Traceback (most recent call last):
  File "$HOME/.local/lib/python3.8/site-packages/pre_commit/error_handler.py", line 65, in error_handler
    yield
  File "$HOME/.local/lib/python3.8/site-packages/pre_commit/main.py", line 357, in main
    return hook_impl(
  File "$HOME/.local/lib/python3.8/site-packages/pre_commit/commands/hook_impl.py", line 223, in hook_impl
    ns = _run_ns(hook_type, color, args, stdin)
  File "$HOME/.local/lib/python3.8/site-packages/pre_commit/commands/hook_impl.py", line 195, in _run_ns
    return _pre_push_ns(color, args, stdin)
  File "$HOME/.local/lib/python3.8/site-packages/pre_commit/commands/hook_impl.py", line 113, in _pre_push_ns
    _, local_sha, remote_branch, remote_sha = line.split()
ValueError: too many values to unpack (expected 4)
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pre_commit/commands/hook_impl.py`
Content:
```
1 from __future__ import annotations
2 
3 import argparse
4 import os.path
5 import subprocess
6 import sys
7 from typing import Sequence
8 
9 from pre_commit.commands.run import run
10 from pre_commit.envcontext import envcontext
11 from pre_commit.parse_shebang import normalize_cmd
12 from pre_commit.store import Store
13 
14 Z40 = '0' * 40
15 
16 
17 def _run_legacy(
18         hook_type: str,
19         hook_dir: str,
20         args: Sequence[str],
21 ) -> tuple[int, bytes]:
22     if os.environ.get('PRE_COMMIT_RUNNING_LEGACY'):
23         raise SystemExit(
24             f"bug: pre-commit's script is installed in migration mode\n"
25             f'run `pre-commit install -f --hook-type {hook_type}` to fix '
26             f'this\n\n'
27             f'Please report this bug at '
28             f'https://github.com/pre-commit/pre-commit/issues',
29         )
30 
31     if hook_type == 'pre-push':
32         stdin = sys.stdin.buffer.read()
33     else:
34         stdin = b''
35 
36     # not running in legacy mode
37     legacy_hook = os.path.join(hook_dir, f'{hook_type}.legacy')
38     if not os.access(legacy_hook, os.X_OK):
39         return 0, stdin
40 
41     with envcontext((('PRE_COMMIT_RUNNING_LEGACY', '1'),)):
42         cmd = normalize_cmd((legacy_hook, *args))
43         return subprocess.run(cmd, input=stdin).returncode, stdin
44 
45 
46 def _validate_config(
47         retv: int,
48         config: str,
49         skip_on_missing_config: bool,
50 ) -> None:
51     if not os.path.isfile(config):
52         if skip_on_missing_config or os.getenv('PRE_COMMIT_ALLOW_NO_CONFIG'):
53             print(f'`{config}` config file not found. Skipping `pre-commit`.')
54             raise SystemExit(retv)
55         else:
56             print(
57                 f'No {config} file was found\n'
58                 f'- To temporarily silence this, run '
59                 f'`PRE_COMMIT_ALLOW_NO_CONFIG=1 git ...`\n'
60                 f'- To permanently silence this, install pre-commit with the '
61                 f'--allow-missing-config option\n'
62                 f'- To uninstall pre-commit run `pre-commit uninstall`',
63             )
64             raise SystemExit(1)
65 
66 
67 def _ns(
68         hook_type: str,
69         color: bool,
70         *,
71         all_files: bool = False,
72         remote_branch: str | None = None,
73         local_branch: str | None = None,
74         from_ref: str | None = None,
75         to_ref: str | None = None,
76         remote_name: str | None = None,
77         remote_url: str | None = None,
78         commit_msg_filename: str | None = None,
79         checkout_type: str | None = None,
80         is_squash_merge: str | None = None,
81         rewrite_command: str | None = None,
82 ) -> argparse.Namespace:
83     return argparse.Namespace(
84         color=color,
85         hook_stage=hook_type.replace('pre-', ''),
86         remote_branch=remote_branch,
87         local_branch=local_branch,
88         from_ref=from_ref,
89         to_ref=to_ref,
90         remote_name=remote_name,
91         remote_url=remote_url,
92         commit_msg_filename=commit_msg_filename,
93         all_files=all_files,
94         checkout_type=checkout_type,
95         is_squash_merge=is_squash_merge,
96         rewrite_command=rewrite_command,
97         files=(),
98         hook=None,
99         verbose=False,
100         show_diff_on_failure=False,
101     )
102 
103 
104 def _rev_exists(rev: str) -> bool:
105     return not subprocess.call(('git', 'rev-list', '--quiet', rev))
106 
107 
108 def _pre_push_ns(
109         color: bool,
110         args: Sequence[str],
111         stdin: bytes,
112 ) -> argparse.Namespace | None:
113     remote_name = args[0]
114     remote_url = args[1]
115 
116     for line in stdin.decode().splitlines():
117         local_branch, local_sha, remote_branch, remote_sha = line.split()
118         if local_sha == Z40:
119             continue
120         elif remote_sha != Z40 and _rev_exists(remote_sha):
121             return _ns(
122                 'pre-push', color,
123                 from_ref=remote_sha, to_ref=local_sha,
124                 remote_branch=remote_branch,
125                 local_branch=local_branch,
126                 remote_name=remote_name, remote_url=remote_url,
127             )
128         else:
129             # ancestors not found in remote
130             ancestors = subprocess.check_output((
131                 'git', 'rev-list', local_sha, '--topo-order', '--reverse',
132                 '--not', f'--remotes={remote_name}',
133             )).decode().strip()
134             if not ancestors:
135                 continue
136             else:
137                 first_ancestor = ancestors.splitlines()[0]
138                 cmd = ('git', 'rev-list', '--max-parents=0', local_sha)
139                 roots = set(subprocess.check_output(cmd).decode().splitlines())
140                 if first_ancestor in roots:
141                     # pushing the whole tree including root commit
142                     return _ns(
143                         'pre-push', color,
144                         all_files=True,
145                         remote_name=remote_name, remote_url=remote_url,
146                         remote_branch=remote_branch,
147                         local_branch=local_branch,
148                     )
149                 else:
150                     rev_cmd = ('git', 'rev-parse', f'{first_ancestor}^')
151                     source = subprocess.check_output(rev_cmd).decode().strip()
152                     return _ns(
153                         'pre-push', color,
154                         from_ref=source, to_ref=local_sha,
155                         remote_name=remote_name, remote_url=remote_url,
156                         remote_branch=remote_branch,
157                         local_branch=local_branch,
158                     )
159 
160     # nothing to push
161     return None
162 
163 
164 _EXPECTED_ARG_LENGTH_BY_HOOK = {
165     'commit-msg': 1,
166     'post-checkout': 3,
167     'post-commit': 0,
168     'pre-commit': 0,
169     'pre-merge-commit': 0,
170     'post-merge': 1,
171     'post-rewrite': 1,
172     'pre-push': 2,
173 }
174 
175 
176 def _check_args_length(hook_type: str, args: Sequence[str]) -> None:
177     if hook_type == 'prepare-commit-msg':
178         if len(args) < 1 or len(args) > 3:
179             raise SystemExit(
180                 f'hook-impl for {hook_type} expected 1, 2, or 3 arguments '
181                 f'but got {len(args)}: {args}',
182             )
183     elif hook_type in _EXPECTED_ARG_LENGTH_BY_HOOK:
184         expected = _EXPECTED_ARG_LENGTH_BY_HOOK[hook_type]
185         if len(args) != expected:
186             arguments_s = 'argument' if expected == 1 else 'arguments'
187             raise SystemExit(
188                 f'hook-impl for {hook_type} expected {expected} {arguments_s} '
189                 f'but got {len(args)}: {args}',
190             )
191     else:
192         raise AssertionError(f'unexpected hook type: {hook_type}')
193 
194 
195 def _run_ns(
196         hook_type: str,
197         color: bool,
198         args: Sequence[str],
199         stdin: bytes,
200 ) -> argparse.Namespace | None:
201     _check_args_length(hook_type, args)
202     if hook_type == 'pre-push':
203         return _pre_push_ns(color, args, stdin)
204     elif hook_type in {'commit-msg', 'prepare-commit-msg'}:
205         return _ns(hook_type, color, commit_msg_filename=args[0])
206     elif hook_type in {'post-commit', 'pre-merge-commit', 'pre-commit'}:
207         return _ns(hook_type, color)
208     elif hook_type == 'post-checkout':
209         return _ns(
210             hook_type, color,
211             from_ref=args[0], to_ref=args[1], checkout_type=args[2],
212         )
213     elif hook_type == 'post-merge':
214         return _ns(hook_type, color, is_squash_merge=args[0])
215     elif hook_type == 'post-rewrite':
216         return _ns(hook_type, color, rewrite_command=args[0])
217     else:
218         raise AssertionError(f'unexpected hook type: {hook_type}')
219 
220 
221 def hook_impl(
222         store: Store,
223         *,
224         config: str,
225         color: bool,
226         hook_type: str,
227         hook_dir: str,
228         skip_on_missing_config: bool,
229         args: Sequence[str],
230 ) -> int:
231     retv, stdin = _run_legacy(hook_type, hook_dir, args)
232     _validate_config(retv, config, skip_on_missing_config)
233     ns = _run_ns(hook_type, color, args, stdin)
234     if ns is None:
235         return retv
236     else:
237         return retv | run(config, store, ns)
238
```
--- 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/hook_impl.py b/pre_commit/commands/hook_impl.py
--- a/pre_commit/commands/hook_impl.py
+++ b/pre_commit/commands/hook_impl.py
@@ -114,7 +114,8 @@
     remote_url = args[1]
 
     for line in stdin.decode().splitlines():
-        local_branch, local_sha, remote_branch, remote_sha = line.split()
+        parts = line.rsplit(maxsplit=3)
+        local_branch, local_sha, remote_branch, remote_sha = parts
         if local_sha == Z40:
             continue
         elif remote_sha != Z40 and _rev_exists(remote_sha):

{"golden_diff": "diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py\n--- a/pre_commit/commands/hook_impl.py\n+++ b/pre_commit/commands/hook_impl.py\n@@ -114,7 +114,8 @@\n     remote_url = args[1]\n \n     for line in stdin.decode().splitlines():\n-        local_branch, local_sha, remote_branch, remote_sha = line.split()\n+        parts = line.rsplit(maxsplit=3)\n+        local_branch, local_sha, remote_branch, remote_sha = parts\n         if local_sha == Z40:\n             continue\n         elif remote_sha != Z40 and _rev_exists(remote_sha):\n", "issue": "pre-push hook failed with \"ValueError: too many values to unpack (expected 4)\"\n### describe your issue\n\nI ran\r\n\r\n```\r\ngit push --dry-run origin HEAD^\"{/^[a-zA-Z]+: }\":refs/for/main%wip\r\n```\r\n\r\nand expected the hook to run properly, but it failed with a somewhat subtle error:\r\n\r\n```\r\nAn unexpected error has occurred: ValueError: too many values to unpack (expected 4)\r\nCheck the log at $HOME/.cache/pre-commit/pre-commit.log\r\n```\r\n\r\nIt was more clear from the `pre-commit.log` file, though (see below). I reproduced the issue using HEAD (f9473e756decd141a9834994840c1cb124564c2a) as well.\n\n### pre-commit --version\n\n2.12.1\n\n### .pre-commit-config.yaml\n\n```yaml\nrepos:\r\n  - repo: https://github.com/pre-commit/pre-commit-hooks\r\n    rev: v3.2.0\r\n    hooks:\r\n      - id: check-added-large-files\r\n      - id: check-json\r\n      - id: check-yaml\r\n      - id: end-of-file-fixer\r\n      - id: trailing-whitespace\r\n\r\n  - repo: https://github.com/wwade/pre-commit-golang\r\n    rev: 503834f5c0933fbdf9a55e92329c1957e48f6d0a\r\n    hooks:\r\n      - id: go-fmt\r\n      - id: go-imports\r\n      - id: go-cyclo\r\n        args: [-over=15]\r\n      - id: validate-toml\r\n      - id: golangci-lint\r\n      - id: go-unit-tests\r\n      - id: go-mod-tidy\n```\n\n\n### ~/.cache/pre-commit/pre-commit.log (if present)\n\n### version information\r\n\r\n```\r\npre-commit version: 2.12.1\r\nsys.version:\r\n    3.8.10 (default, Mar 15 2022, 12:22:08) \r\n    [GCC 9.4.0]\r\nsys.executable: /usr/bin/python3\r\nos.name: posix\r\nsys.platform: linux\r\n```\r\n\r\n### error information\r\n\r\n```\r\nAn unexpected error has occurred: ValueError: too many values to unpack (expected 4)\r\n```\r\n\r\n```\r\nTraceback (most recent call last):\r\n  File \"$HOME/.local/lib/python3.8/site-packages/pre_commit/error_handler.py\", line 65, in error_handler\r\n    yield\r\n  File \"$HOME/.local/lib/python3.8/site-packages/pre_commit/main.py\", line 357, in main\r\n    return hook_impl(\r\n  File \"$HOME/.local/lib/python3.8/site-packages/pre_commit/commands/hook_impl.py\", line 223, in hook_impl\r\n    ns = _run_ns(hook_type, color, args, stdin)\r\n  File \"$HOME/.local/lib/python3.8/site-packages/pre_commit/commands/hook_impl.py\", line 195, in _run_ns\r\n    return _pre_push_ns(color, args, stdin)\r\n  File \"$HOME/.local/lib/python3.8/site-packages/pre_commit/commands/hook_impl.py\", line 113, in _pre_push_ns\r\n    _, local_sha, remote_branch, remote_sha = line.split()\r\nValueError: too many values to unpack (expected 4)\r\n```\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport argparse\nimport os.path\nimport subprocess\nimport sys\nfrom typing import Sequence\n\nfrom pre_commit.commands.run import run\nfrom pre_commit.envcontext import envcontext\nfrom pre_commit.parse_shebang import normalize_cmd\nfrom pre_commit.store import Store\n\nZ40 = '0' * 40\n\n\ndef _run_legacy(\n        hook_type: str,\n        hook_dir: str,\n        args: Sequence[str],\n) -> tuple[int, bytes]:\n    if os.environ.get('PRE_COMMIT_RUNNING_LEGACY'):\n        raise SystemExit(\n            f\"bug: pre-commit's script is installed in migration mode\\n\"\n            f'run `pre-commit install -f --hook-type {hook_type}` to fix '\n            f'this\\n\\n'\n            f'Please report this bug at '\n            f'https://github.com/pre-commit/pre-commit/issues',\n        )\n\n    if hook_type == 'pre-push':\n        stdin = sys.stdin.buffer.read()\n    else:\n        stdin = b''\n\n    # not running in legacy mode\n    legacy_hook = os.path.join(hook_dir, f'{hook_type}.legacy')\n    if not os.access(legacy_hook, os.X_OK):\n        return 0, stdin\n\n    with envcontext((('PRE_COMMIT_RUNNING_LEGACY', '1'),)):\n        cmd = normalize_cmd((legacy_hook, *args))\n        return subprocess.run(cmd, input=stdin).returncode, stdin\n\n\ndef _validate_config(\n        retv: int,\n        config: str,\n        skip_on_missing_config: bool,\n) -> None:\n    if not os.path.isfile(config):\n        if skip_on_missing_config or os.getenv('PRE_COMMIT_ALLOW_NO_CONFIG'):\n            print(f'`{config}` config file not found. Skipping `pre-commit`.')\n            raise SystemExit(retv)\n        else:\n            print(\n                f'No {config} file was found\\n'\n                f'- To temporarily silence this, run '\n                f'`PRE_COMMIT_ALLOW_NO_CONFIG=1 git ...`\\n'\n                f'- To permanently silence this, install pre-commit with the '\n                f'--allow-missing-config option\\n'\n                f'- To uninstall pre-commit run `pre-commit uninstall`',\n            )\n            raise SystemExit(1)\n\n\ndef _ns(\n        hook_type: str,\n        color: bool,\n        *,\n        all_files: bool = False,\n        remote_branch: str | None = None,\n        local_branch: str | None = None,\n        from_ref: str | None = None,\n        to_ref: str | None = None,\n        remote_name: str | None = None,\n        remote_url: str | None = None,\n        commit_msg_filename: str | None = None,\n        checkout_type: str | None = None,\n        is_squash_merge: str | None = None,\n        rewrite_command: str | None = None,\n) -> argparse.Namespace:\n    return argparse.Namespace(\n        color=color,\n        hook_stage=hook_type.replace('pre-', ''),\n        remote_branch=remote_branch,\n        local_branch=local_branch,\n        from_ref=from_ref,\n        to_ref=to_ref,\n        remote_name=remote_name,\n        remote_url=remote_url,\n        commit_msg_filename=commit_msg_filename,\n        all_files=all_files,\n        checkout_type=checkout_type,\n        is_squash_merge=is_squash_merge,\n        rewrite_command=rewrite_command,\n        files=(),\n        hook=None,\n        verbose=False,\n        show_diff_on_failure=False,\n    )\n\n\ndef _rev_exists(rev: str) -> bool:\n    return not subprocess.call(('git', 'rev-list', '--quiet', rev))\n\n\ndef _pre_push_ns(\n        color: bool,\n        args: Sequence[str],\n        stdin: bytes,\n) -> argparse.Namespace | None:\n    remote_name = args[0]\n    remote_url = args[1]\n\n    for line in stdin.decode().splitlines():\n        local_branch, local_sha, remote_branch, remote_sha = line.split()\n        if local_sha == Z40:\n            continue\n        elif remote_sha != Z40 and _rev_exists(remote_sha):\n            return _ns(\n                'pre-push', color,\n                from_ref=remote_sha, to_ref=local_sha,\n                remote_branch=remote_branch,\n                local_branch=local_branch,\n                remote_name=remote_name, remote_url=remote_url,\n            )\n        else:\n            # ancestors not found in remote\n            ancestors = subprocess.check_output((\n                'git', 'rev-list', local_sha, '--topo-order', '--reverse',\n                '--not', f'--remotes={remote_name}',\n            )).decode().strip()\n            if not ancestors:\n                continue\n            else:\n                first_ancestor = ancestors.splitlines()[0]\n                cmd = ('git', 'rev-list', '--max-parents=0', local_sha)\n                roots = set(subprocess.check_output(cmd).decode().splitlines())\n                if first_ancestor in roots:\n                    # pushing the whole tree including root commit\n                    return _ns(\n                        'pre-push', color,\n                        all_files=True,\n                        remote_name=remote_name, remote_url=remote_url,\n                        remote_branch=remote_branch,\n                        local_branch=local_branch,\n                    )\n                else:\n                    rev_cmd = ('git', 'rev-parse', f'{first_ancestor}^')\n                    source = subprocess.check_output(rev_cmd).decode().strip()\n                    return _ns(\n                        'pre-push', color,\n                        from_ref=source, to_ref=local_sha,\n                        remote_name=remote_name, remote_url=remote_url,\n                        remote_branch=remote_branch,\n                        local_branch=local_branch,\n                    )\n\n    # nothing to push\n    return None\n\n\n_EXPECTED_ARG_LENGTH_BY_HOOK = {\n    'commit-msg': 1,\n    'post-checkout': 3,\n    'post-commit': 0,\n    'pre-commit': 0,\n    'pre-merge-commit': 0,\n    'post-merge': 1,\n    'post-rewrite': 1,\n    'pre-push': 2,\n}\n\n\ndef _check_args_length(hook_type: str, args: Sequence[str]) -> None:\n    if hook_type == 'prepare-commit-msg':\n        if len(args) < 1 or len(args) > 3:\n            raise SystemExit(\n                f'hook-impl for {hook_type} expected 1, 2, or 3 arguments '\n                f'but got {len(args)}: {args}',\n            )\n    elif hook_type in _EXPECTED_ARG_LENGTH_BY_HOOK:\n        expected = _EXPECTED_ARG_LENGTH_BY_HOOK[hook_type]\n        if len(args) != expected:\n            arguments_s = 'argument' if expected == 1 else 'arguments'\n            raise SystemExit(\n                f'hook-impl for {hook_type} expected {expected} {arguments_s} '\n                f'but got {len(args)}: {args}',\n            )\n    else:\n        raise AssertionError(f'unexpected hook type: {hook_type}')\n\n\ndef _run_ns(\n        hook_type: str,\n        color: bool,\n        args: Sequence[str],\n        stdin: bytes,\n) -> argparse.Namespace | None:\n    _check_args_length(hook_type, args)\n    if hook_type == 'pre-push':\n        return _pre_push_ns(color, args, stdin)\n    elif hook_type in {'commit-msg', 'prepare-commit-msg'}:\n        return _ns(hook_type, color, commit_msg_filename=args[0])\n    elif hook_type in {'post-commit', 'pre-merge-commit', 'pre-commit'}:\n        return _ns(hook_type, color)\n    elif hook_type == 'post-checkout':\n        return _ns(\n            hook_type, color,\n            from_ref=args[0], to_ref=args[1], checkout_type=args[2],\n        )\n    elif hook_type == 'post-merge':\n        return _ns(hook_type, color, is_squash_merge=args[0])\n    elif hook_type == 'post-rewrite':\n        return _ns(hook_type, color, rewrite_command=args[0])\n    else:\n        raise AssertionError(f'unexpected hook type: {hook_type}')\n\n\ndef hook_impl(\n        store: Store,\n        *,\n        config: str,\n        color: bool,\n        hook_type: str,\n        hook_dir: str,\n        skip_on_missing_config: bool,\n        args: Sequence[str],\n) -> int:\n    retv, stdin = _run_legacy(hook_type, hook_dir, args)\n    _validate_config(retv, config, skip_on_missing_config)\n    ns = _run_ns(hook_type, color, args, stdin)\n    if ns is None:\n        return retv\n    else:\n        return retv | run(config, store, ns)\n", "path": "pre_commit/commands/hook_impl.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport argparse\nimport os.path\nimport subprocess\nimport sys\nfrom typing import Sequence\n\nfrom pre_commit.commands.run import run\nfrom pre_commit.envcontext import envcontext\nfrom pre_commit.parse_shebang import normalize_cmd\nfrom pre_commit.store import Store\n\nZ40 = '0' * 40\n\n\ndef _run_legacy(\n        hook_type: str,\n        hook_dir: str,\n        args: Sequence[str],\n) -> tuple[int, bytes]:\n    if os.environ.get('PRE_COMMIT_RUNNING_LEGACY'):\n        raise SystemExit(\n            f\"bug: pre-commit's script is installed in migration mode\\n\"\n            f'run `pre-commit install -f --hook-type {hook_type}` to fix '\n            f'this\\n\\n'\n            f'Please report this bug at '\n            f'https://github.com/pre-commit/pre-commit/issues',\n        )\n\n    if hook_type == 'pre-push':\n        stdin = sys.stdin.buffer.read()\n    else:\n        stdin = b''\n\n    # not running in legacy mode\n    legacy_hook = os.path.join(hook_dir, f'{hook_type}.legacy')\n    if not os.access(legacy_hook, os.X_OK):\n        return 0, stdin\n\n    with envcontext((('PRE_COMMIT_RUNNING_LEGACY', '1'),)):\n        cmd = normalize_cmd((legacy_hook, *args))\n        return subprocess.run(cmd, input=stdin).returncode, stdin\n\n\ndef _validate_config(\n        retv: int,\n        config: str,\n        skip_on_missing_config: bool,\n) -> None:\n    if not os.path.isfile(config):\n        if skip_on_missing_config or os.getenv('PRE_COMMIT_ALLOW_NO_CONFIG'):\n            print(f'`{config}` config file not found. Skipping `pre-commit`.')\n            raise SystemExit(retv)\n        else:\n            print(\n                f'No {config} file was found\\n'\n                f'- To temporarily silence this, run '\n                f'`PRE_COMMIT_ALLOW_NO_CONFIG=1 git ...`\\n'\n                f'- To permanently silence this, install pre-commit with the '\n                f'--allow-missing-config option\\n'\n                f'- To uninstall pre-commit run `pre-commit uninstall`',\n            )\n            raise SystemExit(1)\n\n\ndef _ns(\n        hook_type: str,\n        color: bool,\n        *,\n        all_files: bool = False,\n        remote_branch: str | None = None,\n        local_branch: str | None = None,\n        from_ref: str | None = None,\n        to_ref: str | None = None,\n        remote_name: str | None = None,\n        remote_url: str | None = None,\n        commit_msg_filename: str | None = None,\n        checkout_type: str | None = None,\n        is_squash_merge: str | None = None,\n        rewrite_command: str | None = None,\n) -> argparse.Namespace:\n    return argparse.Namespace(\n        color=color,\n        hook_stage=hook_type.replace('pre-', ''),\n        remote_branch=remote_branch,\n        local_branch=local_branch,\n        from_ref=from_ref,\n        to_ref=to_ref,\n        remote_name=remote_name,\n        remote_url=remote_url,\n        commit_msg_filename=commit_msg_filename,\n        all_files=all_files,\n        checkout_type=checkout_type,\n        is_squash_merge=is_squash_merge,\n        rewrite_command=rewrite_command,\n        files=(),\n        hook=None,\n        verbose=False,\n        show_diff_on_failure=False,\n    )\n\n\ndef _rev_exists(rev: str) -> bool:\n    return not subprocess.call(('git', 'rev-list', '--quiet', rev))\n\n\ndef _pre_push_ns(\n        color: bool,\n        args: Sequence[str],\n        stdin: bytes,\n) -> argparse.Namespace | None:\n    remote_name = args[0]\n    remote_url = args[1]\n\n    for line in stdin.decode().splitlines():\n        parts = line.rsplit(maxsplit=3)\n        local_branch, local_sha, remote_branch, remote_sha = parts\n        if local_sha == Z40:\n            continue\n        elif remote_sha != Z40 and _rev_exists(remote_sha):\n            return _ns(\n                'pre-push', color,\n                from_ref=remote_sha, to_ref=local_sha,\n                remote_branch=remote_branch,\n                local_branch=local_branch,\n                remote_name=remote_name, remote_url=remote_url,\n            )\n        else:\n            # ancestors not found in remote\n            ancestors = subprocess.check_output((\n                'git', 'rev-list', local_sha, '--topo-order', '--reverse',\n                '--not', f'--remotes={remote_name}',\n            )).decode().strip()\n            if not ancestors:\n                continue\n            else:\n                first_ancestor = ancestors.splitlines()[0]\n                cmd = ('git', 'rev-list', '--max-parents=0', local_sha)\n                roots = set(subprocess.check_output(cmd).decode().splitlines())\n                if first_ancestor in roots:\n                    # pushing the whole tree including root commit\n                    return _ns(\n                        'pre-push', color,\n                        all_files=True,\n                        remote_name=remote_name, remote_url=remote_url,\n                        remote_branch=remote_branch,\n                        local_branch=local_branch,\n                    )\n                else:\n                    rev_cmd = ('git', 'rev-parse', f'{first_ancestor}^')\n                    source = subprocess.check_output(rev_cmd).decode().strip()\n                    return _ns(\n                        'pre-push', color,\n                        from_ref=source, to_ref=local_sha,\n                        remote_name=remote_name, remote_url=remote_url,\n                        remote_branch=remote_branch,\n                        local_branch=local_branch,\n                    )\n\n    # nothing to push\n    return None\n\n\n_EXPECTED_ARG_LENGTH_BY_HOOK = {\n    'commit-msg': 1,\n    'post-checkout': 3,\n    'post-commit': 0,\n    'pre-commit': 0,\n    'pre-merge-commit': 0,\n    'post-merge': 1,\n    'post-rewrite': 1,\n    'pre-push': 2,\n}\n\n\ndef _check_args_length(hook_type: str, args: Sequence[str]) -> None:\n    if hook_type == 'prepare-commit-msg':\n        if len(args) < 1 or len(args) > 3:\n            raise SystemExit(\n                f'hook-impl for {hook_type} expected 1, 2, or 3 arguments '\n                f'but got {len(args)}: {args}',\n            )\n    elif hook_type in _EXPECTED_ARG_LENGTH_BY_HOOK:\n        expected = _EXPECTED_ARG_LENGTH_BY_HOOK[hook_type]\n        if len(args) != expected:\n            arguments_s = 'argument' if expected == 1 else 'arguments'\n            raise SystemExit(\n                f'hook-impl for {hook_type} expected {expected} {arguments_s} '\n                f'but got {len(args)}: {args}',\n            )\n    else:\n        raise AssertionError(f'unexpected hook type: {hook_type}')\n\n\ndef _run_ns(\n        hook_type: str,\n        color: bool,\n        args: Sequence[str],\n        stdin: bytes,\n) -> argparse.Namespace | None:\n    _check_args_length(hook_type, args)\n    if hook_type == 'pre-push':\n        return _pre_push_ns(color, args, stdin)\n    elif hook_type in {'commit-msg', 'prepare-commit-msg'}:\n        return _ns(hook_type, color, commit_msg_filename=args[0])\n    elif hook_type in {'post-commit', 'pre-merge-commit', 'pre-commit'}:\n        return _ns(hook_type, color)\n    elif hook_type == 'post-checkout':\n        return _ns(\n            hook_type, color,\n            from_ref=args[0], to_ref=args[1], checkout_type=args[2],\n        )\n    elif hook_type == 'post-merge':\n        return _ns(hook_type, color, is_squash_merge=args[0])\n    elif hook_type == 'post-rewrite':\n        return _ns(hook_type, color, rewrite_command=args[0])\n    else:\n        raise AssertionError(f'unexpected hook type: {hook_type}')\n\n\ndef hook_impl(\n        store: Store,\n        *,\n        config: str,\n        color: bool,\n        hook_type: str,\n        hook_dir: str,\n        skip_on_missing_config: bool,\n        args: Sequence[str],\n) -> int:\n    retv, stdin = _run_legacy(hook_type, hook_dir, args)\n    _validate_config(retv, config, skip_on_missing_config)\n    ns = _run_ns(hook_type, color, args, stdin)\n    if ns is None:\n        return retv\n    else:\n        return retv | run(config, store, ns)\n", "path": "pre_commit/commands/hook_impl.py"}]}