We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
"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.

--- 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/git.py`
Content:
```
1 from __future__ import unicode_literals
2 
3 import logging
4 import os.path
5 import sys
6 
7 from pre_commit.util import cmd_output
8 
9 
10 logger = logging.getLogger(__name__)
11 
12 
13 def zsplit(s):
14     s = s.strip('\0')
15     if s:
16         return s.split('\0')
17     else:
18         return []
19 
20 
21 def no_git_env():
22     # Too many bugs dealing with environment variables and GIT:
23     # https://github.com/pre-commit/pre-commit/issues/300
24     # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running
25     # pre-commit hooks
26     # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE
27     # while running pre-commit hooks in submodules.
28     # GIT_DIR: Causes git clone to clone wrong thing
29     # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit
30     return {
31         k: v for k, v in os.environ.items()
32         if not k.startswith('GIT_') or k in {'GIT_SSH'}
33     }
34 
35 
36 def get_root():
37     return cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip()
38 
39 
40 def get_git_dir(git_root='.'):
41     opts = ('--git-common-dir', '--git-dir')
42     _, out, _ = cmd_output('git', 'rev-parse', *opts, cwd=git_root)
43     for line, opt in zip(out.splitlines(), opts):
44         if line != opt:  # pragma: no branch (git < 2.5)
45             return os.path.normpath(os.path.join(git_root, line))
46     else:
47         raise AssertionError('unreachable: no git dir')
48 
49 
50 def get_remote_url(git_root):
51     ret = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)[1]
52     return ret.strip()
53 
54 
55 def is_in_merge_conflict():
56     git_dir = get_git_dir('.')
57     return (
58         os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and
59         os.path.exists(os.path.join(git_dir, 'MERGE_HEAD'))
60     )
61 
62 
63 def parse_merge_msg_for_conflicts(merge_msg):
64     # Conflicted files start with tabs
65     return [
66         line.lstrip(b'#').strip().decode('UTF-8')
67         for line in merge_msg.splitlines()
68         # '#\t' for git 2.4.1
69         if line.startswith((b'\t', b'#\t'))
70     ]
71 
72 
73 def get_conflicted_files():
74     logger.info('Checking merge-conflict files only.')
75     # Need to get the conflicted files from the MERGE_MSG because they could
76     # have resolved the conflict by choosing one side or the other
77     with open(os.path.join(get_git_dir('.'), 'MERGE_MSG'), 'rb') as f:
78         merge_msg = f.read()
79     merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)
80 
81     # This will get the rest of the changes made after the merge.
82     # If they resolved the merge conflict by choosing a mesh of both sides
83     # this will also include the conflicted files
84     tree_hash = cmd_output('git', 'write-tree')[1].strip()
85     merge_diff_filenames = zsplit(cmd_output(
86         'git', 'diff', '--name-only', '--no-ext-diff', '-z',
87         '-m', tree_hash, 'HEAD', 'MERGE_HEAD',
88     )[1])
89     return set(merge_conflict_filenames) | set(merge_diff_filenames)
90 
91 
92 def get_staged_files():
93     return zsplit(cmd_output(
94         'git', 'diff', '--staged', '--name-only', '--no-ext-diff', '-z',
95         # Everything except for D
96         '--diff-filter=ACMRTUXB',
97     )[1])
98 
99 
100 def get_all_files():
101     return zsplit(cmd_output('git', 'ls-files', '-z')[1])
102 
103 
104 def get_changed_files(new, old):
105     return zsplit(cmd_output(
106         'git', 'diff', '--name-only', '--no-ext-diff', '-z',
107         '{}...{}'.format(old, new),
108     )[1])
109 
110 
111 def head_rev(remote):
112     _, out, _ = cmd_output('git', 'ls-remote', '--exit-code', remote, 'HEAD')
113     return out.split()[0]
114 
115 
116 def has_diff(*args, **kwargs):
117     repo = kwargs.pop('repo', '.')
118     assert not kwargs, kwargs
119     cmd = ('git', 'diff', '--quiet', '--no-ext-diff') + args
120     return cmd_output(*cmd, cwd=repo, retcode=None)[0]
121 
122 
123 def commit(repo='.'):
124     env = no_git_env()
125     name, email = 'pre-commit', 'asottile+pre-commit@umich.edu'
126     env['GIT_AUTHOR_NAME'] = env['GIT_COMMITTER_NAME'] = name
127     env['GIT_AUTHOR_EMAIL'] = env['GIT_COMMITTER_EMAIL'] = email
128     cmd = ('git', 'commit', '--no-edit', '--no-gpg-sign', '-n', '-minit')
129     cmd_output(*cmd, cwd=repo, env=env)
130 
131 
132 def git_path(name, repo='.'):
133     _, out, _ = cmd_output('git', 'rev-parse', '--git-path', name, cwd=repo)
134     return os.path.join(repo, out.strip())
135 
136 
137 def check_for_cygwin_mismatch():
138     """See https://github.com/pre-commit/pre-commit/issues/354"""
139     if sys.platform in ('cygwin', 'win32'):  # pragma: no cover (windows)
140         is_cygwin_python = sys.platform == 'cygwin'
141         toplevel = cmd_output('git', 'rev-parse', '--show-toplevel')[1]
142         is_cygwin_git = toplevel.startswith('/')
143 
144         if is_cygwin_python ^ is_cygwin_git:
145             exe_type = {True: '(cygwin)', False: '(windows)'}
146             logger.warn(
147                 'pre-commit has detected a mix of cygwin python / git\n'
148                 'This combination is not supported, it is likely you will '
149                 'receive an error later in the program.\n'
150                 'Make sure to use cygwin git+python while using cygwin\n'
151                 'These can be installed through the cygwin installer.\n'
152                 ' - python {}\n'
153                 ' - git {}\n'.format(
154                     exe_type[is_cygwin_python], exe_type[is_cygwin_git],
155                 ),
156             )
157
```

Path: `pre_commit/staged_files_only.py`
Content:
```
1 from __future__ import unicode_literals
2 
3 import contextlib
4 import io
5 import logging
6 import os.path
7 import time
8 
9 from pre_commit.util import CalledProcessError
10 from pre_commit.util import cmd_output
11 from pre_commit.util import mkdirp
12 
13 
14 logger = logging.getLogger('pre_commit')
15 
16 
17 def _git_apply(patch):
18     args = ('apply', '--whitespace=nowarn', patch)
19     try:
20         cmd_output('git', *args, encoding=None)
21     except CalledProcessError:
22         # Retry with autocrlf=false -- see #570
23         cmd_output('git', '-c', 'core.autocrlf=false', *args, encoding=None)
24 
25 
26 @contextlib.contextmanager
27 def staged_files_only(patch_dir):
28     """Clear any unstaged changes from the git working directory inside this
29     context.
30     """
31     # Determine if there are unstaged files
32     tree = cmd_output('git', 'write-tree')[1].strip()
33     retcode, diff_stdout_binary, _ = cmd_output(
34         'git', 'diff-index', '--ignore-submodules', '--binary',
35         '--exit-code', '--no-color', '--no-ext-diff', tree, '--',
36         retcode=None,
37         encoding=None,
38     )
39     if retcode and diff_stdout_binary.strip():
40         patch_filename = 'patch{}'.format(int(time.time()))
41         patch_filename = os.path.join(patch_dir, patch_filename)
42         logger.warning('Unstaged files detected.')
43         logger.info(
44             'Stashing unstaged files to {}.'.format(patch_filename),
45         )
46         # Save the current unstaged changes as a patch
47         mkdirp(patch_dir)
48         with io.open(patch_filename, 'wb') as patch_file:
49             patch_file.write(diff_stdout_binary)
50 
51         # Clear the working directory of unstaged changes
52         cmd_output('git', 'checkout', '--', '.')
53         try:
54             yield
55         finally:
56             # Try to apply the patch we saved
57             try:
58                 _git_apply(patch_filename)
59             except CalledProcessError:
60                 logger.warning(
61                     'Stashed changes conflicted with hook auto-fixes... '
62                     'Rolling back fixes...',
63                 )
64                 # We failed to apply the patch, presumably due to fixes made
65                 # by hooks.
66                 # Roll back the changes made by hooks.
67                 cmd_output('git', 'checkout', '--', '.')
68                 _git_apply(patch_filename)
69             logger.info('Restored changes from {}.'.format(patch_filename))
70     else:
71         # There weren't any staged files so we don't need to do anything
72         # special
73         yield
74
```
--- 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/git.py b/pre_commit/git.py
--- a/pre_commit/git.py
+++ b/pre_commit/git.py
@@ -97,6 +97,20 @@
     )[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
--- a/pre_commit/staged_files_only.py
+++ b/pre_commit/staged_files_only.py
@@ -6,9 +6,11 @@
 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 @@
 
 
 @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 @@
         # 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

{"golden_diff": "diff --git a/pre_commit/git.py b/pre_commit/git.py\n--- a/pre_commit/git.py\n+++ b/pre_commit/git.py\n@@ -97,6 +97,20 @@\n     )[1])\n \n \n+def intent_to_add_files():\n+    _, stdout_binary, _ = cmd_output('git', 'status', '--porcelain', '-z')\n+    parts = list(reversed(zsplit(stdout_binary)))\n+    intent_to_add = []\n+    while parts:\n+        line = parts.pop()\n+        status, filename = line[:3], line[3:]\n+        if status[0] in {'C', 'R'}:  # renames / moves have an additional arg\n+            parts.pop()\n+        if status[1] == 'A':\n+            intent_to_add.append(filename)\n+    return intent_to_add\n+\n+\n def get_all_files():\n     return zsplit(cmd_output('git', 'ls-files', '-z')[1])\n \ndiff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py\n--- a/pre_commit/staged_files_only.py\n+++ b/pre_commit/staged_files_only.py\n@@ -6,9 +6,11 @@\n import os.path\n import time\n \n+from pre_commit import git\n from pre_commit.util import CalledProcessError\n from pre_commit.util import cmd_output\n from pre_commit.util import mkdirp\n+from pre_commit.xargs import xargs\n \n \n logger = logging.getLogger('pre_commit')\n@@ -24,11 +26,22 @@\n \n \n @contextlib.contextmanager\n-def staged_files_only(patch_dir):\n-    \"\"\"Clear any unstaged changes from the git working directory inside this\n-    context.\n-    \"\"\"\n-    # Determine if there are unstaged files\n+def _intent_to_add_cleared():\n+    intent_to_add = git.intent_to_add_files()\n+    if intent_to_add:\n+        logger.warning('Unstaged intent-to-add files detected.')\n+\n+        xargs(('git', 'rm', '--cached', '--'), intent_to_add)\n+        try:\n+            yield\n+        finally:\n+            xargs(('git', 'add', '--intent-to-add', '--'), intent_to_add)\n+    else:\n+        yield\n+\n+\n+@contextlib.contextmanager\n+def _unstaged_changes_cleared(patch_dir):\n     tree = cmd_output('git', 'write-tree')[1].strip()\n     retcode, diff_stdout_binary, _ = cmd_output(\n         'git', 'diff-index', '--ignore-submodules', '--binary',\n@@ -71,3 +84,12 @@\n         # There weren't any staged files so we don't need to do anything\n         # special\n         yield\n+\n+\n+@contextlib.contextmanager\n+def staged_files_only(patch_dir):\n+    \"\"\"Clear any unstaged changes from the git working directory inside this\n+    context.\n+    \"\"\"\n+    with _intent_to_add_cleared(), _unstaged_changes_cleared(patch_dir):\n+        yield\n", "issue": "\"git add -N\" prevents restoring stashed changes.\nTo reproduce, start with this simple `pre-commit-config.yaml` in an otherwise empty repo:\r\n\r\n```yaml\r\nrepos:\r\n- repo: https://github.com/pre-commit/pre-commit-hooks\r\n  rev: master\r\n  hooks: \r\n    - id: end-of-file-fixer\r\n```\r\n\r\nThe hook used doesn't really matter. end-of-file-fixer is just an example. \r\n\r\nRun the following:\r\n```bash\r\necho \"new\" > newfile\r\necho \"\\n\\n\\n\" > needs-fixing\r\ngit add -N newfile\r\n# newfile is now staged as an empty file\r\ngit add needs-fixing\r\ngit commit -m \"fix\"\r\n```\r\n\r\nThe following output is generated: \r\n\r\n```\r\n[WARNING] Unstaged files detected.\r\n[INFO] Stashing unstaged files to /home/henniss/.cache/pre-commit/patch1544663784.\r\nFix End of Files.........................................................Failed\r\nhookid: end-of-file-fixer\r\n\r\nFiles were modified by this hook. Additional output:\r\n\r\nFixing needs-fixing\r\n\r\n[WARNING] Stashed changes conflicted with hook auto-fixes... Rolling back fixes...\r\nAn unexpected error has occurred: CalledProcessError: Command: ('/usr/lib/git-core/git', '-c', 'core.autocrlf=false', 'apply', '--whitespace=nowarn', '/home/henniss/.cache/pre-commit/patch1544663784')\r\nReturn code: 1\r\nExpected return code: 0\r\nOutput: (none)\r\nErrors:\r\n    error: newfile: already exists in working directory\r\n\r\n\r\nCheck the log at /home/henniss/.cache/pre-commit/pre-commit.log\r\n```\r\n\r\n`cat newfile` now shows that it is empty. The unstaged changes aren't restored.\n", "before_files": [{"content": "from __future__ import unicode_literals\n\nimport logging\nimport os.path\nimport sys\n\nfrom pre_commit.util import cmd_output\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef zsplit(s):\n    s = s.strip('\\0')\n    if s:\n        return s.split('\\0')\n    else:\n        return []\n\n\ndef no_git_env():\n    # Too many bugs dealing with environment variables and GIT:\n    # https://github.com/pre-commit/pre-commit/issues/300\n    # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running\n    # pre-commit hooks\n    # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE\n    # while running pre-commit hooks in submodules.\n    # GIT_DIR: Causes git clone to clone wrong thing\n    # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit\n    return {\n        k: v for k, v in os.environ.items()\n        if not k.startswith('GIT_') or k in {'GIT_SSH'}\n    }\n\n\ndef get_root():\n    return cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip()\n\n\ndef get_git_dir(git_root='.'):\n    opts = ('--git-common-dir', '--git-dir')\n    _, out, _ = cmd_output('git', 'rev-parse', *opts, cwd=git_root)\n    for line, opt in zip(out.splitlines(), opts):\n        if line != opt:  # pragma: no branch (git < 2.5)\n            return os.path.normpath(os.path.join(git_root, line))\n    else:\n        raise AssertionError('unreachable: no git dir')\n\n\ndef get_remote_url(git_root):\n    ret = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)[1]\n    return ret.strip()\n\n\ndef is_in_merge_conflict():\n    git_dir = get_git_dir('.')\n    return (\n        os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and\n        os.path.exists(os.path.join(git_dir, 'MERGE_HEAD'))\n    )\n\n\ndef parse_merge_msg_for_conflicts(merge_msg):\n    # Conflicted files start with tabs\n    return [\n        line.lstrip(b'#').strip().decode('UTF-8')\n        for line in merge_msg.splitlines()\n        # '#\\t' for git 2.4.1\n        if line.startswith((b'\\t', b'#\\t'))\n    ]\n\n\ndef get_conflicted_files():\n    logger.info('Checking merge-conflict files only.')\n    # Need to get the conflicted files from the MERGE_MSG because they could\n    # have resolved the conflict by choosing one side or the other\n    with open(os.path.join(get_git_dir('.'), 'MERGE_MSG'), 'rb') as f:\n        merge_msg = f.read()\n    merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)\n\n    # This will get the rest of the changes made after the merge.\n    # If they resolved the merge conflict by choosing a mesh of both sides\n    # this will also include the conflicted files\n    tree_hash = cmd_output('git', 'write-tree')[1].strip()\n    merge_diff_filenames = zsplit(cmd_output(\n        'git', 'diff', '--name-only', '--no-ext-diff', '-z',\n        '-m', tree_hash, 'HEAD', 'MERGE_HEAD',\n    )[1])\n    return set(merge_conflict_filenames) | set(merge_diff_filenames)\n\n\ndef get_staged_files():\n    return zsplit(cmd_output(\n        'git', 'diff', '--staged', '--name-only', '--no-ext-diff', '-z',\n        # Everything except for D\n        '--diff-filter=ACMRTUXB',\n    )[1])\n\n\ndef get_all_files():\n    return zsplit(cmd_output('git', 'ls-files', '-z')[1])\n\n\ndef get_changed_files(new, old):\n    return zsplit(cmd_output(\n        'git', 'diff', '--name-only', '--no-ext-diff', '-z',\n        '{}...{}'.format(old, new),\n    )[1])\n\n\ndef head_rev(remote):\n    _, out, _ = cmd_output('git', 'ls-remote', '--exit-code', remote, 'HEAD')\n    return out.split()[0]\n\n\ndef has_diff(*args, **kwargs):\n    repo = kwargs.pop('repo', '.')\n    assert not kwargs, kwargs\n    cmd = ('git', 'diff', '--quiet', '--no-ext-diff') + args\n    return cmd_output(*cmd, cwd=repo, retcode=None)[0]\n\n\ndef commit(repo='.'):\n    env = no_git_env()\n    name, email = 'pre-commit', 'asottile+pre-commit@umich.edu'\n    env['GIT_AUTHOR_NAME'] = env['GIT_COMMITTER_NAME'] = name\n    env['GIT_AUTHOR_EMAIL'] = env['GIT_COMMITTER_EMAIL'] = email\n    cmd = ('git', 'commit', '--no-edit', '--no-gpg-sign', '-n', '-minit')\n    cmd_output(*cmd, cwd=repo, env=env)\n\n\ndef git_path(name, repo='.'):\n    _, out, _ = cmd_output('git', 'rev-parse', '--git-path', name, cwd=repo)\n    return os.path.join(repo, out.strip())\n\n\ndef check_for_cygwin_mismatch():\n    \"\"\"See https://github.com/pre-commit/pre-commit/issues/354\"\"\"\n    if sys.platform in ('cygwin', 'win32'):  # pragma: no cover (windows)\n        is_cygwin_python = sys.platform == 'cygwin'\n        toplevel = cmd_output('git', 'rev-parse', '--show-toplevel')[1]\n        is_cygwin_git = toplevel.startswith('/')\n\n        if is_cygwin_python ^ is_cygwin_git:\n            exe_type = {True: '(cygwin)', False: '(windows)'}\n            logger.warn(\n                'pre-commit has detected a mix of cygwin python / git\\n'\n                'This combination is not supported, it is likely you will '\n                'receive an error later in the program.\\n'\n                'Make sure to use cygwin git+python while using cygwin\\n'\n                'These can be installed through the cygwin installer.\\n'\n                ' - python {}\\n'\n                ' - git {}\\n'.format(\n                    exe_type[is_cygwin_python], exe_type[is_cygwin_git],\n                ),\n            )\n", "path": "pre_commit/git.py"}, {"content": "from __future__ import unicode_literals\n\nimport contextlib\nimport io\nimport logging\nimport os.path\nimport time\n\nfrom pre_commit.util import CalledProcessError\nfrom pre_commit.util import cmd_output\nfrom pre_commit.util import mkdirp\n\n\nlogger = logging.getLogger('pre_commit')\n\n\ndef _git_apply(patch):\n    args = ('apply', '--whitespace=nowarn', patch)\n    try:\n        cmd_output('git', *args, encoding=None)\n    except CalledProcessError:\n        # Retry with autocrlf=false -- see #570\n        cmd_output('git', '-c', 'core.autocrlf=false', *args, encoding=None)\n\n\n@contextlib.contextmanager\ndef staged_files_only(patch_dir):\n    \"\"\"Clear any unstaged changes from the git working directory inside this\n    context.\n    \"\"\"\n    # Determine if there are unstaged files\n    tree = cmd_output('git', 'write-tree')[1].strip()\n    retcode, diff_stdout_binary, _ = cmd_output(\n        'git', 'diff-index', '--ignore-submodules', '--binary',\n        '--exit-code', '--no-color', '--no-ext-diff', tree, '--',\n        retcode=None,\n        encoding=None,\n    )\n    if retcode and diff_stdout_binary.strip():\n        patch_filename = 'patch{}'.format(int(time.time()))\n        patch_filename = os.path.join(patch_dir, patch_filename)\n        logger.warning('Unstaged files detected.')\n        logger.info(\n            'Stashing unstaged files to {}.'.format(patch_filename),\n        )\n        # Save the current unstaged changes as a patch\n        mkdirp(patch_dir)\n        with io.open(patch_filename, 'wb') as patch_file:\n            patch_file.write(diff_stdout_binary)\n\n        # Clear the working directory of unstaged changes\n        cmd_output('git', 'checkout', '--', '.')\n        try:\n            yield\n        finally:\n            # Try to apply the patch we saved\n            try:\n                _git_apply(patch_filename)\n            except CalledProcessError:\n                logger.warning(\n                    'Stashed changes conflicted with hook auto-fixes... '\n                    'Rolling back fixes...',\n                )\n                # We failed to apply the patch, presumably due to fixes made\n                # by hooks.\n                # Roll back the changes made by hooks.\n                cmd_output('git', 'checkout', '--', '.')\n                _git_apply(patch_filename)\n            logger.info('Restored changes from {}.'.format(patch_filename))\n    else:\n        # There weren't any staged files so we don't need to do anything\n        # special\n        yield\n", "path": "pre_commit/staged_files_only.py"}], "after_files": [{"content": "from __future__ import unicode_literals\n\nimport logging\nimport os.path\nimport sys\n\nfrom pre_commit.util import cmd_output\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef zsplit(s):\n    s = s.strip('\\0')\n    if s:\n        return s.split('\\0')\n    else:\n        return []\n\n\ndef no_git_env():\n    # Too many bugs dealing with environment variables and GIT:\n    # https://github.com/pre-commit/pre-commit/issues/300\n    # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running\n    # pre-commit hooks\n    # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE\n    # while running pre-commit hooks in submodules.\n    # GIT_DIR: Causes git clone to clone wrong thing\n    # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit\n    return {\n        k: v for k, v in os.environ.items()\n        if not k.startswith('GIT_') or k in {'GIT_SSH'}\n    }\n\n\ndef get_root():\n    return cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip()\n\n\ndef get_git_dir(git_root='.'):\n    opts = ('--git-common-dir', '--git-dir')\n    _, out, _ = cmd_output('git', 'rev-parse', *opts, cwd=git_root)\n    for line, opt in zip(out.splitlines(), opts):\n        if line != opt:  # pragma: no branch (git < 2.5)\n            return os.path.normpath(os.path.join(git_root, line))\n    else:\n        raise AssertionError('unreachable: no git dir')\n\n\ndef get_remote_url(git_root):\n    ret = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)[1]\n    return ret.strip()\n\n\ndef is_in_merge_conflict():\n    git_dir = get_git_dir('.')\n    return (\n        os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and\n        os.path.exists(os.path.join(git_dir, 'MERGE_HEAD'))\n    )\n\n\ndef parse_merge_msg_for_conflicts(merge_msg):\n    # Conflicted files start with tabs\n    return [\n        line.lstrip(b'#').strip().decode('UTF-8')\n        for line in merge_msg.splitlines()\n        # '#\\t' for git 2.4.1\n        if line.startswith((b'\\t', b'#\\t'))\n    ]\n\n\ndef get_conflicted_files():\n    logger.info('Checking merge-conflict files only.')\n    # Need to get the conflicted files from the MERGE_MSG because they could\n    # have resolved the conflict by choosing one side or the other\n    with open(os.path.join(get_git_dir('.'), 'MERGE_MSG'), 'rb') as f:\n        merge_msg = f.read()\n    merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)\n\n    # This will get the rest of the changes made after the merge.\n    # If they resolved the merge conflict by choosing a mesh of both sides\n    # this will also include the conflicted files\n    tree_hash = cmd_output('git', 'write-tree')[1].strip()\n    merge_diff_filenames = zsplit(cmd_output(\n        'git', 'diff', '--name-only', '--no-ext-diff', '-z',\n        '-m', tree_hash, 'HEAD', 'MERGE_HEAD',\n    )[1])\n    return set(merge_conflict_filenames) | set(merge_diff_filenames)\n\n\ndef get_staged_files():\n    return zsplit(cmd_output(\n        'git', 'diff', '--staged', '--name-only', '--no-ext-diff', '-z',\n        # Everything except for D\n        '--diff-filter=ACMRTUXB',\n    )[1])\n\n\ndef intent_to_add_files():\n    _, stdout_binary, _ = cmd_output('git', 'status', '--porcelain', '-z')\n    parts = list(reversed(zsplit(stdout_binary)))\n    intent_to_add = []\n    while parts:\n        line = parts.pop()\n        status, filename = line[:3], line[3:]\n        if status[0] in {'C', 'R'}:  # renames / moves have an additional arg\n            parts.pop()\n        if status[1] == 'A':\n            intent_to_add.append(filename)\n    return intent_to_add\n\n\ndef get_all_files():\n    return zsplit(cmd_output('git', 'ls-files', '-z')[1])\n\n\ndef get_changed_files(new, old):\n    return zsplit(cmd_output(\n        'git', 'diff', '--name-only', '--no-ext-diff', '-z',\n        '{}...{}'.format(old, new),\n    )[1])\n\n\ndef head_rev(remote):\n    _, out, _ = cmd_output('git', 'ls-remote', '--exit-code', remote, 'HEAD')\n    return out.split()[0]\n\n\ndef has_diff(*args, **kwargs):\n    repo = kwargs.pop('repo', '.')\n    assert not kwargs, kwargs\n    cmd = ('git', 'diff', '--quiet', '--no-ext-diff') + args\n    return cmd_output(*cmd, cwd=repo, retcode=None)[0]\n\n\ndef commit(repo='.'):\n    env = no_git_env()\n    name, email = 'pre-commit', 'asottile+pre-commit@umich.edu'\n    env['GIT_AUTHOR_NAME'] = env['GIT_COMMITTER_NAME'] = name\n    env['GIT_AUTHOR_EMAIL'] = env['GIT_COMMITTER_EMAIL'] = email\n    cmd = ('git', 'commit', '--no-edit', '--no-gpg-sign', '-n', '-minit')\n    cmd_output(*cmd, cwd=repo, env=env)\n\n\ndef git_path(name, repo='.'):\n    _, out, _ = cmd_output('git', 'rev-parse', '--git-path', name, cwd=repo)\n    return os.path.join(repo, out.strip())\n\n\ndef check_for_cygwin_mismatch():\n    \"\"\"See https://github.com/pre-commit/pre-commit/issues/354\"\"\"\n    if sys.platform in ('cygwin', 'win32'):  # pragma: no cover (windows)\n        is_cygwin_python = sys.platform == 'cygwin'\n        toplevel = cmd_output('git', 'rev-parse', '--show-toplevel')[1]\n        is_cygwin_git = toplevel.startswith('/')\n\n        if is_cygwin_python ^ is_cygwin_git:\n            exe_type = {True: '(cygwin)', False: '(windows)'}\n            logger.warn(\n                'pre-commit has detected a mix of cygwin python / git\\n'\n                'This combination is not supported, it is likely you will '\n                'receive an error later in the program.\\n'\n                'Make sure to use cygwin git+python while using cygwin\\n'\n                'These can be installed through the cygwin installer.\\n'\n                ' - python {}\\n'\n                ' - git {}\\n'.format(\n                    exe_type[is_cygwin_python], exe_type[is_cygwin_git],\n                ),\n            )\n", "path": "pre_commit/git.py"}, {"content": "from __future__ import unicode_literals\n\nimport contextlib\nimport io\nimport logging\nimport os.path\nimport time\n\nfrom pre_commit import git\nfrom pre_commit.util import CalledProcessError\nfrom pre_commit.util import cmd_output\nfrom pre_commit.util import mkdirp\nfrom pre_commit.xargs import xargs\n\n\nlogger = logging.getLogger('pre_commit')\n\n\ndef _git_apply(patch):\n    args = ('apply', '--whitespace=nowarn', patch)\n    try:\n        cmd_output('git', *args, encoding=None)\n    except CalledProcessError:\n        # Retry with autocrlf=false -- see #570\n        cmd_output('git', '-c', 'core.autocrlf=false', *args, encoding=None)\n\n\n@contextlib.contextmanager\ndef _intent_to_add_cleared():\n    intent_to_add = git.intent_to_add_files()\n    if intent_to_add:\n        logger.warning('Unstaged intent-to-add files detected.')\n\n        xargs(('git', 'rm', '--cached', '--'), intent_to_add)\n        try:\n            yield\n        finally:\n            xargs(('git', 'add', '--intent-to-add', '--'), intent_to_add)\n    else:\n        yield\n\n\n@contextlib.contextmanager\ndef _unstaged_changes_cleared(patch_dir):\n    tree = cmd_output('git', 'write-tree')[1].strip()\n    retcode, diff_stdout_binary, _ = cmd_output(\n        'git', 'diff-index', '--ignore-submodules', '--binary',\n        '--exit-code', '--no-color', '--no-ext-diff', tree, '--',\n        retcode=None,\n        encoding=None,\n    )\n    if retcode and diff_stdout_binary.strip():\n        patch_filename = 'patch{}'.format(int(time.time()))\n        patch_filename = os.path.join(patch_dir, patch_filename)\n        logger.warning('Unstaged files detected.')\n        logger.info(\n            'Stashing unstaged files to {}.'.format(patch_filename),\n        )\n        # Save the current unstaged changes as a patch\n        mkdirp(patch_dir)\n        with io.open(patch_filename, 'wb') as patch_file:\n            patch_file.write(diff_stdout_binary)\n\n        # Clear the working directory of unstaged changes\n        cmd_output('git', 'checkout', '--', '.')\n        try:\n            yield\n        finally:\n            # Try to apply the patch we saved\n            try:\n                _git_apply(patch_filename)\n            except CalledProcessError:\n                logger.warning(\n                    'Stashed changes conflicted with hook auto-fixes... '\n                    'Rolling back fixes...',\n                )\n                # We failed to apply the patch, presumably due to fixes made\n                # by hooks.\n                # Roll back the changes made by hooks.\n                cmd_output('git', 'checkout', '--', '.')\n                _git_apply(patch_filename)\n            logger.info('Restored changes from {}.'.format(patch_filename))\n    else:\n        # There weren't any staged files so we don't need to do anything\n        # special\n        yield\n\n\n@contextlib.contextmanager\ndef staged_files_only(patch_dir):\n    \"\"\"Clear any unstaged changes from the git working directory inside this\n    context.\n    \"\"\"\n    with _intent_to_add_cleared(), _unstaged_changes_cleared(patch_dir):\n        yield\n", "path": "pre_commit/staged_files_only.py"}]}