From 598e54640bc0036e05f6de7aeb04fbe937cc4213 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 4 Sep 2014 08:44:07 -0700 Subject: [PATCH] Use virtualenv python from install-time for less virtualenv requirements at commit time. --- pre_commit/commands/install_uninstall.py | 15 +++++++---- pre_commit/output.py | 3 ++- pre_commit/resources/pre-commit-hook | 20 +++++++++++--- tests/commands/install_uninstall_test.py | 34 ++++++++++++++++++++---- 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py index 6b75321a..1c23739d 100644 --- a/pre_commit/commands/install_uninstall.py +++ b/pre_commit/commands/install_uninstall.py @@ -6,6 +6,7 @@ import logging 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 @@ logger = logging.getLogger('pre_commit') # 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 index 8e41be90..dcf4c5cc 100644 --- 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 @@ from pre_commit import five # 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 diff --git a/pre_commit/resources/pre-commit-hook b/pre_commit/resources/pre-commit-hook index bdb1431d..b004d29a 100755 --- a/pre_commit/resources/pre-commit-hook +++ b/pre_commit/resources/pre-commit-hook @@ -1,6 +1,6 @@ #!/usr/bin/env bash # This is a randomish md5 to identify this script -# 4d9958c90bc262f47553e2c073f14cfe +# 49fd668cb42069aa1b6048464be5d395 pushd `dirname $0` > /dev/null HERE=`pwd` @@ -8,12 +8,21 @@ popd > /dev/null retv=0 +ENV_PYTHON='{sys_executable}' + which pre-commit >& /dev/null WHICH_RETV=$? +"$ENV_PYTHON" -c 'import pre_commit.main' >& /dev/null +ENV_PYTHON_RETV=$? python -c 'import pre_commit.main' >& /dev/null PYTHON_RETV=$? -if [ $WHICH_RETV -ne 0 ] && [ $PYTHON_RETV -ne 0 ]; then + +if (( + (WHICH_RETV != 0) && + (ENV_PYTHON_RETV != 0) && + (PYTHON_RETV != 0) +)); then echo '`pre-commit` not found. Did you forget to activate your virtualenv?' exit 1 fi @@ -29,15 +38,18 @@ fi # Run pre-commit -if [ $WHICH_RETV -eq 0 ]; then +if ((WHICH_RETV == 0)); then pre-commit PRE_COMMIT_RETV=$? +elif ((ENV_PYTHON_RETV == 0)); then + "$ENV_PYTHON" -m pre_commit.main + PRE_COMMIT_RETV=$? else python -m pre_commit.main PRE_COMMIT_RETV=$? fi -if [ $PRE_COMMIT_RETV -ne 0 ]; then +if ((PRE_COMMIT_RETV != 0)); then retv=1 fi diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index a1c29da8..c9c89c53 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -2,11 +2,13 @@ from __future__ import absolute_import 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)