Merging upstream version 3.2.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
577069b57b
commit
bdf4a19272
23 changed files with 357 additions and 102 deletions
|
@ -12,6 +12,7 @@ from pre_commit.clientlib import CONFIG_HOOK_DICT
|
|||
from pre_commit.clientlib import CONFIG_REPO_DICT
|
||||
from pre_commit.clientlib import CONFIG_SCHEMA
|
||||
from pre_commit.clientlib import DEFAULT_LANGUAGE_VERSION
|
||||
from pre_commit.clientlib import MANIFEST_HOOK_DICT
|
||||
from pre_commit.clientlib import MANIFEST_SCHEMA
|
||||
from pre_commit.clientlib import META_HOOK_DICT
|
||||
from pre_commit.clientlib import OptionalSensibleRegexAtHook
|
||||
|
@ -416,3 +417,50 @@ def test_warn_additional(schema):
|
|||
x for x in schema.items if isinstance(x, cfgv.WarnAdditionalKeys)
|
||||
)
|
||||
assert allowed_keys == set(warn_additional.keys)
|
||||
|
||||
|
||||
def test_stages_migration_for_default_stages():
|
||||
cfg = {
|
||||
'default_stages': ['commit-msg', 'push', 'commit', 'merge-commit'],
|
||||
'repos': [],
|
||||
}
|
||||
cfgv.validate(cfg, CONFIG_SCHEMA)
|
||||
cfg = cfgv.apply_defaults(cfg, CONFIG_SCHEMA)
|
||||
assert cfg['default_stages'] == [
|
||||
'commit-msg', 'pre-push', 'pre-commit', 'pre-merge-commit',
|
||||
]
|
||||
|
||||
|
||||
def test_manifest_stages_defaulting():
|
||||
dct = {
|
||||
'id': 'fake-hook',
|
||||
'name': 'fake-hook',
|
||||
'entry': 'fake-hook',
|
||||
'language': 'system',
|
||||
'stages': ['commit-msg', 'push', 'commit', 'merge-commit'],
|
||||
}
|
||||
cfgv.validate(dct, MANIFEST_HOOK_DICT)
|
||||
dct = cfgv.apply_defaults(dct, MANIFEST_HOOK_DICT)
|
||||
assert dct['stages'] == [
|
||||
'commit-msg', 'pre-push', 'pre-commit', 'pre-merge-commit',
|
||||
]
|
||||
|
||||
|
||||
def test_config_hook_stages_defaulting_missing():
|
||||
dct = {'id': 'fake-hook'}
|
||||
cfgv.validate(dct, CONFIG_HOOK_DICT)
|
||||
dct = cfgv.apply_defaults(dct, CONFIG_HOOK_DICT)
|
||||
assert dct == {'id': 'fake-hook'}
|
||||
|
||||
|
||||
def test_config_hook_stages_defaulting():
|
||||
dct = {
|
||||
'id': 'fake-hook',
|
||||
'stages': ['commit-msg', 'push', 'commit', 'merge-commit'],
|
||||
}
|
||||
cfgv.validate(dct, CONFIG_HOOK_DICT)
|
||||
dct = cfgv.apply_defaults(dct, CONFIG_HOOK_DICT)
|
||||
assert dct == {
|
||||
'id': 'fake-hook',
|
||||
'stages': ['commit-msg', 'pre-push', 'pre-commit', 'pre-merge-commit'],
|
||||
}
|
||||
|
|
|
@ -100,6 +100,8 @@ def test_run_legacy_recursive(tmpdir):
|
|||
('commit-msg', ['.git/COMMIT_EDITMSG']),
|
||||
('post-commit', []),
|
||||
('post-merge', ['1']),
|
||||
('pre-rebase', ['main', 'topic']),
|
||||
('pre-rebase', ['main']),
|
||||
('post-checkout', ['old_head', 'new_head', '1']),
|
||||
('post-rewrite', ['amend']),
|
||||
# multiple choices for commit-editmsg
|
||||
|
@ -139,13 +141,36 @@ def test_check_args_length_prepare_commit_msg_error():
|
|||
)
|
||||
|
||||
|
||||
def test_check_args_length_pre_rebase_error():
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
hook_impl._check_args_length('pre-rebase', [])
|
||||
msg, = excinfo.value.args
|
||||
assert msg == 'hook-impl for pre-rebase expected 1 or 2 arguments but got 0: []' # noqa: E501
|
||||
|
||||
|
||||
def test_run_ns_pre_commit():
|
||||
ns = hook_impl._run_ns('pre-commit', True, (), b'')
|
||||
assert ns is not None
|
||||
assert ns.hook_stage == 'commit'
|
||||
assert ns.hook_stage == 'pre-commit'
|
||||
assert ns.color is True
|
||||
|
||||
|
||||
def test_run_ns_pre_rebase():
|
||||
ns = hook_impl._run_ns('pre-rebase', True, ('main', 'topic'), b'')
|
||||
assert ns is not None
|
||||
assert ns.hook_stage == 'pre-rebase'
|
||||
assert ns.color is True
|
||||
assert ns.pre_rebase_upstream == 'main'
|
||||
assert ns.pre_rebase_branch == 'topic'
|
||||
|
||||
ns = hook_impl._run_ns('pre-rebase', True, ('main',), b'')
|
||||
assert ns is not None
|
||||
assert ns.hook_stage == 'pre-rebase'
|
||||
assert ns.color is True
|
||||
assert ns.pre_rebase_upstream == 'main'
|
||||
assert ns.pre_rebase_branch is None
|
||||
|
||||
|
||||
def test_run_ns_commit_msg():
|
||||
ns = hook_impl._run_ns('commit-msg', False, ('.git/COMMIT_MSG',), b'')
|
||||
assert ns is not None
|
||||
|
@ -245,7 +270,7 @@ def test_run_ns_pre_push_updating_branch(push_example):
|
|||
ns = hook_impl._run_ns('pre-push', False, args, stdin)
|
||||
|
||||
assert ns is not None
|
||||
assert ns.hook_stage == 'push'
|
||||
assert ns.hook_stage == 'pre-push'
|
||||
assert ns.color is False
|
||||
assert ns.remote_name == 'origin'
|
||||
assert ns.remote_url == src
|
||||
|
|
|
@ -810,6 +810,46 @@ def test_post_merge_integration(tempdir_factory, store):
|
|||
assert os.path.exists('post-merge.tmp')
|
||||
|
||||
|
||||
def test_pre_rebase_integration(tempdir_factory, store):
|
||||
path = git_dir(tempdir_factory)
|
||||
config = {
|
||||
'repos': [
|
||||
{
|
||||
'repo': 'local',
|
||||
'hooks': [{
|
||||
'id': 'pre-rebase',
|
||||
'name': 'Pre rebase',
|
||||
'entry': 'touch pre-rebase.tmp',
|
||||
'language': 'system',
|
||||
'always_run': True,
|
||||
'verbose': True,
|
||||
'stages': ['pre-rebase'],
|
||||
}],
|
||||
},
|
||||
],
|
||||
}
|
||||
write_config(path, config)
|
||||
with cwd(path):
|
||||
install(C.CONFIG_FILE, store, hook_types=['pre-rebase'])
|
||||
open('foo', 'a').close()
|
||||
cmd_output('git', 'add', '.')
|
||||
git_commit()
|
||||
|
||||
cmd_output('git', 'checkout', '-b', 'branch')
|
||||
open('bar', 'a').close()
|
||||
cmd_output('git', 'add', '.')
|
||||
git_commit()
|
||||
|
||||
cmd_output('git', 'checkout', 'master')
|
||||
open('baz', 'a').close()
|
||||
cmd_output('git', 'add', '.')
|
||||
git_commit()
|
||||
|
||||
cmd_output('git', 'checkout', 'branch')
|
||||
cmd_output('git', 'rebase', 'master', 'branch')
|
||||
assert os.path.exists('pre-rebase.tmp')
|
||||
|
||||
|
||||
def test_post_rewrite_integration(tempdir_factory, store):
|
||||
path = git_dir(tempdir_factory)
|
||||
config = {
|
||||
|
|
|
@ -354,13 +354,13 @@ def test_show_diff_on_failure(
|
|||
({'hook': 'bash_hook'}, (b'Bash hook', b'Passed'), 0, True),
|
||||
(
|
||||
{'hook': 'nope'},
|
||||
(b'No hook with id `nope` in stage `commit`',),
|
||||
(b'No hook with id `nope` in stage `pre-commit`',),
|
||||
1,
|
||||
True,
|
||||
),
|
||||
(
|
||||
{'hook': 'nope', 'hook_stage': 'push'},
|
||||
(b'No hook with id `nope` in stage `push`',),
|
||||
{'hook': 'nope', 'hook_stage': 'pre-push'},
|
||||
(b'No hook with id `nope` in stage `pre-push`',),
|
||||
1,
|
||||
True,
|
||||
),
|
||||
|
@ -563,6 +563,16 @@ def test_merge_conflict_resolved(cap_out, store, in_merge_conflict):
|
|||
assert msg in printed
|
||||
|
||||
|
||||
def test_rebase(cap_out, store, repo_with_passing_hook):
|
||||
args = run_opts(pre_rebase_upstream='master', pre_rebase_branch='topic')
|
||||
environ: MutableMapping[str, str] = {}
|
||||
ret, printed = _do_run(
|
||||
cap_out, store, repo_with_passing_hook, args, environ,
|
||||
)
|
||||
assert environ['PRE_COMMIT_PRE_REBASE_UPSTREAM'] == 'master'
|
||||
assert environ['PRE_COMMIT_PRE_REBASE_BRANCH'] == 'topic'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('hooks', 'expected'),
|
||||
(
|
||||
|
@ -818,7 +828,7 @@ def test_stages(cap_out, store, repo_with_passing_hook):
|
|||
'language': 'pygrep',
|
||||
'stages': [stage],
|
||||
}
|
||||
for i, stage in enumerate(('commit', 'push', 'manual'), 1)
|
||||
for i, stage in enumerate(('pre-commit', 'pre-push', 'manual'), 1)
|
||||
],
|
||||
}
|
||||
add_config_to_repo(repo_with_passing_hook, config)
|
||||
|
@ -833,8 +843,8 @@ def test_stages(cap_out, store, repo_with_passing_hook):
|
|||
assert printed.count(b'hook ') == 1
|
||||
return printed
|
||||
|
||||
assert _run_for_stage('commit').startswith(b'hook 1...')
|
||||
assert _run_for_stage('push').startswith(b'hook 2...')
|
||||
assert _run_for_stage('pre-commit').startswith(b'hook 1...')
|
||||
assert _run_for_stage('pre-push').startswith(b'hook 2...')
|
||||
assert _run_for_stage('manual').startswith(b'hook 3...')
|
||||
|
||||
|
||||
|
@ -1173,7 +1183,7 @@ def test_args_hook_only(cap_out, store, repo_with_passing_hook):
|
|||
),
|
||||
'language': 'system',
|
||||
'files': r'\.py$',
|
||||
'stages': ['commit'],
|
||||
'stages': ['pre-commit'],
|
||||
},
|
||||
{
|
||||
'id': 'do_not_commit',
|
||||
|
|
|
@ -36,10 +36,10 @@ def test_read_pyvenv_cfg_non_utf8(tmpdir):
|
|||
|
||||
def test_norm_version_expanduser():
|
||||
home = os.path.expanduser('~')
|
||||
if os.name == 'nt': # pragma: nt cover
|
||||
if sys.platform == 'win32': # pragma: win32 cover
|
||||
path = r'~\python343'
|
||||
expected_path = fr'{home}\python343'
|
||||
else: # pragma: nt no cover
|
||||
else: # pragma: win32 no cover
|
||||
path = '~/.pyenv/versions/3.4.3/bin/python'
|
||||
expected_path = f'{home}/.pyenv/versions/3.4.3/bin/python'
|
||||
result = python.norm_version(path)
|
||||
|
@ -233,3 +233,54 @@ setup(
|
|||
return_value=False,
|
||||
):
|
||||
assert run_language(tmp_path, python, 'myexe') == (0, b'ohai\n')
|
||||
|
||||
|
||||
def _make_hello_hello(tmp_path):
|
||||
setup_py = '''\
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='socks',
|
||||
version='0.0.0',
|
||||
py_modules=['socks'],
|
||||
entry_points={'console_scripts': ['socks = socks:main']},
|
||||
)
|
||||
'''
|
||||
|
||||
main_py = '''\
|
||||
import sys
|
||||
|
||||
def main():
|
||||
print(repr(sys.argv[1:]))
|
||||
print('hello hello')
|
||||
return 0
|
||||
'''
|
||||
tmp_path.joinpath('setup.py').write_text(setup_py)
|
||||
tmp_path.joinpath('socks.py').write_text(main_py)
|
||||
|
||||
|
||||
def test_simple_python_hook(tmp_path):
|
||||
_make_hello_hello(tmp_path)
|
||||
|
||||
ret = run_language(tmp_path, python, 'socks', [os.devnull])
|
||||
assert ret == (0, f'[{os.devnull!r}]\nhello hello\n'.encode())
|
||||
|
||||
|
||||
def test_simple_python_hook_default_version(tmp_path):
|
||||
# make sure that this continues to work for platforms where default
|
||||
# language detection does not work
|
||||
with mock.patch.object(
|
||||
python,
|
||||
'get_default_version',
|
||||
return_value=C.DEFAULT,
|
||||
):
|
||||
test_simple_python_hook(tmp_path)
|
||||
|
||||
|
||||
def test_python_hook_weird_setup_cfg(tmp_path):
|
||||
_make_hello_hello(tmp_path)
|
||||
setup_cfg = '[install]\ninstall_scripts=/usr/sbin'
|
||||
tmp_path.joinpath('setup.cfg').write_text(setup_cfg)
|
||||
|
||||
ret = run_language(tmp_path, python, 'socks', [os.devnull])
|
||||
assert ret == (0, f'[{os.devnull!r}]\nhello hello\n'.encode())
|
||||
|
|
|
@ -216,3 +216,9 @@ def test_expected_fatal_error_no_git_repo(in_tmpdir, cap_out, mock_store_dir):
|
|||
'Is it installed, and are you in a Git repository directory?'
|
||||
)
|
||||
assert cap_out_lines[-1] == f'Check the log at {log_file}'
|
||||
|
||||
|
||||
def test_hook_stage_migration(mock_store_dir):
|
||||
with mock.patch.object(main, 'run') as mck:
|
||||
main.main(('run', '--hook-stage', 'commit'))
|
||||
assert mck.call_args[0][2].hook_stage == 'pre-commit'
|
||||
|
|
|
@ -94,7 +94,7 @@ def test_normexe_does_not_exist_sep():
|
|||
assert excinfo.value.args == ('Executable `./i-dont-exist-lol` not found',)
|
||||
|
||||
|
||||
@pytest.mark.xfail(os.name == 'nt', reason='posix only')
|
||||
@pytest.mark.xfail(sys.platform == 'win32', reason='posix only')
|
||||
def test_normexe_not_executable(tmpdir): # pragma: win32 no cover
|
||||
tmpdir.join('exe').ensure()
|
||||
with tmpdir.as_cwd(), pytest.raises(OSError) as excinfo:
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import shlex
|
||||
import shutil
|
||||
import sys
|
||||
from typing import Any
|
||||
from unittest import mock
|
||||
|
||||
|
@ -16,6 +18,7 @@ from pre_commit.clientlib import CONFIG_SCHEMA
|
|||
from pre_commit.clientlib import load_manifest
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import python
|
||||
from pre_commit.languages import system
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.repository import _hook_installed
|
||||
from pre_commit.repository import all_hooks
|
||||
|
@ -79,51 +82,6 @@ def _test_hook_repo(
|
|||
assert out == expected
|
||||
|
||||
|
||||
def test_python_hook(tempdir_factory, store):
|
||||
_test_hook_repo(
|
||||
tempdir_factory, store, 'python_hooks_repo',
|
||||
'foo', [os.devnull],
|
||||
f'[{os.devnull!r}]\nHello World\n'.encode(),
|
||||
)
|
||||
|
||||
|
||||
def test_python_hook_default_version(tempdir_factory, store):
|
||||
# make sure that this continues to work for platforms where default
|
||||
# language detection does not work
|
||||
with mock.patch.object(
|
||||
python,
|
||||
'get_default_version',
|
||||
return_value=C.DEFAULT,
|
||||
):
|
||||
test_python_hook(tempdir_factory, store)
|
||||
|
||||
|
||||
def test_python_hook_args_with_spaces(tempdir_factory, store):
|
||||
_test_hook_repo(
|
||||
tempdir_factory, store, 'python_hooks_repo',
|
||||
'foo',
|
||||
[],
|
||||
b"['i have spaces', 'and\"\\'quotes', '$and !this']\n"
|
||||
b'Hello World\n',
|
||||
config_kwargs={
|
||||
'hooks': [{
|
||||
'id': 'foo',
|
||||
'args': ['i have spaces', 'and"\'quotes', '$and !this'],
|
||||
}],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_python_hook_weird_setup_cfg(in_git_dir, tempdir_factory, store):
|
||||
in_git_dir.join('setup.cfg').write('[install]\ninstall_scripts=/usr/sbin')
|
||||
|
||||
_test_hook_repo(
|
||||
tempdir_factory, store, 'python_hooks_repo',
|
||||
'foo', [os.devnull],
|
||||
f'[{os.devnull!r}]\nHello World\n'.encode(),
|
||||
)
|
||||
|
||||
|
||||
def test_python_venv_deprecation(store, caplog):
|
||||
config = {
|
||||
'repo': 'local',
|
||||
|
@ -198,7 +156,7 @@ def test_intermixed_stdout_stderr(tempdir_factory, store):
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(os.name == 'nt', reason='ptys are posix-only')
|
||||
@pytest.mark.xfail(sys.platform == 'win32', reason='ptys are posix-only')
|
||||
def test_output_isatty(tempdir_factory, store):
|
||||
_test_hook_repo(
|
||||
tempdir_factory, store, 'stdout_stderr_repo',
|
||||
|
@ -430,7 +388,7 @@ def test_local_python_repo(store, local_python_config):
|
|||
def test_default_language_version(store, local_python_config):
|
||||
config: dict[str, Any] = {
|
||||
'default_language_version': {'python': 'fake'},
|
||||
'default_stages': ['commit'],
|
||||
'default_stages': ['pre-commit'],
|
||||
'repos': [local_python_config],
|
||||
}
|
||||
|
||||
|
@ -447,18 +405,18 @@ def test_default_language_version(store, local_python_config):
|
|||
def test_default_stages(store, local_python_config):
|
||||
config: dict[str, Any] = {
|
||||
'default_language_version': {'python': C.DEFAULT},
|
||||
'default_stages': ['commit'],
|
||||
'default_stages': ['pre-commit'],
|
||||
'repos': [local_python_config],
|
||||
}
|
||||
|
||||
# `stages` was not set, should default
|
||||
hook, = all_hooks(config, store)
|
||||
assert hook.stages == ['commit']
|
||||
assert hook.stages == ['pre-commit']
|
||||
|
||||
# `stages` is set, should not default
|
||||
config['repos'][0]['hooks'][0]['stages'] = ['push']
|
||||
config['repos'][0]['hooks'][0]['stages'] = ['pre-push']
|
||||
hook, = all_hooks(config, store)
|
||||
assert hook.stages == ['push']
|
||||
assert hook.stages == ['pre-push']
|
||||
|
||||
|
||||
def test_hook_id_not_present(tempdir_factory, store, caplog):
|
||||
|
@ -526,11 +484,19 @@ def test_manifest_hooks(tempdir_factory, store):
|
|||
name='Bash hook',
|
||||
pass_filenames=True,
|
||||
require_serial=False,
|
||||
stages=(
|
||||
'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg',
|
||||
'post-commit', 'manual', 'post-checkout', 'push', 'post-merge',
|
||||
stages=[
|
||||
'commit-msg',
|
||||
'post-checkout',
|
||||
'post-commit',
|
||||
'post-merge',
|
||||
'post-rewrite',
|
||||
),
|
||||
'pre-commit',
|
||||
'pre-merge-commit',
|
||||
'pre-push',
|
||||
'pre-rebase',
|
||||
'prepare-commit-msg',
|
||||
'manual',
|
||||
],
|
||||
types=['file'],
|
||||
types_or=[],
|
||||
verbose=False,
|
||||
|
@ -582,3 +548,14 @@ def test_non_installable_hook_error_for_additional_dependencies(store, caplog):
|
|||
'using language `system` which does not install an environment. '
|
||||
'Perhaps you meant to use a specific language?'
|
||||
)
|
||||
|
||||
|
||||
def test_args_with_spaces_and_quotes(tmp_path):
|
||||
ret = run_language(
|
||||
tmp_path, system,
|
||||
f"{shlex.quote(sys.executable)} -c 'import sys; print(sys.argv[1:])'",
|
||||
('i have spaces', 'and"\'quotes', '$and !this'),
|
||||
)
|
||||
|
||||
expected = b"['i have spaces', 'and\"\\'quotes', '$and !this']\n"
|
||||
assert ret == (0, expected)
|
||||
|
|
|
@ -187,7 +187,7 @@ def test_xargs_propagate_kwargs_to_cmd():
|
|||
assert b'Pre commit is awesome' in stdout
|
||||
|
||||
|
||||
@pytest.mark.xfail(os.name == 'nt', reason='posix only')
|
||||
@pytest.mark.xfail(sys.platform == 'win32', reason='posix only')
|
||||
def test_xargs_color_true_makes_tty():
|
||||
retcode, out = xargs.xargs(
|
||||
(sys.executable, '-c', 'import sys; print(sys.stdout.isatty())'),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue