diff --git a/pre_commit/git.py b/pre_commit/git.py
index ccdd185..f0b5040 100644
--- a/pre_commit/git.py
+++ b/pre_commit/git.py
@@ -97,6 +97,20 @@ def get_staged_files():
     )[1])
 
 
+def intent_to_add_files():
+    _, stdout_binary, _ = cmd_output('git', 'status', '--porcelain', '-z')
+    parts = list(reversed(zsplit(stdout_binary)))
+    intent_to_add = []
+    while parts:
+        line = parts.pop()
+        status, filename = line[:3], line[3:]
+        if status[0] in {'C', 'R'}:  # renames / moves have an additional arg
+            parts.pop()
+        if status[1] == 'A':
+            intent_to_add.append(filename)
+    return intent_to_add
+
+
 def get_all_files():
     return zsplit(cmd_output('git', 'ls-files', '-z')[1])
 
diff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py
index 1d0c364..7af319d 100644
--- a/pre_commit/staged_files_only.py
+++ b/pre_commit/staged_files_only.py
@@ -6,9 +6,11 @@ import logging
 import os.path
 import time
 
+from pre_commit import git
 from pre_commit.util import CalledProcessError
 from pre_commit.util import cmd_output
 from pre_commit.util import mkdirp
+from pre_commit.xargs import xargs
 
 
 logger = logging.getLogger('pre_commit')
@@ -24,11 +26,22 @@ def _git_apply(patch):
 
 
 @contextlib.contextmanager
-def staged_files_only(patch_dir):
-    """Clear any unstaged changes from the git working directory inside this
-    context.
-    """
-    # Determine if there are unstaged files
+def _intent_to_add_cleared():
+    intent_to_add = git.intent_to_add_files()
+    if intent_to_add:
+        logger.warning('Unstaged intent-to-add files detected.')
+
+        xargs(('git', 'rm', '--cached', '--'), intent_to_add)
+        try:
+            yield
+        finally:
+            xargs(('git', 'add', '--intent-to-add', '--'), intent_to_add)
+    else:
+        yield
+
+
+@contextlib.contextmanager
+def _unstaged_changes_cleared(patch_dir):
     tree = cmd_output('git', 'write-tree')[1].strip()
     retcode, diff_stdout_binary, _ = cmd_output(
         'git', 'diff-index', '--ignore-submodules', '--binary',
@@ -71,3 +84,12 @@ def staged_files_only(patch_dir):
         # There weren't any staged files so we don't need to do anything
         # special
         yield
+
+
+@contextlib.contextmanager
+def staged_files_only(patch_dir):
+    """Clear any unstaged changes from the git working directory inside this
+    context.
+    """
+    with _intent_to_add_cleared(), _unstaged_changes_cleared(patch_dir):
+        yield

"git add -N" prevents restoring stashed changes.
To reproduce, start with this simple `pre-commit-config.yaml` in an otherwise empty repo:

```yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: master
  hooks: 
    - id: end-of-file-fixer
```

The hook used doesn't really matter. end-of-file-fixer is just an example. 

Run the following:
```bash
echo "new" > newfile
echo "\n\n\n" > needs-fixing
git add -N newfile
# newfile is now staged as an empty file
git add needs-fixing
git commit -m "fix"
```

The following output is generated: 

```
[WARNING] Unstaged files detected.
[INFO] Stashing unstaged files to /home/henniss/.cache/pre-commit/patch1544663784.
Fix End of Files.........................................................Failed
hookid: end-of-file-fixer

Files were modified by this hook. Additional output:

Fixing needs-fixing

[WARNING] Stashed changes conflicted with hook auto-fixes... Rolling back fixes...
An unexpected error has occurred: CalledProcessError: Command: ('/usr/lib/git-core/git', '-c', 'core.autocrlf=false', 'apply', '--whitespace=nowarn', '/home/henniss/.cache/pre-commit/patch1544663784')
Return code: 1
Expected return code: 0
Output: (none)
Errors:
    error: newfile: already exists in working directory


Check the log at /home/henniss/.cache/pre-commit/pre-commit.log
```

`cat newfile` now shows that it is empty. The unstaged changes aren't restored.
diff --git a/tests/git_test.py b/tests/git_test.py
index a78b745..43f1c15 100644
--- a/tests/git_test.py
+++ b/tests/git_test.py
@@ -155,3 +155,21 @@ def test_get_conflicted_files_non_ascii(in_merge_conflict):
     cmd_output('git', 'add', '.')
     ret = git.get_conflicted_files()
     assert ret == {'conflict_file', 'интервью'}
+
+
+def test_intent_to_add(in_git_dir):
+    in_git_dir.join('a').ensure()
+    cmd_output('git', 'add', '--intent-to-add', 'a')
+
+    assert git.intent_to_add_files() == ['a']
+
+
+def test_status_output_with_rename(in_git_dir):
+    in_git_dir.join('a').write('1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n')
+    cmd_output('git', 'add', 'a')
+    git_commit()
+    cmd_output('git', 'mv', 'a', 'b')
+    in_git_dir.join('c').ensure()
+    cmd_output('git', 'add', '--intent-to-add', 'c')
+
+    assert git.intent_to_add_files() == ['c']
diff --git a/tests/staged_files_only_test.py b/tests/staged_files_only_test.py
index 619d739..2410bff 100644
--- a/tests/staged_files_only_test.py
+++ b/tests/staged_files_only_test.py
@@ -9,6 +9,7 @@ import shutil
 
 import pytest
 
+from pre_commit import git
 from pre_commit.staged_files_only import staged_files_only
 from pre_commit.util import cmd_output
 from testing.auto_namedtuple import auto_namedtuple
@@ -339,3 +340,14 @@ def test_autocrlf_commited_crlf(in_git_dir, patch_dir):
 
     with staged_files_only(patch_dir):
         assert_no_diff()
+
+
+def test_intent_to_add(in_git_dir, patch_dir):
+    """Regression test for #881"""
+    _write(b'hello\nworld\n')
+    cmd_output('git', 'add', '--intent-to-add', 'foo')
+
+    assert git.intent_to_add_files() == ['foo']
+    with staged_files_only(patch_dir):
+        assert_no_diff()
+    assert git.intent_to_add_files() == ['foo']

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio mock
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aspy.yaml==1.3.0
cfgv==3.4.0
coverage==7.8.0
distlib==0.3.9
exceptiongroup==1.2.2
execnet==2.1.1
filelock==3.18.0
identify==2.6.9
importlib_metadata==8.6.1
iniconfig==2.1.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@2941a1142b92c3a2d24e2066f56c23c417c2049a#egg=pre_commit
pytest==8.3.5
pytest-asyncio==0.26.0
pytest-cov==6.0.0
pytest-mock==3.14.0
pytest-xdist==3.6.1
PyYAML==6.0.2
six==1.17.0
toml==0.10.2
tomli==2.2.1
typing_extensions==4.13.0
virtualenv==20.29.3
zipp==3.21.0

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
      - cfgv==3.4.0
      - coverage==7.8.0
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - filelock==3.18.0
      - identify==2.6.9
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - mock==5.2.0
      - nodeenv==1.9.1
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-asyncio==0.26.0
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - pytest-xdist==3.6.1
      - pyyaml==6.0.2
      - six==1.17.0
      - toml==0.10.2
      - tomli==2.2.1
      - typing-extensions==4.13.0
      - virtualenv==20.29.3
      - zipp==3.21.0
prefix: /opt/conda/envs/pre-commit

tests/git_test.py::test_intent_to_add tests/git_test.py::test_status_output_with_rename tests/staged_files_only_test.py::test_intent_to_add
tests/git_test.py::test_get_root_at_root tests/git_test.py::test_get_root_deeper 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_conflicted_files tests/git_test.py::test_get_conflicted_files_unstaged_files tests/git_test.py::test_parse_merge_msg_for_conflicts[Merge 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/staged_files_only_test.py::test_foo_staged tests/staged_files_only_test.py::test_foo_nothing_unstaged tests/staged_files_only_test.py::test_foo_something_unstaged tests/staged_files_only_test.py::test_does_not_crash_patch_dir_does_not_exist tests/staged_files_only_test.py::test_something_unstaged_ext_diff_tool tests/staged_files_only_test.py::test_foo_something_unstaged_diff_color_always tests/staged_files_only_test.py::test_foo_both_modify_non_conflicting tests/staged_files_only_test.py::test_foo_both_modify_conflicting tests/staged_files_only_test.py::test_img_staged tests/staged_files_only_test.py::test_img_nothing_unstaged tests/staged_files_only_test.py::test_img_something_unstaged tests/staged_files_only_test.py::test_img_conflict tests/staged_files_only_test.py::test_stage_utf8_changes tests/staged_files_only_test.py::test_stage_non_utf8_changes tests/staged_files_only_test.py::test_non_utf8_conflicting_diff tests/staged_files_only_test.py::test_crlf[true-True-True] tests/staged_files_only_test.py::test_crlf[true-True-False] tests/staged_files_only_test.py::test_crlf[true-False-True] tests/staged_files_only_test.py::test_crlf[true-False-False] tests/staged_files_only_test.py::test_crlf[false-True-True] tests/staged_files_only_test.py::test_crlf[false-True-False] tests/staged_files_only_test.py::test_crlf[false-False-True] tests/staged_files_only_test.py::test_crlf[false-False-False] tests/staged_files_only_test.py::test_crlf[input-True-True] tests/staged_files_only_test.py::test_crlf[input-True-False] tests/staged_files_only_test.py::test_crlf[input-False-True] tests/staged_files_only_test.py::test_crlf[input-False-False] tests/staged_files_only_test.py::test_whitespace_errors tests/staged_files_only_test.py::test_autocrlf_commited_crlf
pre_commit/git.py pre_commit/staged_files_only.py
pre_commit/git.py pre_commit/staged_files_only.py