pawamoy: I used a ternary operator in the `zsplit` method to avoid coverage falling under 100. I honestly don't feel like testing `zsplit` to make sure the `else return []` branch is met with an input like `\0\0` is useful, but if you still want it, I can add it 🙂 
asottile: > 
> 
> I used a ternary operator in the `zsplit` method to avoid coverage falling under 100. Unless we move `zsplit` in the utils module, I don't think testing it to make sure the `else return []` branch is met with an input like `\0\0` is useful, but if you still want it, I can add it 🙂

testing this is important, it's the entire reason it's a separate function and not just `.split('\0')`
pawamoy: Fair enough, I'll write some tests 🙂 
pawamoy: Test correctly failing without the fix:

```
_____________ test_check_git_filemode_passing_unusual_characters ______________

tmpdir = local('C:\\Users\\MAZZUCTI\\AppData\\Local\\Temp\\1\\pytest-of-unknown\\pytest-6\\test_check_git_filemode_passin1')

    def test_check_git_filemode_passing_unusual_characters(tmpdir):
        with tmpdir.as_cwd():
            cmd_output('git', 'init', '.')

            f = tmpdir.join('ma▒ana.txt')
            f.write('#!/usr/bin/env bash')
            f_path = str(f)
            cmd_output('chmod', '+x', f_path)
            cmd_output('git', 'add', f_path)
            cmd_output('git', 'update-index', '--chmod=+x', f_path)

            files = (f_path,)
>           assert check_executables_have_shebangs._check_git_filemode(files) == 0

tests\check_executables_have_shebangs_test.py:88:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
pre_commit_hooks\check_executables_have_shebangs.py:44: in _check_git_filemode
    has_shebang = _check_has_shebang(path)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

path = '"ma\\303\\261ana.txt"'

    def _check_has_shebang(path: str) -> int:
>       with open(path, 'rb') as f:
E       OSError: [Errno 22] Invalid argument: '"ma\\303\\261ana.txt"'

pre_commit_hooks\check_executables_have_shebangs.py:53: OSError
```
diff --git a/pre_commit_hooks/check_executables_have_shebangs.py b/pre_commit_hooks/check_executables_have_shebangs.py
index 1c50ea0..a02d2a9 100644
--- a/pre_commit_hooks/check_executables_have_shebangs.py
+++ b/pre_commit_hooks/check_executables_have_shebangs.py
@@ -12,6 +12,14 @@ from pre_commit_hooks.util import cmd_output
 EXECUTABLE_VALUES = frozenset(('1', '3', '5', '7'))
 
 
+def zsplit(s: str) -> List[str]:
+    s = s.strip('\0')
+    if s:
+        return s.split('\0')
+    else:
+        return []
+
+
 def check_executables(paths: List[str]) -> int:
     if sys.platform == 'win32':  # pragma: win32 cover
         return _check_git_filemode(paths)
@@ -26,9 +34,9 @@ def check_executables(paths: List[str]) -> int:
 
 
 def _check_git_filemode(paths: Sequence[str]) -> int:
-    outs = cmd_output('git', 'ls-files', '--stage', '--', *paths)
+    outs = cmd_output('git', 'ls-files', '-z', '--stage', '--', *paths)
     seen: Set[str] = set()
-    for out in outs.splitlines():
+    for out in zsplit(outs):
         metadata, path = out.split('\t')
         tagmode = metadata.split(' ', 1)[0]
 

Filenames with unusual characters break check_executables_have_shebangs
The `check_executables_have_shebangs` gets file paths with the output of the following git command:

```
git ls-files --stage -- path1 path2
```

I'm not sure about Linux, but on Windows, when the filename contains an unusual character (sorry about the choice of "unusual" to describe the character, I don't want to speculate on encoding issues), the character is escaped with integer sequences, and git wraps the file path in double-quotes (because of the backslashes I guess):

```console
$ git ls-files --stage -- tests/demo/doc/mañana.txt tests/demo/doc/manana.txt
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       tests/demo/doc/manana.txt
100644 5ab9dcdd36df0f76f202227ecb7ae8a5baaa456b 0       "tests/demo/doc/ma\303\261ana.txt"
```

The resulting path variable becomes `"tests/demo/doc/ma\303\261ana.txt"`, and then the script tries to `open` it, which fails of course because of the quotes and escaping:

```
Traceback (most recent call last):
  File "C:\Users\user\AppData\Local\Programs\Python\Python36\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Users\user\AppData\Local\Programs\Python\Python36\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Users\user\.cache\pre-commit\repofzzr3u3t\py_env-python3\Scripts\check-executables-have-shebangs.EXE\__main__.py", line 7, in <module>
  File "c:\users\user\.cache\pre-commit\repofzzr3u3t\py_env-python3\lib\site-packages\pre_commit_hooks\check_executables_have_shebangs.py", line 66, in main
    return check_executables(args.filenames)
  File "c:\users\user\.cache\pre-commit\repofzzr3u3t\py_env-python3\lib\site-packages\pre_commit_hooks\check_executables_have_shebangs.py", line 17, in check_executables
    return _check_git_filemode(paths)
  File "c:\users\user\.cache\pre-commit\repofzzr3u3t\py_env-python3\lib\site-packages\pre_commit_hooks\check_executables_have_shebangs.py", line 36, in _check_git_filemode
    has_shebang = _check_has_shebang(path)
  File "c:\users\user\.cache\pre-commit\repofzzr3u3t\py_env-python3\lib\site-packages\pre_commit_hooks\check_executables_have_shebangs.py", line 45, in _check_has_shebang
    with open(path, 'rb') as f:
OSError: [Errno 22] Invalid argument: '"tests/demo/doc/ma\\303\\261ana.txt"'
```

