diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index ac418a7..8b80bef 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -36,13 +36,6 @@ def _hook_msg_start(hook, verbose):
     )
 
 
-def get_changed_files(new, old):
-    return cmd_output(
-        'git', 'diff', '--no-ext-diff', '--name-only',
-        '{}...{}'.format(old, new),
-    )[1].splitlines()
-
-
 def filter_filenames_by_types(filenames, types, exclude_types):
     types, exclude_types = frozenset(types), frozenset(exclude_types)
     ret = []
@@ -56,7 +49,7 @@ def filter_filenames_by_types(filenames, types, exclude_types):
 def get_filenames(args, include_expr, exclude_expr):
     if args.origin and args.source:
         getter = git.get_files_matching(
-            lambda: get_changed_files(args.origin, args.source),
+            lambda: git.get_changed_files(args.origin, args.source),
         )
     elif args.hook_stage == 'commit-msg':
         def getter(*_):
diff --git a/pre_commit/git.py b/pre_commit/git.py
index 4b519c8..cdf807b 100644
--- a/pre_commit/git.py
+++ b/pre_commit/git.py
@@ -15,6 +15,14 @@ from pre_commit.util import memoize_by_cwd
 logger = logging.getLogger('pre_commit')
 
 
+def zsplit(s):
+    s = s.strip('\0')
+    if s:
+        return s.split('\0')
+    else:
+        return []
+
+
 def get_root():
     try:
         return cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip()
@@ -67,25 +75,32 @@ def get_conflicted_files():
     # If they resolved the merge conflict by choosing a mesh of both sides
     # this will also include the conflicted files
     tree_hash = cmd_output('git', 'write-tree')[1].strip()
-    merge_diff_filenames = cmd_output(
-        'git', 'diff', '--no-ext-diff',
-        '-m', tree_hash, 'HEAD', 'MERGE_HEAD', '--name-only',
-    )[1].splitlines()
+    merge_diff_filenames = zsplit(cmd_output(
+        'git', 'diff', '--name-only', '--no-ext-diff', '-z',
+        '-m', tree_hash, 'HEAD', 'MERGE_HEAD',
+    )[1])
     return set(merge_conflict_filenames) | set(merge_diff_filenames)
 
 
 @memoize_by_cwd
 def get_staged_files():
-    return cmd_output(
-        'git', 'diff', '--staged', '--name-only', '--no-ext-diff',
+    return zsplit(cmd_output(
+        'git', 'diff', '--staged', '--name-only', '--no-ext-diff', '-z',
         # Everything except for D
         '--diff-filter=ACMRTUXB',
-    )[1].splitlines()
+    )[1])
 
 
 @memoize_by_cwd
 def get_all_files():
-    return cmd_output('git', 'ls-files')[1].splitlines()
+    return zsplit(cmd_output('git', 'ls-files', '-z')[1])
+
+
+def get_changed_files(new, old):
+    return zsplit(cmd_output(
+        'git', 'diff', '--name-only', '--no-ext-diff', '-z',
+        '{}...{}'.format(old, new),
+    )[1])
 
 
 def get_files_matching(all_file_list_strategy):

Non-English directories are skipped
Looks like pre-commit skips non-English directories. Please take a look at those:

* https://github.com/GolangShow/golangshow.com/commit/df0529b104739ba8d1de9182d37dfb80bbddf679
* https://travis-ci.org/GolangShow/golangshow.com/builds/271948939

