Repository: pre-commit/pre-commit

Issue:
Incorrect shebang in .git/hooks/pre-commit for python3 only installations
The shebang for `.git/hooks/pre-commit` is `#!/usr/bin/env python`. I work with setups where `python3` is the only python in env.

Could the shebang be the install python instead? I.e. the installation under `INSTALL_PYTHON = '/usr/bin/python3'`


Discussion:
hmm! that's a good point I hadn't thought of but presents some issues I can think of:

- windows (official python installs) always use `python` and not `python3`
- `INSTALL_PYTHON` might be ephemeral, it's why there's three options for [detecting `pre-commit` in the script](https://github.com/pre-commit/pre-commit/blob/51659ee606c5207b30ce863086b2a05f572ad245/pre_commit/resources/hook-tmpl#L93-L104)

that said, I think we can get this right, probably by inspecting `sys.executable` and determining what possible values could be used.


Maybe something like this?

```python
if sys.platform != 'win32' and six.PY3:
    return '#!/usr/bin/env python3'
else:
    return '#!/usr/bin/env python'
```
> Maybe something like this?
> 
> ```python
> if sys.platform != 'win32' and six.PY3:
>     return '#!/usr/bin/env python3'
> else:
>     return '#!/usr/bin/env python'
> ```

Thanks for the quick reply! I believe that would solve my problem.
hmmm I just thought of another case while working on this that might not work.  Some distributions (for instance `arch`) install python 3.x as `/usr/bin/python` and won't (?) necessarily have a `/usr/bin/python3` executable

I'll have to think about this some more -- maybe inspecting `sys.executable` is necessary.  Maybe something like [this](https://github.com/pre-commit/pre-commit/blob/51659ee606c5207b30ce863086b2a05f572ad245/pre_commit/languages/python_venv.py#L21)

Resolving patch:
diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py
--- a/pre_commit/commands/install_uninstall.py
+++ b/pre_commit/commands/install_uninstall.py
@@ -8,6 +8,7 @@
 
 from pre_commit import git
 from pre_commit import output
+from pre_commit.languages import python
 from pre_commit.repository import repositories
 from pre_commit.util import cmd_output
 from pre_commit.util import make_executable
@@ -43,6 +44,16 @@ def is_our_script(filename):
     return any(h in contents for h in (CURRENT_HASH,) + PRIOR_HASHES)
 
 
+def shebang():
+    if sys.platform == 'win32':
+        py = 'python'
+    else:
+        py = python.get_default_version()
+        if py == 'default':
+            py = 'python'
+    return '#!/usr/bin/env {}'.format(py)
+
+
 def install(
         runner, store, overwrite=False, hooks=False, hook_type='pre-commit',
         skip_on_missing_conf=False,
@@ -84,6 +95,8 @@ def install(
         before, rest = contents.split(TEMPLATE_START)
         to_template, after = rest.split(TEMPLATE_END)
 
+        before = before.replace('#!/usr/bin/env python', shebang())
+
         hook_file.write(before + TEMPLATE_START)
         for line in to_template.splitlines():
             var = line.split()[0]


Test patch:
diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py
--- a/tests/commands/install_uninstall_test.py
+++ b/tests/commands/install_uninstall_test.py
@@ -17,7 +17,9 @@
 from pre_commit.commands.install_uninstall import install_hooks
 from pre_commit.commands.install_uninstall import is_our_script
 from pre_commit.commands.install_uninstall import PRIOR_HASHES
+from pre_commit.commands.install_uninstall import shebang
 from pre_commit.commands.install_uninstall import uninstall
+from pre_commit.languages import python
 from pre_commit.runner import Runner
 from pre_commit.util import cmd_output
 from pre_commit.util import make_executable
@@ -45,6 +47,24 @@ def test_is_previous_pre_commit(tmpdir):
     assert is_our_script(f.strpath)
 
 
+def test_shebang_windows():
+    with mock.patch.object(sys, 'platform', 'win32'):
+        assert shebang() == '#!/usr/bin/env python'
+
+
+def test_shebang_otherwise():
+    with mock.patch.object(sys, 'platform', 'posix'):
+        assert 'default' not in shebang()
+
+
+def test_shebang_returns_default():
+    with mock.patch.object(sys, 'platform', 'posix'):
+        with mock.patch.object(
+            python, 'get_default_version', return_value='default',
+        ):
+            assert shebang() == '#!/usr/bin/env python'
+
+
 def test_install_pre_commit(tempdir_factory, store):
     path = git_dir(tempdir_factory)
     runner = Runner(path, C.CONFIG_FILE)