To fix the quotes issue, the pre-commit script could try to remove them with a `.strip('"')` call.

For the character escaping, I have no idea! Do you 😄 ?

Ref: https://github.com/copier-org/copier/pull/224#issuecomment-665079662
diff --git a/tests/check_executables_have_shebangs_test.py b/tests/check_executables_have_shebangs_test.py
index 5895a2a..7046081 100644
--- a/tests/check_executables_have_shebangs_test.py
+++ b/tests/check_executables_have_shebangs_test.py
@@ -73,6 +73,21 @@ def test_check_git_filemode_passing(tmpdir):
         assert check_executables_have_shebangs._check_git_filemode(files) == 0
 
 
+def test_check_git_filemode_passing_unusual_characters(tmpdir):
+    with tmpdir.as_cwd():
+        cmd_output('git', 'init', '.')
+
+        f = tmpdir.join('mañana.txt')
+        f.write('#!/usr/bin/env bash')
+        f_path = str(f)
+        cmd_output('chmod', '+x', f_path)
+        cmd_output('git', 'add', f_path)
+        cmd_output('git', 'update-index', '--chmod=+x', f_path)
+
+        files = (f_path,)
+        assert check_executables_have_shebangs._check_git_filemode(files) == 0
+
+
 def test_check_git_filemode_failing(tmpdir):
     with tmpdir.as_cwd():
         cmd_output('git', 'init', '.')
@@ -87,6 +102,16 @@ def test_check_git_filemode_failing(tmpdir):
         assert check_executables_have_shebangs._check_git_filemode(files) == 1
 
 
+@pytest.mark.parametrize('out', ('\0f1\0f2\0', '\0f1\0f2', 'f1\0f2\0'))
+def test_check_zsplits_correctly(out):
+    assert check_executables_have_shebangs.zsplit(out) == ['f1', 'f2']
+
+
+@pytest.mark.parametrize('out', ('\0\0', '\0', ''))
+def test_check_zsplit_returns_empty(out):
+    assert check_executables_have_shebangs.zsplit(out) == []
+
+
 @pytest.mark.parametrize(
     ('content', 'mode', 'expected'),
     (

pip install -e .
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
covdefaults==2.3.0
coverage==7.8.0
exceptiongroup==1.2.2
iniconfig==2.1.0
packaging==24.2
pluggy==1.5.0
-e git+https://github.com/pre-commit/pre-commit-hooks.git@5372f44b858f6eef834d9632e9960c39c296d448#egg=pre_commit_hooks
pytest==8.3.5
ruamel.yaml==0.18.10
ruamel.yaml.clib==0.2.12
toml==0.10.2
tomli==2.2.1

name: pre-commit-hooks
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - covdefaults==2.3.0
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - ruamel-yaml==0.18.10
      - ruamel-yaml-clib==0.2.12
      - toml==0.10.2
      - tomli==2.2.1
prefix: /opt/conda/envs/pre-commit-hooks

tests/check_executables_have_shebangs_test.py::test_check_git_filemode_passing_unusual_characters tests/check_executables_have_shebangs_test.py::test_check_zsplits_correctly[\x00f1\x00f2\x00] tests/check_executables_have_shebangs_test.py::test_check_zsplits_correctly[\x00f1\x00f2] tests/check_executables_have_shebangs_test.py::test_check_zsplits_correctly[f1\x00f2\x00] tests/check_executables_have_shebangs_test.py::test_check_zsplit_returns_empty[\x00\x00] tests/check_executables_have_shebangs_test.py::test_check_zsplit_returns_empty[\x00] tests/check_executables_have_shebangs_test.py::test_check_zsplit_returns_empty[]
tests/check_executables_have_shebangs_test.py::test_has_shebang[#!/bin/bash\nhello tests/check_executables_have_shebangs_test.py::test_has_shebang[#!/usr/bin/env tests/check_executables_have_shebangs_test.py::test_has_shebang[#!python] tests/check_executables_have_shebangs_test.py::test_has_shebang[#!\xe2\x98\x83] tests/check_executables_have_shebangs_test.py::test_bad_shebang[] tests/check_executables_have_shebangs_test.py::test_bad_shebang[ tests/check_executables_have_shebangs_test.py::test_bad_shebang[\n#!python\n] tests/check_executables_have_shebangs_test.py::test_bad_shebang[python\n] tests/check_executables_have_shebangs_test.py::test_bad_shebang[\xe2\x98\x83] tests/check_executables_have_shebangs_test.py::test_check_git_filemode_passing tests/check_executables_have_shebangs_test.py::test_check_git_filemode_failing tests/check_executables_have_shebangs_test.py::test_git_executable_shebang[shebang tests/check_executables_have_shebangs_test.py::test_git_executable_shebang[no
pre_commit_hooks/check_executables_have_shebangs.py
pre_commit_hooks/check_executables_have_shebangs.py