A relevant bit from the full Travis build output:
```
$ pre-commit run --all-files
[…]
Trim Trailing Whitespace.................................................Failed
hookid: trailing-whitespace
Files were modified by this hook. Additional output:
Fixing public/episode/2017/04-14-096/index.html
Fixing public/episode/2016/03-31-050/index.html
[…]
Fixing public/episode/2016/09-01-072/index.html
Fixing public/episode/2015/12-29-036/index.html
The command "pre-commit run --all-files" exited with 1.

$ git status
HEAD detached at df0529b
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
	modified:   "public/categories/\320\263\320\276\321\201\321\202\320\270/index.html"
	modified:   "public/categories/\320\263\320\276\321\201\321\202\320\270/index.xml"
	modified:   "public/categories/\320\270\320\275\321\202\320\265\321\200\320\262\321\214\321\216/index.html"
	modified:   "public/categories/\320\270\320\275\321\202\320\265\321\200\320\262\321\214\321\216/index.xml"
	modified:   "public/categories/\320\270\321\202\320\276\320\263\320\270/index.html"
	modified:   "public/categories/\320\270\321\202\320\276\320\263\320\270/index.xml"
	modified:   "public/categories/\320\272\320\260\320\274\320\270\320\275/index.html"
	modified:   "public/categories/\320\272\320\260\320\274\320\270\320\275/index.xml"
	modified:   "public/categories/\320\275\320\276\320\262\320\276\321\201\321\202\320\270/index.html"
	modified:   "public/categories/\320\275\320\276\320\262\320\276\321\201\321\202\320\270/index.xml"
	modified:   public/js/highlight.pack.js
	modified:   public/page/1/index.html
no changes added to commit (use "git add" and/or "git commit -a")
```

Those directories in `public/categories/` are `гости`, `итоги`, `камин`, `новости`, and `интервью`.
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index 924d097..5ed0ad8 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -15,7 +15,6 @@ from pre_commit.commands.install_uninstall import install
 from pre_commit.commands.run import _compute_cols
 from pre_commit.commands.run import _get_skips
 from pre_commit.commands.run import _has_unmerged_paths
-from pre_commit.commands.run import get_changed_files
 from pre_commit.commands.run import run
 from pre_commit.runner import Runner
 from pre_commit.util import cmd_output
@@ -501,18 +500,6 @@ def test_hook_install_failure(mock_out_store_directory, tempdir_factory):
         assert '☃'.encode('UTF-8') + '²'.encode('latin1') in stdout
 
 
-def test_get_changed_files():
-    files = get_changed_files(
-        '78c682a1d13ba20e7cb735313b9314a74365cd3a',
-        '3387edbb1288a580b37fe25225aa0b856b18ad1a',
-    )
-    assert files == ['CHANGELOG.md', 'setup.py']
-
-    # files changed in source but not in origin should not be returned
-    files = get_changed_files('HEAD~10', 'HEAD')
-    assert files == []
-
-
 def test_lots_of_files(mock_out_store_directory, tempdir_factory):
     # windows xargs seems to have a bug, here's a regression test for
     # our workaround
diff --git a/tests/git_test.py b/tests/git_test.py
index 0500a42..4f67911 100644
--- a/tests/git_test.py
+++ b/tests/git_test.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
@@ -162,3 +163,63 @@ OTHER_MERGE_MSG = MERGE_MSG + b'\tother_conflict_file\n'
 def test_parse_merge_msg_for_conflicts(input, expected_output):
     ret = git.parse_merge_msg_for_conflicts(input)
     assert ret == expected_output
