Repository: pre-commit/pre-commit

Issue:
Choose python more intelligently in the file installed to .git/hooks/pre-commit
Since we know which python we're running as when `pre-commit install` is run (`sys.executable`), let's put that into `.git/hooks/pre-commit` and try that python first.

This will involve bumping the magic number in the resource file, and probably updating the logic to check for the same sys.executable existing inside that file.



Discussion:

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
@@ -6,6 +6,7 @@
 import os
 import os.path
 import stat
+import sys
 
 from pre_commit.logging_handler import LoggingHandler
 from pre_commit.util import resource_filename
@@ -15,12 +16,13 @@
 
 
 # This is used to identify the hook file we install
-PREVIOUS_IDENTIFYING_HASHES = [
+PREVIOUS_IDENTIFYING_HASHES = (
+    '4d9958c90bc262f47553e2c073f14cfe',
     'd8ee923c46731b42cd95cc869add4062',
-]
+)
 
 
-IDENTIFYING_HASH = '4d9958c90bc262f47553e2c073f14cfe'
+IDENTIFYING_HASH = '49fd668cb42069aa1b6048464be5d395'
 
 
 def is_our_pre_commit(filename):
@@ -63,8 +65,11 @@ def install(runner, overwrite=False, hooks=False):
             )
         )
 
-    with open(runner.pre_commit_path, 'w') as pre_commit_file_obj:
-        pre_commit_file_obj.write(open(pre_commit_file).read())
+    with io.open(runner.pre_commit_path, 'w') as pre_commit_file_obj:
+        contents = io.open(pre_commit_file).read().format(
+            sys_executable=sys.executable,
+        )
+        pre_commit_file_obj.write(contents)
     make_executable(runner.pre_commit_path)
 
     print('pre-commit installed at {0}'.format(runner.pre_commit_path))
diff --git a/pre_commit/output.py b/pre_commit/output.py
--- a/pre_commit/output.py
+++ b/pre_commit/output.py
@@ -1,5 +1,6 @@
 from __future__ import unicode_literals
 
+import os
 import subprocess
 import sys
 
@@ -10,7 +11,7 @@
 # TODO: smell: import side-effects
 COLS = int(
     subprocess.Popen(
-        ['tput', 'cols'], stdout=subprocess.PIPE
+        ['tput', 'cols'], stdout=subprocess.PIPE, stderr=open(os.devnull, 'w'),
     ).communicate()[0] or
     # Default in the case of no terminal
     80


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
@@ -2,11 +2,13 @@
 from __future__ import unicode_literals
 
 import io
+import mock
 import os
 import os.path
 import re
 import subprocess
 import stat
+import sys
 from plumbum import local
 
 from pre_commit.commands.install_uninstall import IDENTIFYING_HASH
@@ -53,7 +55,9 @@ def test_install_pre_commit(tmpdir_factory):
     assert os.path.exists(runner.pre_commit_path)
     pre_commit_contents = io.open(runner.pre_commit_path).read()
     pre_commit_script = resource_filename('pre-commit-hook')
-    expected_contents = io.open(pre_commit_script).read()
+    expected_contents = io.open(pre_commit_script).read().format(
+        sys_executable=sys.executable,
+    )
     assert pre_commit_contents == expected_contents
     stat_result = os.stat(runner.pre_commit_path)
     assert stat_result.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
@@ -76,12 +80,17 @@ def test_uninstall(tmpdir_factory):
     assert not os.path.exists(runner.pre_commit_path)
 
 
-def _get_commit_output(tmpdir_factory, touch_file='foo', home=None):
+def _get_commit_output(
+        tmpdir_factory,
+        touch_file='foo',
+        home=None,
+        env_base=os.environ,
+):
     local['touch'](touch_file)
     local['git']('add', touch_file)
     # Don't want to write to home directory
     home = home or tmpdir_factory.get()
-    env = dict(os.environ, **{'PRE_COMMIT_HOME': home})
+    env = dict(env_base, **{'PRE_COMMIT_HOME': home})
     return local['git'].run(
         ['commit', '-m', 'Commit!', '--allow-empty'],
         # git commit puts pre-commit to stderr
@@ -136,11 +145,12 @@ def test_install_idempotent(tmpdir_factory):
 def test_environment_not_sourced(tmpdir_factory):
     path = make_consuming_repo(tmpdir_factory, 'script_hooks_repo')
     with local.cwd(path):
-        assert install(Runner(path)) == 0
+        # Patch the executable to simulate rming virtualenv
+        with mock.patch.object(sys, 'executable', '/bin/false'):
+            assert install(Runner(path)) == 0
 
         ret, stdout, stderr = local['git'].run(
             ['commit', '--allow-empty', '-m', 'foo'],
-            # XXX: 'HOME' makes this test pass on OSX
             env={'HOME': os.environ['HOME']},
             retcode=None,
         )
@@ -362,3 +372,17 @@ def test_installs_hooks_with_hooks_True(
 
         assert ret == 0
         assert PRE_INSTALLED.match(output)
+
+
+def test_installed_from_venv(tmpdir_factory):
+    path = make_consuming_repo(tmpdir_factory, 'script_hooks_repo')
+    with local.cwd(path):
+        install(Runner(path))
+        # No environment so pre-commit is not on the path when running!
+        # Should still pick up the python from when we installed
+        ret, output = _get_commit_output(
+            tmpdir_factory,
+            env_base={'HOME': os.environ['HOME']},
+        )
+        assert ret == 0
+        assert NORMAL_PRE_COMMIT_RUN.match(output)