diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py index 4698ee1e..249c87d0 100644 --- a/pre_commit/commands/install_uninstall.py +++ b/pre_commit/commands/install_uninstall.py @@ -9,13 +9,23 @@ import stat # This is used to identify the hook file we install -IDENTIFYING_HASH = 'd8ee923c46731b42cd95cc869add4062' +PREVIOUS_IDENTIFYING_HASHES = [ + 'd8ee923c46731b42cd95cc869add4062', +] + + +IDENTIFYING_HASH = '4d9958c90bc262f47553e2c073f14cfe' def is_our_pre_commit(filename): return IDENTIFYING_HASH in io.open(filename).read() +def is_previous_pre_commit(filename): + contents = io.open(filename).read() + return any(hash in contents for hash in PREVIOUS_IDENTIFYING_HASHES) + + def make_executable(filename): original_mode = os.stat(filename).st_mode os.chmod( @@ -33,7 +43,8 @@ def install(runner, overwrite=False): # If we have an existing hook, move it to pre-commit.legacy if ( os.path.exists(runner.pre_commit_path) and - not is_our_pre_commit(runner.pre_commit_path) + not is_our_pre_commit(runner.pre_commit_path) and + not is_previous_pre_commit(runner.pre_commit_path) ): os.rename(runner.pre_commit_path, runner.pre_commit_legacy_path) @@ -58,9 +69,17 @@ def install(runner, overwrite=False): def uninstall(runner): """Uninstall the pre-commit hooks.""" - if os.path.exists(runner.pre_commit_path): - os.remove(runner.pre_commit_path) - print('pre-commit uninstalled') + # If our file doesn't exist or it isn't ours, gtfo. + if ( + not os.path.exists(runner.pre_commit_path) or ( + not is_our_pre_commit(runner.pre_commit_path) and + not is_previous_pre_commit(runner.pre_commit_path) + ) + ): + return 0 + + os.remove(runner.pre_commit_path) + print('pre-commit uninstalled') if os.path.exists(runner.pre_commit_legacy_path): os.rename(runner.pre_commit_legacy_path, runner.pre_commit_path) diff --git a/pre_commit/resources/pre-commit-hook b/pre_commit/resources/pre-commit-hook index 60c620cf..3ddf8943 100755 --- a/pre_commit/resources/pre-commit-hook +++ b/pre_commit/resources/pre-commit-hook @@ -1,13 +1,17 @@ #!/usr/bin/env bash # This is a randomish md5 to identify this script -# d8ee923c46731b42cd95cc869add4062 +# 4d9958c90bc262f47553e2c073f14cfe HERE=$(dirname $(readlink -f "$0")) retv=0 -which pre-commit > /dev/null -if [ $? -ne 0 ]; then +which pre-commit >& /dev/null +WHICH_RETV=$? +python -c 'import pre_commit.main' >& /dev/null +PYTHON_RETV=$? + +if [ $WHICH_RETV -ne 0 ] && [ $PYTHON_RETV -ne 0 ]; then echo '`pre-commit` not found. Did you forget to activate your virtualenv?' exit 1 fi @@ -23,8 +27,15 @@ fi # Run pre-commit -pre-commit -if [ $? -ne 0 ]; then +if [ $WHICH_RETV -eq 0 ]; then + pre-commit + PRE_COMMIT_RETV=$? +else + python -m pre_commit.main + PRE_COMMIT_RETV=$? +fi + +if [ $PRE_COMMIT_RETV -ne 0 ]; then retv=1 fi diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index 4832afbb..14fafa1a 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -10,8 +10,11 @@ import subprocess import stat from plumbum import local +from pre_commit.commands.install_uninstall import IDENTIFYING_HASH +from pre_commit.commands.install_uninstall import PREVIOUS_IDENTIFYING_HASHES from pre_commit.commands.install_uninstall import install from pre_commit.commands.install_uninstall import is_our_pre_commit +from pre_commit.commands.install_uninstall import is_previous_pre_commit from pre_commit.commands.install_uninstall import make_executable from pre_commit.commands.install_uninstall import uninstall from pre_commit.runner import Runner @@ -31,6 +34,25 @@ def test_is_our_pre_commit(): ) is True +def test_is_not_previous_pre_commit(): + assert is_previous_pre_commit('setup.py') is False + + +def test_is_also_not_previous_pre_commit(): + assert is_previous_pre_commit( + pkg_resources.resource_filename( + 'pre_commit', 'resources/pre-commit-hook', + ) + ) is False + + +def test_is_previous_pre_commit(in_tmpdir): + with io.open('foo', 'w') as foo_file: + foo_file.write(PREVIOUS_IDENTIFYING_HASHES[0]) + + assert is_previous_pre_commit('foo') + + def test_install_pre_commit(tmpdir_factory): path = git_dir(tmpdir_factory) runner = Runner(path) @@ -276,3 +298,43 @@ def test_uninstall_restores_legacy_hooks(tmpdir_factory): ret, output = _get_commit_output(tmpdir_factory, touch_file='baz') assert ret == 0 assert EXISTING_COMMIT_RUN.match(output) + + +def test_replace_old_commit_script(tmpdir_factory): + path = make_consuming_repo(tmpdir_factory, 'script_hooks_repo') + with local.cwd(path): + runner = Runner(path) + + # Install a script that looks like our old script + pre_commit_contents = io.open( + pkg_resources.resource_filename( + 'pre_commit', 'resources/pre-commit-hook', + ) + ).read() + new_contents = pre_commit_contents.replace( + IDENTIFYING_HASH, PREVIOUS_IDENTIFYING_HASHES[-1], + ) + + with io.open(runner.pre_commit_path, 'w') as pre_commit_file: + pre_commit_file.write(new_contents) + make_executable(runner.pre_commit_path) + + # Install normally + assert install(runner) == 0 + + ret, output = _get_commit_output(tmpdir_factory) + assert ret == 0 + assert NORMAL_PRE_COMMIT_RUN.match(output) + + +def test_uninstall_doesnt_remove_not_our_hooks(tmpdir_factory): + path = git_dir(tmpdir_factory) + with local.cwd(path): + runner = Runner(path) + with io.open(runner.pre_commit_path, 'w') as pre_commit_file: + pre_commit_file.write('#!/usr/bin/env bash\necho 1\n') + make_executable(runner.pre_commit_path) + + assert uninstall(runner) == 0 + + assert os.path.exists(runner.pre_commit_path)