+
+
+def test_get_changed_files():
+    files = git.get_changed_files(
+        '78c682a1d13ba20e7cb735313b9314a74365cd3a',
+        '3387edbb1288a580b37fe25225aa0b856b18ad1a',
+    )
+    assert files == ['CHANGELOG.md', 'setup.py']
+
+    # files changed in source but not in origin should not be returned
+    files = git.get_changed_files('HEAD~10', 'HEAD')
+    assert files == []
+
+
+@pytest.mark.parametrize(
+    ('s', 'expected'),
+    (
+        ('foo\0bar\0', ['foo', 'bar']),
+        ('foo\0', ['foo']),
+        ('', []),
+        ('foo', ['foo']),
+    ),
+)
+def test_zsplit(s, expected):
+    assert git.zsplit(s) == expected
+
+
+@pytest.fixture
+def non_ascii_repo(tmpdir):
+    repo = tmpdir.join('repo').ensure_dir()
+    with repo.as_cwd():
+        cmd_output('git', 'init', '.')
+        cmd_output('git', 'commit', '--allow-empty', '-m', 'initial commit')
+        repo.join('интервью').ensure()
+        cmd_output('git', 'add', '.')
+        cmd_output('git', 'commit', '--allow-empty', '-m', 'initial commit')
+        yield repo
+
+
+def test_all_files_non_ascii(non_ascii_repo):
+    ret = git.get_all_files()
+    assert ret == ['интервью']
+
+
+def test_staged_files_non_ascii(non_ascii_repo):
+    non_ascii_repo.join('интервью').write('hi')
+    cmd_output('git', 'add', '.')
+    assert git.get_staged_files() == ['интервью']
+
+
+def test_changed_files_non_ascii(non_ascii_repo):
+    ret = git.get_changed_files('HEAD', 'HEAD^')
+    assert ret == ['интервью']
+
+
+def test_get_conflicted_files_non_ascii(in_merge_conflict):
+    open('интервью', 'a').close()
+    cmd_output('git', 'add', '.')
+    ret = git.get_conflicted_files()
+    assert ret == {'conflict_file', 'интервью'}

pip install -e .[dev]
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
aspy.yaml==1.3.0
cached-property==2.0.1
coverage==7.8.0
distlib==0.3.9
exceptiongroup==1.2.2
filelock==3.18.0
flake8==7.2.0
identify==2.6.9
iniconfig==2.1.0
mccabe==0.7.0
mock==5.2.0
nodeenv==1.9.1
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
-e git+https://github.com/pre-commit/pre-commit.git@8b14c6c5ae24757b0f6f60979a9ffe06039c298f#egg=pre_commit
pycodestyle==2.13.0
pyflakes==3.3.1
pytest==8.3.5
pytest-env==1.1.5
PyYAML==6.0.2
six==1.17.0
tomli==2.2.1
virtualenv==20.29.3

name: pre-commit
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:
      - aspy-yaml==1.3.0
      - cached-property==2.0.1
      - coverage==7.8.0
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - flake8==7.2.0
      - identify==2.6.9
      - iniconfig==2.1.0
      - mccabe==0.7.0
      - mock==5.2.0
      - nodeenv==1.9.1
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pycodestyle==2.13.0
      - pyflakes==3.3.1
      - pytest==8.3.5
      - pytest-env==1.1.5
      - pyyaml==6.0.2
      - six==1.17.0
      - tomli==2.2.1
      - virtualenv==20.29.3
prefix: /opt/conda/envs/pre-commit

