def test_pre_push_legacy(tempdir_factory): upstream = make_consuming_repo(tempdir_factory, 'script_hooks_repo') path = tempdir_factory.get() cmd_output('git', 'clone', upstream, path) with cwd(path): runner = Runner(path, C.CONFIG_FILE) hook_path = runner.get_hook_path('pre-push') mkdirp(os.path.dirname(hook_path)) with io.open(hook_path, 'w') as hook_file: hook_file.write( '#!/usr/bin/env bash\n' 'set -eu\n' 'read lr ls rr rs\n' 'test -n "$lr" -a -n "$ls" -a -n "$rr" -a -n "$rs"\n' 'echo legacy\n', ) make_executable(hook_path) install(runner, hook_type='pre-push') assert _get_commit_output(tempdir_factory)[0] == 0 retc, output = _get_push_output(tempdir_factory) assert retc == 0 first_line, _, third_line = output.splitlines()[:3] assert first_line == 'legacy' assert third_line.startswith('Bash hook') assert third_line.endswith('Passed')
def write_executable(shebang, filename='run'): os.mkdir('bin') path = os.path.join('bin', filename) with io.open(path, 'w') as f: f.write('#!{}'.format(shebang)) make_executable(path) return path
def test_run_legacy_executes_legacy_script(tmpdir, capfd): hook = tmpdir.join('pre-commit.legacy') hook.write('#!/usr/bin/env bash\necho hi "$@"\nexit 1\n') make_executable(hook) retv, stdin = hook_impl._run_legacy('pre-commit', tmpdir, ('arg1', 'arg2')) assert capfd.readouterr().out.strip() == 'hi arg1 arg2' assert (retv, stdin) == (1, b'')
def test_unhealthy_then_replaced(python_dir): prefix, tmpdir = python_dir python.install_environment(prefix, C.DEFAULT, ()) # simulate an exe which returns an old version exe_name = win_exe('python') py_exe = prefix.path(python.bin_dir('py_env-default'), exe_name) os.rename(py_exe, f'{py_exe}.tmp') with open(py_exe, 'w') as f: f.write('#!/usr/bin/env bash\necho 1.2.3\n') make_executable(py_exe) # should be unhealthy due to version mismatch ret = python.health_check(prefix, C.DEFAULT) assert ret == ( f'virtualenv python version did not match created version:\n' f'- actual version: 1.2.3\n' f'- expected version: {python._version_info(sys.executable)}\n') # now put the exe back and it should be healthy again os.replace(f'{py_exe}.tmp', py_exe) assert python.health_check(prefix, C.DEFAULT) is None
def prepare_commit_msg_repo(tempdir_factory): path = git_dir(tempdir_factory) script_name = 'add_sign_off.sh' config = { 'repo': 'local', 'hooks': [{ 'id': 'add-signoff', 'name': 'Add "Signed off by:"', 'entry': './{}'.format(script_name), 'language': 'script', 'stages': ['prepare-commit-msg'], }], } write_config(path, config) with cwd(path): with io.open(script_name, 'w') as script_file: script_file.write( '#!/usr/bin/env bash\n' 'set -eu\n' 'echo "\nSigned off by: " >> "$1"\n', ) make_executable(script_name) cmd_output('git', 'add', '.') git_commit(msg=prepare_commit_msg_repo.__name__) yield path
def test_prepare_commit_msg_legacy( prepare_commit_msg_repo, tempdir_factory, store, ): hook_path = os.path.join( prepare_commit_msg_repo, '.git/hooks/prepare-commit-msg', ) mkdirp(os.path.dirname(hook_path)) with io.open(hook_path, 'w') as hook_file: hook_file.write( '#!/usr/bin/env bash\n' 'set -eu\n' 'test -e "$1"\n' 'echo legacy\n', ) make_executable(hook_path) install(C.CONFIG_FILE, store, hook_type='prepare-commit-msg') msg = 'Hi' retc, out = _get_commit_output(tempdir_factory, msg=msg) assert retc == 0 first_line, second_line = out.splitlines()[:2] assert first_line == 'legacy' assert second_line.startswith('Add "Signed off by:"...') commit_msg_path = os.path.join( prepare_commit_msg_repo, '.git/COMMIT_EDITMSG', ) with io.open(commit_msg_path) as f: assert 'Signed off by: ' in f.read()
def test_pre_push_legacy(tempdir_factory, store): upstream = make_consuming_repo(tempdir_factory, 'script_hooks_repo') path = tempdir_factory.get() cmd_output('git', 'clone', upstream, path) with cwd(path): mkdirp(os.path.join(path, '.git/hooks')) with io.open(os.path.join(path, '.git/hooks/pre-push'), 'w') as f: f.write( '#!/usr/bin/env bash\n' 'set -eu\n' 'read lr ls rr rs\n' 'test -n "$lr" -a -n "$ls" -a -n "$rr" -a -n "$rs"\n' 'echo legacy\n', ) make_executable(f.name) install(C.CONFIG_FILE, store, hook_type='pre-push') assert _get_commit_output(tempdir_factory)[0] == 0 retc, output = _get_push_output(tempdir_factory) assert retc == 0 first_line, _, third_line = output.splitlines()[:3] assert first_line == 'legacy' assert third_line.startswith('Bash hook') assert third_line.endswith('Passed')
def install( config_file, store, overwrite=False, hooks=False, hook_type='pre-commit', skip_on_missing_conf=False, ): """Install the pre-commit hooks.""" if cmd_output('git', 'config', 'core.hooksPath', retcode=None)[1].strip(): logger.error( 'Cowardly refusing to install hooks with `core.hooksPath` set.\n' 'hint: `git config --unset-all core.hooksPath`', ) return 1 hook_path, legacy_path = _hook_paths(hook_type) mkdirp(os.path.dirname(hook_path)) # If we have an existing hook, move it to pre-commit.legacy if os.path.lexists(hook_path) and not is_our_script(hook_path): os.rename(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): os.remove(legacy_path) elif os.path.exists(legacy_path): output.write_line( 'Running in migration mode with existing hooks at {}\n' 'Use -f to use only pre-commit.'.format(legacy_path), ) params = { 'CONFIG': config_file, 'HOOK_TYPE': hook_type, 'INSTALL_PYTHON': sys.executable, 'SKIP_ON_MISSING_CONFIG': skip_on_missing_conf, } with io.open(hook_path, 'w') as hook_file: contents = resource_text('hook-tmpl') before, rest = contents.split(TEMPLATE_START) to_template, after = rest.split(TEMPLATE_END) before = before.replace('#!/usr/bin/env python3', shebang()) hook_file.write(before + TEMPLATE_START) for line in to_template.splitlines(): var = line.split()[0] hook_file.write('{} = {!r}\n'.format(var, params[var])) hook_file.write(TEMPLATE_END + after) make_executable(hook_path) output.write_line('pre-commit installed at {}'.format(hook_path)) # If they requested we install all of the hooks, do so. if hooks: install_hooks(config_file, store) return 0
def test_shebang_posix_on_path(tmpdir): exe = tmpdir.join(f'python{sys.version_info[0]}').ensure() make_executable(exe) with mock.patch.object(sys, 'platform', 'posix'): with mock.patch.object(os, 'defpath', tmpdir.strpath): expected = f'#!/usr/bin/env python{sys.version_info[0]}' assert shebang() == expected
def test_uninstall_doesnt_remove_not_our_hooks(in_git_dir): pre_commit = in_git_dir.join('.git/hooks').ensure_dir().join('pre-commit') pre_commit.write('#!/usr/bin/env bash\necho 1\n') make_executable(pre_commit.strpath) assert uninstall(hook_types=['pre-commit']) == 0 assert pre_commit.exists()
def test_uninstall_doesnt_remove_not_our_hooks(in_git_dir): pre_commit = in_git_dir.join('.git/hooks').ensure_dir().join('pre-commit') pre_commit.write('#!/usr/bin/env bash\necho 1\n') make_executable(pre_commit.strpath) assert uninstall() == 0 assert pre_commit.exists()
def install( config_file, store, overwrite=False, hooks=False, hook_type='pre-commit', skip_on_missing_conf=False, ): """Install the pre-commit hooks.""" if cmd_output('git', 'config', 'core.hooksPath', retcode=None)[1].strip(): logger.error( 'Cowardly refusing to install hooks with `core.hooksPath` set.\n' 'hint: `git config --unset-all core.hooksPath`', ) return 1 hook_path, legacy_path = _hook_paths(hook_type) mkdirp(os.path.dirname(hook_path)) # If we have an existing hook, move it to pre-commit.legacy if os.path.lexists(hook_path) and not is_our_script(hook_path): shutil.move(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): os.remove(legacy_path) elif os.path.exists(legacy_path): output.write_line( 'Running in migration mode with existing hooks at {}\n' 'Use -f to use only pre-commit.'.format(legacy_path), ) params = { 'CONFIG': config_file, 'HOOK_TYPE': hook_type, 'INSTALL_PYTHON': sys.executable, 'SKIP_ON_MISSING_CONFIG': skip_on_missing_conf, } with io.open(hook_path, 'w') as hook_file: contents = resource_text('hook-tmpl') before, rest = contents.split(TEMPLATE_START) to_template, after = rest.split(TEMPLATE_END) before = before.replace('#!/usr/bin/env python3', shebang()) hook_file.write(before + TEMPLATE_START) for line in to_template.splitlines(): var = line.split()[0] hook_file.write('{} = {!r}\n'.format(var, params[var])) hook_file.write(TEMPLATE_END + after) make_executable(hook_path) output.write_line('pre-commit installed at {}'.format(hook_path)) # If they requested we install all of the hooks, do so. if hooks: install_hooks(config_file, store) return 0
def test_cmd_output_no_shebang(tmpdir, fn): f = tmpdir.join('f').ensure() make_executable(f) # previously this raised `OSError` -- the output is platform specific ret, out, _ = fn(str(f), retcode=None, stderr=subprocess.STDOUT) assert ret == 1 assert isinstance(out, bytes) assert out.endswith(b'\n')
def install( runner, overwrite=False, hooks=False, hook_type='pre-commit', skip_on_missing_conf=False, ): """Install the pre-commit hooks.""" hook_path = runner.get_hook_path(hook_type) legacy_path = hook_path + '.legacy' mkdirp(os.path.dirname(hook_path)) # If we have an existing hook, move it to pre-commit.legacy if os.path.lexists(hook_path) and not is_our_script(hook_path): os.rename(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): os.remove(legacy_path) elif os.path.exists(legacy_path): output.write_line( 'Running in migration mode with existing hooks at {}\n' 'Use -f to use only pre-commit.'.format( legacy_path, ), ) with io.open(hook_path, 'w') as pre_commit_file_obj: if hook_type == 'pre-push': with io.open(resource_filename('pre-push-tmpl')) as f: hook_specific_contents = f.read() elif hook_type == 'commit-msg': with io.open(resource_filename('commit-msg-tmpl')) as f: hook_specific_contents = f.read() elif hook_type == 'pre-commit': hook_specific_contents = '' else: raise AssertionError('Unknown hook type: {}'.format(hook_type)) skip_on_missing_conf = 'true' if skip_on_missing_conf else 'false' contents = io.open(resource_filename('hook-tmpl')).read().format( sys_executable=pipes.quote(sys.executable), hook_type=hook_type, hook_specific=hook_specific_contents, config_file=runner.config_file, skip_on_missing_conf=skip_on_missing_conf, ) pre_commit_file_obj.write(contents) make_executable(hook_path) output.write_line('pre-commit installed at {}'.format(hook_path)) # If they requested we install all of the hooks, do so. if hooks: install_hooks(runner) return 0
def install( runner, overwrite=False, hooks=False, hook_type='pre-commit', skip_on_missing_conf=False, ): """Install the pre-commit hooks.""" hook_path = runner.get_hook_path(hook_type) legacy_path = hook_path + '.legacy' mkdirp(os.path.dirname(hook_path)) # If we have an existing hook, move it to pre-commit.legacy if os.path.lexists(hook_path) and not is_our_script(hook_path): os.rename(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): os.remove(legacy_path) elif os.path.exists(legacy_path): output.write_line( 'Running in migration mode with existing hooks at {}\n' 'Use -f to use only pre-commit.'.format(legacy_path, ), ) with io.open(hook_path, 'w') as pre_commit_file_obj: if hook_type == 'pre-push': with io.open(resource_filename('pre-push-tmpl')) as f: hook_specific_contents = f.read() elif hook_type == 'commit-msg': with io.open(resource_filename('commit-msg-tmpl')) as f: hook_specific_contents = f.read() elif hook_type == 'pre-commit': hook_specific_contents = '' else: raise AssertionError('Unknown hook type: {}'.format(hook_type)) skip_on_missing_conf = 'true' if skip_on_missing_conf else 'false' contents = io.open(resource_filename('hook-tmpl')).read().format( sys_executable=sys.executable, hook_type=hook_type, hook_specific=hook_specific_contents, skip_on_missing_conf=skip_on_missing_conf, ) pre_commit_file_obj.write(contents) make_executable(hook_path) output.write_line('pre-commit installed at {}'.format(hook_path)) # If they requested we install all of the hooks, do so. if hooks: install_hooks(runner) return 0
def test_run_legacy_recursive(tmpdir): hook = tmpdir.join('pre-commit.legacy').ensure() make_executable(hook) # simulate a call being recursive def call(*_, **__): return hook_impl._run_legacy('pre-commit', tmpdir, ()) with mock.patch.object(subprocess, 'run', call): with pytest.raises(SystemExit): call()
def test_exclude_types_hook_repository(cap_out, store, tempdir_factory): git_path = make_consuming_repo(tempdir_factory, 'exclude_types_repo') with cwd(git_path): with io.open('exe', 'w') as exe: exe.write('#!/usr/bin/env python3\n') make_executable('exe') cmd_output('git', 'add', 'exe') stage_a_file('bar.py') ret, printed = _do_run(cap_out, store, git_path, run_opts()) assert ret == 1 assert b'bar.py' in printed assert b'exe' not in printed
def test_uninstall_doesnt_remove_not_our_hooks(tempdir_factory): path = git_dir(tempdir_factory) with cwd(path): runner = Runner(path, C.CONFIG_FILE) mkdirp(os.path.join(path, '.git/hooks')) with io.open(os.path.join(path, '.git/hooks/pre-commit'), 'w') as f: f.write('#!/usr/bin/env bash\necho 1\n') make_executable(f.name) assert uninstall(runner) == 0 assert os.path.exists(os.path.join(path, '.git/hooks/pre-commit'))
def test_uninstall_doesnt_remove_not_our_hooks(tempdir_factory): path = git_dir(tempdir_factory) with cwd(path): runner = Runner(path, C.CONFIG_FILE) mkdirp(os.path.dirname(runner.pre_commit_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)
def test_exclude_types_hook_repository(cap_out, store, tempdir_factory): git_path = make_consuming_repo(tempdir_factory, 'exclude_types_repo') with cwd(git_path): with open('exe', 'w') as exe: exe.write('#!/usr/bin/env python3\n') make_executable('exe') cmd_output('git', 'add', 'exe') stage_a_file('bar.py') ret, printed = _do_run(cap_out, store, git_path, run_opts()) assert ret == 1 assert b'bar.py' in printed assert b'exe' not in printed
def install( runner, overwrite=False, hooks=False, hook_type='pre-commit', skip_on_missing_conf=False, ): """Install the pre-commit hooks.""" hook_path = runner.get_hook_path(hook_type) legacy_path = hook_path + '.legacy' mkdirp(os.path.dirname(hook_path)) # If we have an existing hook, move it to pre-commit.legacy if os.path.lexists(hook_path) and not is_our_script(hook_path): os.rename(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): os.remove(legacy_path) elif os.path.exists(legacy_path): output.write_line( 'Running in migration mode with existing hooks at {}\n' 'Use -f to use only pre-commit.'.format(legacy_path), ) params = { 'CONFIG': runner.config_file, 'HOOK_TYPE': hook_type, 'INSTALL_PYTHON': sys.executable, 'SKIP_ON_MISSING_CONFIG': skip_on_missing_conf, } with io.open(hook_path, 'w') as hook_file: with io.open(resource_filename('hook-tmpl')) as f: contents = f.read() before, rest = contents.split(TEMPLATE_START) to_template, after = rest.split(TEMPLATE_END) hook_file.write(before + TEMPLATE_START) for line in to_template.splitlines(): var = line.split()[0] hook_file.write('{} = {!r}\n'.format(var, params[var])) hook_file.write(TEMPLATE_END + after) make_executable(hook_path) output.write_line('pre-commit installed at {}'.format(hook_path)) # If they requested we install all of the hooks, do so. if hooks: install_hooks(runner) return 0
def _install_hook_script( config_file: str, hook_type: str, overwrite: bool = False, skip_on_missing_config: bool = False, git_dir: Optional[str] = None, ) -> None: hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir) os.makedirs(os.path.dirname(hook_path), exist_ok=True) # If we have an existing hook, move it to pre-commit.legacy if os.path.lexists(hook_path) and not is_our_script(hook_path): shutil.move(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): os.remove(legacy_path) elif os.path.exists(legacy_path): output.write_line( f'Running in migration mode with existing hooks at {legacy_path}\n' f'Use -f to use only pre-commit.', ) args = ['hook-impl', f'--config={config_file}', f'--hook-type={hook_type}'] if skip_on_missing_config: args.append('--skip-on-missing-config') with open(hook_path, 'w') as hook_file: contents = resource_text('hook-tmpl') before, rest = contents.split(TEMPLATE_START) _, after = rest.split(TEMPLATE_END) # on windows always use `/bin/sh` since `bash` might not be on PATH # though we use bash-specific features `sh` on windows is actually # bash in "POSIXLY_CORRECT" mode which still supports the features we # use: subshells / arrays if sys.platform == 'win32': # pragma: win32 cover hook_file.write('#!/bin/sh\n') hook_file.write(before + TEMPLATE_START) hook_file.write(f'INSTALL_PYTHON={shlex.quote(sys.executable)}\n') # TODO: python3.8+: shlex.join args_s = ' '.join(shlex.quote(part) for part in args) hook_file.write(f'ARGS=({args_s})\n') hook_file.write(TEMPLATE_END + after) make_executable(hook_path) output.write_line(f'pre-commit installed at {hook_path}')
def test_failing_existing_hook_returns_1(tempdir_factory, store): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): # Write out a failing "old" hook os.makedirs(os.path.join(path, '.git/hooks'), exist_ok=True) with open(os.path.join(path, '.git/hooks/pre-commit'), 'w') as f: f.write('#!/usr/bin/env bash\necho "fail!"\nexit 1\n') make_executable(f.name) assert install(C.CONFIG_FILE, store, hook_types=['pre-commit']) == 0 # We should get a failure from the legacy hook ret, output = _get_commit_output(tempdir_factory) assert ret == 1 assert FAIL_OLD_HOOK.match(output)
def test_failing_existing_hook_returns_1(tempdir_factory, store): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): # Write out a failing "old" hook mkdirp(os.path.join(path, '.git/hooks')) with io.open(os.path.join(path, '.git/hooks/pre-commit'), 'w') as f: f.write('#!/usr/bin/env bash\necho "fail!"\nexit 1\n') make_executable(f.name) assert install(C.CONFIG_FILE, store) == 0 # We should get a failure from the legacy hook ret, output = _get_commit_output(tempdir_factory) assert ret == 1 assert FAIL_OLD_HOOK.match(output)
def test_install_overwrite(tempdir_factory): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): runner = Runner(path, C.CONFIG_FILE) # Write out the "old" hook mkdirp(os.path.dirname(runner.pre_commit_path)) with io.open(runner.pre_commit_path, 'w') as hook_file: hook_file.write('#!/usr/bin/env bash\necho "legacy hook"\n') make_executable(runner.pre_commit_path) assert install(runner, overwrite=True) == 0 ret, output = _get_commit_output(tempdir_factory) assert ret == 0 assert NORMAL_PRE_COMMIT_RUN.match(output)
def test_failing_existing_hook_returns_1(tempdir_factory): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): runner = Runner(path, C.CONFIG_FILE) # Write out a failing "old" hook mkdirp(os.path.dirname(runner.pre_commit_path)) with io.open(runner.pre_commit_path, 'w') as hook_file: hook_file.write('#!/usr/bin/env bash\necho "fail!"\nexit 1\n') make_executable(runner.pre_commit_path) assert install(runner) == 0 # We should get a failure from the legacy hook ret, output = _get_commit_output(tempdir_factory) assert ret == 1 assert FAIL_OLD_HOOK.match(output)
def install(runner, overwrite=False, hooks=False, hook_type='pre-commit'): """Install the pre-commit hooks.""" hook_path = runner.get_hook_path(hook_type) legacy_path = hook_path + '.legacy' mkdirp(os.path.dirname(hook_path)) # If we have an existing hook, move it to pre-commit.legacy if (os.path.lexists(hook_path) and not is_our_pre_commit(hook_path) and not is_previous_pre_commit(hook_path)): os.rename(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): os.remove(legacy_path) elif os.path.exists(legacy_path): print('Running in migration mode with existing hooks at {0}\n' 'Use -f to use only pre-commit.'.format(legacy_path, )) with io.open(hook_path, 'w') as pre_commit_file_obj: if hook_type == 'pre-push': with io.open(resource_filename('pre-push-tmpl')) as fp: pre_push_contents = fp.read() else: pre_push_contents = '' contents = io.open(resource_filename('hook-tmpl')).read().format( sys_executable=sys.executable, hook_type=hook_type, pre_push=pre_push_contents, ) pre_commit_file_obj.write(contents) make_executable(hook_path) print('pre-commit installed at {0}'.format(hook_path)) # If they requested we install all of the hooks, do so. if hooks: # Set up our logging handler logger.addHandler(LoggingHandler(False)) logger.setLevel(logging.INFO) for repository in runner.repositories: repository.require_installed() return 0
def _install_hook_script( config_file: str, hook_type: str, overwrite: bool = False, skip_on_missing_config: bool = False, git_dir: Optional[str] = None, ) -> None: hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir) os.makedirs(os.path.dirname(hook_path), exist_ok=True) # If we have an existing hook, move it to pre-commit.legacy if os.path.lexists(hook_path) and not is_our_script(hook_path): shutil.move(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): os.remove(legacy_path) elif os.path.exists(legacy_path): output.write_line( f'Running in migration mode with existing hooks at {legacy_path}\n' f'Use -f to use only pre-commit.', ) params = { 'CONFIG': config_file, 'HOOK_TYPE': hook_type, 'INSTALL_PYTHON': sys.executable, 'SKIP_ON_MISSING_CONFIG': skip_on_missing_config, } with open(hook_path, 'w') as hook_file: contents = resource_text('hook-tmpl') before, rest = contents.split(TEMPLATE_START) to_template, after = rest.split(TEMPLATE_END) before = before.replace('#!/usr/bin/env python3', shebang()) hook_file.write(before + TEMPLATE_START) for line in to_template.splitlines(): var = line.split()[0] hook_file.write(f'{var} = {params[var]!r}\n') hook_file.write(TEMPLATE_END + after) make_executable(hook_path) output.write_line(f'pre-commit installed at {hook_path}')
def test_uninstall_restores_legacy_hooks(tempdir_factory): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): runner = Runner(path, C.CONFIG_FILE) # Write out an "old" hook mkdirp(os.path.dirname(runner.pre_commit_path)) with io.open(runner.pre_commit_path, 'w') as hook_file: hook_file.write('#!/usr/bin/env bash\necho "legacy hook"\n') make_executable(runner.pre_commit_path) # Now install and uninstall pre-commit assert install(runner) == 0 assert uninstall(runner) == 0 # Make sure we installed the "old" hook correctly ret, output = _get_commit_output(tempdir_factory, touch_file='baz') assert ret == 0 assert EXISTING_COMMIT_RUN.match(output)
def _install_hook_script( config_file: str, hook_type: str, overwrite: bool = False, skip_on_missing_config: bool = False, git_dir: Optional[str] = None, ) -> None: hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir) os.makedirs(os.path.dirname(hook_path), exist_ok=True) # If we have an existing hook, move it to pre-commit.legacy if os.path.lexists(hook_path) and not is_our_script(hook_path): shutil.move(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): os.remove(legacy_path) elif os.path.exists(legacy_path): output.write_line( f"Running in migration mode with existing hooks at {legacy_path}\n" f"Use -f to use only pre-commit.", ) args = ["hook-impl", f"--config={config_file}", f"--hook-type={hook_type}"] if skip_on_missing_config: args.append("--skip-on-missing-config") params = {"INSTALL_PYTHON": sys.executable, "ARGS": args} with open(hook_path, "w") as hook_file: contents = resource_text("hook-tmpl") before, rest = contents.split(TEMPLATE_START) to_template, after = rest.split(TEMPLATE_END) before = before.replace("#!/usr/bin/env python3", shebang()) hook_file.write(before + TEMPLATE_START) for line in to_template.splitlines(): var = line.split()[0] hook_file.write(f"{var} = {params[var]!r}\n") hook_file.write(TEMPLATE_END + after) make_executable(hook_path) output.write_line(f"pre-commit installed at {hook_path}")
def test_commit_msg_legacy(commit_msg_repo, tempdir_factory, store): hook_path = os.path.join(commit_msg_repo, '.git/hooks/commit-msg') os.makedirs(os.path.dirname(hook_path), exist_ok=True) with open(hook_path, 'w') as hook_file: hook_file.write( '#!/usr/bin/env bash\n' 'set -eu\n' 'test -e "$1"\n' 'echo legacy\n', ) make_executable(hook_path) install(C.CONFIG_FILE, store, hook_types=['commit-msg']) msg = 'Hi\nSigned off by: asottile' retc, out = _get_commit_output(tempdir_factory, msg=msg) assert retc == 0 first_line, second_line = out.splitlines()[:2] assert first_line == 'legacy' assert second_line.startswith('Must have "Signed off by:"...')
def _install_hook_script( config_file: str, hook_type: str, overwrite: bool = False, skip_on_missing_config: bool = False, git_dir: Optional[str] = None, ) -> None: hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir) os.makedirs(os.path.dirname(hook_path), exist_ok=True) # If we have an existing hook, move it to pre-commit.legacy if os.path.lexists(hook_path) and not is_our_script(hook_path): shutil.move(hook_path, legacy_path) # If we specify overwrite, we simply delete the legacy file if overwrite and os.path.exists(legacy_path): os.remove(legacy_path) elif os.path.exists(legacy_path): output.write_line( f'Running in migration mode with existing hooks at {legacy_path}\n' f'Use -f to use only pre-commit.', ) args = ['hook-impl', f'--config={config_file}', f'--hook-type={hook_type}'] if skip_on_missing_config: args.append('--skip-on-missing-config') with open(hook_path, 'w') as hook_file: contents = resource_text('hook-tmpl') before, rest = contents.split(TEMPLATE_START) _, after = rest.split(TEMPLATE_END) hook_file.write(before + TEMPLATE_START) hook_file.write(f'INSTALL_PYTHON={shlex.quote(sys.executable)}\n') # TODO: python3.8+: shlex.join args_s = ' '.join(shlex.quote(part) for part in args) hook_file.write(f'ARGS=({args_s})\n') hook_file.write(TEMPLATE_END + after) make_executable(hook_path) output.write_line(f'pre-commit installed at {hook_path}')
def test_replace_old_commit_script(tempdir_factory, store): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): # Install a script that looks like our old script pre_commit_contents = resource_text('hook-tmpl') new_contents = pre_commit_contents.replace( CURRENT_HASH.decode(), PRIOR_HASHES[-1].decode(), ) os.makedirs(os.path.join(path, '.git/hooks'), exist_ok=True) with open(os.path.join(path, '.git/hooks/pre-commit'), 'w') as f: f.write(new_contents) make_executable(f.name) # Install normally assert install(C.CONFIG_FILE, store, hook_types=['pre-commit']) == 0 ret, output = _get_commit_output(tempdir_factory) assert ret == 0 NORMAL_PRE_COMMIT_RUN.assert_matches(output)
def test_install_existing_hook_no_overwrite_idempotent(tempdir_factory): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): runner = Runner(path, C.CONFIG_FILE) # Write out an "old" hook mkdirp(os.path.dirname(runner.pre_commit_path)) with io.open(runner.pre_commit_path, 'w') as hook_file: hook_file.write('#!/usr/bin/env bash\necho "legacy hook"\n') make_executable(runner.pre_commit_path) # Install twice assert install(runner) == 0 assert install(runner) == 0 # We should run both the legacy and pre-commit hooks ret, output = _get_commit_output(tempdir_factory) assert ret == 0 assert output.startswith('legacy hook\n') assert NORMAL_PRE_COMMIT_RUN.match(output[len('legacy hook\n'):])
def test_commit_msg_legacy(commit_msg_repo, tempdir_factory, store): hook_path = os.path.join(commit_msg_repo, '.git/hooks/commit-msg') mkdirp(os.path.dirname(hook_path)) with io.open(hook_path, 'w') as hook_file: hook_file.write( '#!/usr/bin/env bash\n' 'set -eu\n' 'test -e "$1"\n' 'echo legacy\n', ) make_executable(hook_path) install(C.CONFIG_FILE, store, hook_type='commit-msg') msg = 'Hi\nSigned off by: asottile' retc, out = _get_commit_output(tempdir_factory, msg=msg) assert retc == 0 first_line, second_line = out.splitlines()[:2] assert first_line == 'legacy' assert second_line.startswith('Must have "Signed off by:"...')
def test_replace_old_commit_script(tempdir_factory, store): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): # Install a script that looks like our old script pre_commit_contents = resource_text('hook-tmpl') new_contents = pre_commit_contents.replace( CURRENT_HASH, PRIOR_HASHES[-1], ) mkdirp(os.path.join(path, '.git/hooks')) with io.open(os.path.join(path, '.git/hooks/pre-commit'), 'w') as f: f.write(new_contents) make_executable(f.name) # Install normally assert install(C.CONFIG_FILE, store) == 0 ret, output = _get_commit_output(tempdir_factory) assert ret == 0 assert NORMAL_PRE_COMMIT_RUN.match(output)
def test_unhealthy_then_replaced(python_dir): prefix, tmpdir = python_dir python.install_environment(prefix, C.DEFAULT, ()) # simulate an exe which returns an old version exe_name = 'python.exe' if sys.platform == 'win32' else 'python' py_exe = prefix.path(python.bin_dir('py_env-default'), exe_name) os.rename(py_exe, f'{py_exe}.tmp') with open(py_exe, 'w') as f: f.write('#!/usr/bin/env bash\necho 1.2.3\n') make_executable(py_exe) # should be unhealthy due to version mismatch assert python.healthy(prefix, C.DEFAULT) is False # now put the exe back and it should be healthy again os.replace(f'{py_exe}.tmp', py_exe) assert python.healthy(prefix, C.DEFAULT) is True
def test_commit_msg_legacy(commit_msg_repo, tempdir_factory): runner = Runner(commit_msg_repo, C.CONFIG_FILE) hook_path = runner.get_hook_path('commit-msg') mkdirp(os.path.dirname(hook_path)) with io.open(hook_path, 'w') as hook_file: hook_file.write( '#!/usr/bin/env bash\n' 'set -eu\n' 'test -e "$1"\n' 'echo legacy\n', ) make_executable(hook_path) install(runner, hook_type='commit-msg') msg = 'Hi\nSigned off by: asottile' retc, out = _get_commit_output(tempdir_factory, commit_msg=msg) assert retc == 0 first_line, second_line = out.splitlines()[:2] assert first_line == 'legacy' assert second_line.startswith('Must have "Signed off by:"...')
def test_replace_old_commit_script(tempdir_factory, store): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): runner = Runner(path, C.CONFIG_FILE) # Install a script that looks like our old script pre_commit_contents = io.open(resource_filename('hook-tmpl')).read() new_contents = pre_commit_contents.replace( CURRENT_HASH, PRIOR_HASHES[-1], ) mkdirp(os.path.join(path, '.git/hooks')) with io.open(os.path.join(path, '.git/hooks/pre-commit'), 'w') as f: f.write(new_contents) make_executable(f.name) # Install normally assert install(runner, store) == 0 ret, output = _get_commit_output(tempdir_factory) assert ret == 0 assert NORMAL_PRE_COMMIT_RUN.match(output)
def test_replace_old_commit_script(tempdir_factory): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): runner = Runner(path, C.CONFIG_FILE) # Install a script that looks like our old script pre_commit_contents = io.open(resource_filename('hook-tmpl'), ).read() new_contents = pre_commit_contents.replace( IDENTIFYING_HASH, PREVIOUS_IDENTIFYING_HASHES[-1], ) mkdirp(os.path.dirname(runner.pre_commit_path)) 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(tempdir_factory) assert ret == 0 assert NORMAL_PRE_COMMIT_RUN.match(output)
def test_replace_old_commit_script(tempdir_factory): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): runner = Runner(path, C.CONFIG_FILE) # Install a script that looks like our old script pre_commit_contents = io.open( resource_filename('hook-tmpl'), ).read() new_contents = pre_commit_contents.replace( CURRENT_HASH, PRIOR_HASHES[-1], ) mkdirp(os.path.dirname(runner.pre_commit_path)) 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(tempdir_factory) assert ret == 0 assert NORMAL_PRE_COMMIT_RUN.match(output)
def test_install_existing_hooks_no_overwrite(tempdir_factory): path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') with cwd(path): runner = Runner(path) # Write out an "old" hook mkdirp(os.path.dirname(runner.pre_commit_path)) with io.open(runner.pre_commit_path, 'w') as hook_file: hook_file.write('#!/usr/bin/env bash\necho "legacy hook"\n') make_executable(runner.pre_commit_path) # Make sure we installed the "old" hook correctly ret, output = _get_commit_output(tempdir_factory, touch_file='baz') assert ret == 0 assert EXISTING_COMMIT_RUN.match(output) # Now install pre-commit (no-overwrite) assert install(runner) == 0 # We should run both the legacy and pre-commit hooks ret, output = _get_commit_output(tempdir_factory) assert ret == 0 assert output.startswith('legacy hook\n') assert NORMAL_PRE_COMMIT_RUN.match(output[len('legacy hook\n'):])
def _write_legacy_hook(path): mkdirp(os.path.join(path, '.git/hooks')) with io.open(os.path.join(path, '.git/hooks/pre-commit'), 'w') as f: f.write('#!/usr/bin/env bash\necho "legacy hook"\n') make_executable(f.name)
def test_simple_case(tmpdir): x = tmpdir.join('f') x.write_text('#!/usr/bin/env python', encoding='UTF-8') make_executable(x.strpath) assert parse_shebang.parse_filename(x.strpath) == ('python',)