tests/git_test.py::test_get_changed_files tests/git_test.py::test_zsplit[foo\x00bar\x00-expected0] tests/git_test.py::test_zsplit[foo\x00-expected1] tests/git_test.py::test_zsplit[-expected2] tests/git_test.py::test_zsplit[foo-expected3] tests/git_test.py::test_all_files_non_ascii tests/git_test.py::test_staged_files_non_ascii tests/git_test.py::test_changed_files_non_ascii tests/git_test.py::test_get_conflicted_files_non_ascii
tests/commands/run_test.py::test_arbitrary_bytes_hook tests/commands/run_test.py::test_hook_install_failure
tests/commands/run_test.py::test_run_all_hooks_failing tests/commands/run_test.py::test_hook_that_modifies_but_returns_zero tests/commands/run_test.py::test_types_hook_repository tests/commands/run_test.py::test_exclude_types_hook_repository tests/commands/run_test.py::test_show_diff_on_failure tests/commands/run_test.py::test_run[options0-outputs0-0-True] tests/commands/run_test.py::test_run[options1-outputs1-0-True] tests/commands/run_test.py::test_run[options2-outputs2-0-True] tests/commands/run_test.py::test_run[options3-outputs3-1-True] tests/commands/run_test.py::test_run[options4-outputs4-0-True] tests/commands/run_test.py::test_run[options5-outputs5-0-True] tests/commands/run_test.py::test_run[options6-outputs6-0-False] tests/commands/run_test.py::test_run_output_logfile tests/commands/run_test.py::test_always_run tests/commands/run_test.py::test_always_run_alt_config tests/commands/run_test.py::test_origin_source_error_msg[master-master-False] tests/commands/run_test.py::test_origin_source_error_msg[master--True] tests/commands/run_test.py::test_origin_source_error_msg[-master-True] tests/commands/run_test.py::test_has_unmerged_paths[some-True] tests/commands/run_test.py::test_has_unmerged_paths[-False] tests/commands/run_test.py::test_merge_conflict tests/commands/run_test.py::test_merge_conflict_modified tests/commands/run_test.py::test_merge_conflict_resolved tests/commands/run_test.py::test_compute_cols[hooks0-True-80] tests/commands/run_test.py::test_compute_cols[hooks1-False-81] tests/commands/run_test.py::test_compute_cols[hooks2-True-85] tests/commands/run_test.py::test_compute_cols[hooks3-False-82] tests/commands/run_test.py::test_get_skips[environ0-expected_output0] tests/commands/run_test.py::test_get_skips[environ1-expected_output1] tests/commands/run_test.py::test_get_skips[environ2-expected_output2] tests/commands/run_test.py::test_get_skips[environ3-expected_output3] tests/commands/run_test.py::test_get_skips[environ4-expected_output4] tests/commands/run_test.py::test_get_skips[environ5-expected_output5] tests/commands/run_test.py::test_get_skips[environ6-expected_output6] tests/commands/run_test.py::test_skip_hook tests/commands/run_test.py::test_hook_id_not_in_non_verbose_output tests/commands/run_test.py::test_hook_id_in_verbose_output tests/commands/run_test.py::test_multiple_hooks_same_id tests/commands/run_test.py::test_non_ascii_hook_id tests/commands/run_test.py::test_stdout_write_bug_py26 tests/commands/run_test.py::test_lots_of_files tests/commands/run_test.py::test_push_hook tests/commands/run_test.py::test_commit_msg_hook tests/commands/run_test.py::test_local_hook_passes tests/commands/run_test.py::test_local_hook_fails tests/commands/run_test.py::test_error_with_unstaged_config tests/commands/run_test.py::test_no_unstaged_error_with_all_files_or_files[opts0] tests/commands/run_test.py::test_no_unstaged_error_with_all_files_or_files[opts1] tests/commands/run_test.py::test_files_running_subdir tests/commands/run_test.py::test_pass_filenames[True-hook_args0-foo.py] tests/commands/run_test.py::test_pass_filenames[False-hook_args1-] tests/commands/run_test.py::test_pass_filenames[True-hook_args2-some tests/commands/run_test.py::test_pass_filenames[False-hook_args3-some tests/git_test.py::test_get_root_at_root tests/git_test.py::test_get_root_deeper tests/git_test.py::test_get_root_not_git_dir tests/git_test.py::test_get_staged_files_deleted tests/git_test.py::test_is_not_in_merge_conflict tests/git_test.py::test_is_in_merge_conflict tests/git_test.py::test_cherry_pick_conflict tests/git_test.py::test_get_files_matching_base tests/git_test.py::test_matches_broken_symlink tests/git_test.py::test_get_files_matching_total_match tests/git_test.py::test_does_search_instead_of_match tests/git_test.py::test_does_not_include_deleted_fileS tests/git_test.py::test_exclude_removes_files tests/git_test.py::test_get_conflicted_files tests/git_test.py::test_get_conflicted_files_unstaged_files tests/git_test.py::test_parse_merge_msg_for_conflicts[Merge
pre_commit/git.py pre_commit/commands/run.py
pre_commit/git.py pre_commit/commands/run.py