mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-04-15 01:51:46 +04:00
Making it possible to invoke pre-commit run --files some.file from a subdirectory of the repository
This commit is contained in:
parent
894862462d
commit
df8102f57d
4 changed files with 72 additions and 29 deletions
|
|
@ -8,6 +8,7 @@ import pkg_resources
|
||||||
|
|
||||||
from pre_commit import color
|
from pre_commit import color
|
||||||
from pre_commit import five
|
from pre_commit import five
|
||||||
|
from pre_commit import git
|
||||||
from pre_commit.commands.autoupdate import autoupdate
|
from pre_commit.commands.autoupdate import autoupdate
|
||||||
from pre_commit.commands.clean import clean
|
from pre_commit.commands.clean import clean
|
||||||
from pre_commit.commands.install_uninstall import install
|
from pre_commit.commands.install_uninstall import install
|
||||||
|
|
@ -110,7 +111,8 @@ def main(argv=None):
|
||||||
help='Run on all the files in the repo. Implies --no-stash.',
|
help='Run on all the files in the repo. Implies --no-stash.',
|
||||||
)
|
)
|
||||||
run_mutex_group.add_argument(
|
run_mutex_group.add_argument(
|
||||||
'--files', nargs='*', help='Specific filenames to run hooks on.',
|
'--files', nargs='*', default=[],
|
||||||
|
help='Specific filenames to run hooks on.',
|
||||||
)
|
)
|
||||||
|
|
||||||
help = subparsers.add_parser(
|
help = subparsers.add_parser(
|
||||||
|
|
@ -122,6 +124,11 @@ def main(argv=None):
|
||||||
if len(argv) == 0:
|
if len(argv) == 0:
|
||||||
argv = ['run']
|
argv = ['run']
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
if args.command == 'run':
|
||||||
|
args.files = [
|
||||||
|
os.path.relpath(os.path.abspath(filename), git.get_root())
|
||||||
|
for filename in args.files
|
||||||
|
]
|
||||||
|
|
||||||
if args.command == 'help':
|
if args.command == 'help':
|
||||||
if args.help_cmd:
|
if args.help_cmd:
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class Runner(object):
|
||||||
def create(cls):
|
def create(cls):
|
||||||
"""Creates a PreCommitRunner by doing the following:
|
"""Creates a PreCommitRunner by doing the following:
|
||||||
- Finds the root of the current git repository
|
- Finds the root of the current git repository
|
||||||
- chdirs to that directory
|
- chdir to that directory
|
||||||
"""
|
"""
|
||||||
root = git.get_root()
|
root = git.get_root()
|
||||||
os.chdir(root)
|
os.chdir(root)
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,14 @@ def add_config_to_repo(git_path, config):
|
||||||
return git_path
|
return git_path
|
||||||
|
|
||||||
|
|
||||||
def make_consuming_repo(tempdir_factory, repo_source):
|
def make_consuming_repo(tempdir_factory, repo_source, local_hooks=None):
|
||||||
path = make_repo(tempdir_factory, repo_source)
|
path = make_repo(tempdir_factory, repo_source)
|
||||||
config = make_config_from_repo(path)
|
if local_hooks:
|
||||||
|
config = OrderedDict((
|
||||||
|
('repo', 'local'),
|
||||||
|
('hooks', local_hooks),
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
config = make_config_from_repo(path)
|
||||||
git_path = git_dir(tempdir_factory)
|
git_path = git_dir(tempdir_factory)
|
||||||
return add_config_to_repo(git_path, config)
|
return add_config_to_repo(git_path, config)
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ def repo_with_failing_hook(tempdir_factory):
|
||||||
yield git_path
|
yield git_path
|
||||||
|
|
||||||
|
|
||||||
def stage_a_file(filename='foo.py'):
|
def stage_a_file(filename):
|
||||||
cmd_output('touch', filename)
|
cmd_output('touch', filename)
|
||||||
cmd_output('git', 'add', filename)
|
cmd_output('git', 'add', filename)
|
||||||
|
|
||||||
|
|
@ -83,14 +83,15 @@ def _do_run(repo, args, environ={}):
|
||||||
runner = Runner(repo)
|
runner = Runner(repo)
|
||||||
write_mock = mock.Mock()
|
write_mock = mock.Mock()
|
||||||
write_fn = functools.partial(sys_stdout_write_wrapper, stream=write_mock)
|
write_fn = functools.partial(sys_stdout_write_wrapper, stream=write_mock)
|
||||||
ret = run(runner, args, write=write_fn, environ=environ)
|
with cwd(runner.git_root): # replicates Runner.create behaviour
|
||||||
|
ret = run(runner, args, write=write_fn, environ=environ)
|
||||||
printed = get_write_mock_output(write_mock)
|
printed = get_write_mock_output(write_mock)
|
||||||
return ret, printed
|
return ret, printed
|
||||||
|
|
||||||
|
|
||||||
def _test_run(repo, options, expected_outputs, expected_ret, stage):
|
def _test_run(repo, options, expected_outputs, expected_ret, file_to_stage):
|
||||||
if stage:
|
if file_to_stage:
|
||||||
stage_a_file()
|
stage_a_file(file_to_stage)
|
||||||
args = _get_opts(**options)
|
args = _get_opts(**options)
|
||||||
ret, printed = _do_run(repo, args)
|
ret, printed = _do_run(repo, args)
|
||||||
|
|
||||||
|
|
@ -112,14 +113,14 @@ def test_run_all_hooks_failing(
|
||||||
b'Fail\nfoo.py\n',
|
b'Fail\nfoo.py\n',
|
||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
True,
|
'foo.py',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_arbitrary_bytes_hook(tempdir_factory, mock_out_store_directory):
|
def test_arbitrary_bytes_hook(tempdir_factory, mock_out_store_directory):
|
||||||
git_path = make_consuming_repo(tempdir_factory, 'arbitrary_bytes_repo')
|
git_path = make_consuming_repo(tempdir_factory, 'arbitrary_bytes_repo')
|
||||||
with cwd(git_path):
|
with cwd(git_path):
|
||||||
_test_run(git_path, {}, (b'\xe2\x98\x83\xb2\n',), 1, True)
|
_test_run(git_path, {}, (b'\xe2\x98\x83\xb2\n',), 1, 'foo.py')
|
||||||
|
|
||||||
|
|
||||||
def test_hook_that_modifies_but_returns_zero(
|
def test_hook_that_modifies_but_returns_zero(
|
||||||
|
|
@ -147,30 +148,30 @@ def test_hook_that_modifies_but_returns_zero(
|
||||||
b'Files were modified by this hook.\n',
|
b'Files were modified by this hook.\n',
|
||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
True,
|
'foo.py',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
('options', 'outputs', 'expected_ret', 'stage'),
|
('options', 'outputs', 'expected_ret', 'file_to_stage'),
|
||||||
(
|
(
|
||||||
({}, (b'Bash hook', b'Passed'), 0, True),
|
({}, (b'Bash hook', b'Passed'), 0, 'foo.py'),
|
||||||
({'verbose': True}, (b'foo.py\nHello World',), 0, True),
|
({'verbose': True}, (b'foo.py\nHello World',), 0, 'foo.py'),
|
||||||
({'hook': 'bash_hook'}, (b'Bash hook', b'Passed'), 0, True),
|
({'hook': 'bash_hook'}, (b'Bash hook', b'Passed'), 0, 'foo.py'),
|
||||||
({'hook': 'nope'}, (b'No hook with id `nope`',), 1, True),
|
({'hook': 'nope'}, (b'No hook with id `nope`',), 1, 'foo.py'),
|
||||||
(
|
(
|
||||||
{'all_files': True, 'verbose': True},
|
{'all_files': True, 'verbose': True},
|
||||||
(b'foo.py',),
|
(b'foo.py',),
|
||||||
0,
|
0,
|
||||||
True,
|
'foo.py',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{'files': ('foo.py',), 'verbose': True},
|
{'files': ('foo.py',), 'verbose': True},
|
||||||
(b'foo.py',),
|
(b'foo.py',),
|
||||||
0,
|
0,
|
||||||
True,
|
'foo.py',
|
||||||
),
|
),
|
||||||
({}, (b'Bash hook', b'(no files to check)', b'Skipped'), 0, False),
|
({}, (b'Bash hook', b'(no files to check)', b'Skipped'), 0, None),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
def test_run(
|
def test_run(
|
||||||
|
|
@ -178,16 +179,17 @@ def test_run(
|
||||||
options,
|
options,
|
||||||
outputs,
|
outputs,
|
||||||
expected_ret,
|
expected_ret,
|
||||||
stage,
|
file_to_stage,
|
||||||
mock_out_store_directory,
|
mock_out_store_directory,
|
||||||
):
|
):
|
||||||
_test_run(repo_with_passing_hook, options, outputs, expected_ret, stage)
|
_test_run(repo_with_passing_hook, options, outputs, expected_ret,
|
||||||
|
file_to_stage)
|
||||||
|
|
||||||
|
|
||||||
def test_always_run(repo_with_passing_hook, mock_out_store_directory):
|
def test_always_run(repo_with_passing_hook, mock_out_store_directory):
|
||||||
with modify_config() as config:
|
with modify_config() as config:
|
||||||
config[0]['hooks'][0]['always_run'] = True
|
config[0]['hooks'][0]['always_run'] = True
|
||||||
_test_run(repo_with_passing_hook, {}, (b'Bash hook', b'Passed'), 0, False)
|
_test_run(repo_with_passing_hook, {}, (b'Bash hook', b'Passed'), 0, None)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -229,7 +231,7 @@ def test_no_stash(
|
||||||
expect_stash,
|
expect_stash,
|
||||||
mock_out_store_directory,
|
mock_out_store_directory,
|
||||||
):
|
):
|
||||||
stage_a_file()
|
stage_a_file('foo.py')
|
||||||
# Make unstaged changes
|
# Make unstaged changes
|
||||||
with open('foo.py', 'w') as foo_file:
|
with open('foo.py', 'w') as foo_file:
|
||||||
foo_file.write('import os\n')
|
foo_file.write('import os\n')
|
||||||
|
|
@ -323,7 +325,7 @@ def test_multiple_hooks_same_id(
|
||||||
# Add bash hook on there again
|
# Add bash hook on there again
|
||||||
with modify_config() as config:
|
with modify_config() as config:
|
||||||
config[0]['hooks'].append({'id': 'bash_hook'})
|
config[0]['hooks'].append({'id': 'bash_hook'})
|
||||||
stage_a_file()
|
stage_a_file('foo.py')
|
||||||
|
|
||||||
ret, output = _do_run(repo_with_passing_hook, _get_opts())
|
ret, output = _do_run(repo_with_passing_hook, _get_opts())
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
|
|
@ -352,7 +354,7 @@ def test_stdout_write_bug_py26(
|
||||||
with cwd(repo_with_failing_hook):
|
with cwd(repo_with_failing_hook):
|
||||||
with modify_config() as config:
|
with modify_config() as config:
|
||||||
config[0]['hooks'][0]['args'] = ['☃']
|
config[0]['hooks'][0]['args'] = ['☃']
|
||||||
stage_a_file()
|
stage_a_file('foo.py')
|
||||||
|
|
||||||
install(Runner(repo_with_failing_hook))
|
install(Runner(repo_with_failing_hook))
|
||||||
|
|
||||||
|
|
@ -489,7 +491,7 @@ def test_local_hook_for_stages(
|
||||||
{'hook_stage': hook_stage},
|
{'hook_stage': hook_stage},
|
||||||
expected_outputs=expected_output,
|
expected_outputs=expected_output,
|
||||||
expected_ret=0,
|
expected_ret=0,
|
||||||
stage=False
|
file_to_stage=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -521,7 +523,7 @@ def test_local_hook_passes(repo_with_passing_hook, mock_out_store_directory):
|
||||||
options={},
|
options={},
|
||||||
expected_outputs=[b''],
|
expected_outputs=[b''],
|
||||||
expected_ret=0,
|
expected_ret=0,
|
||||||
stage=False
|
file_to_stage=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -547,7 +549,7 @@ def test_local_hook_fails(repo_with_passing_hook, mock_out_store_directory):
|
||||||
options={},
|
options={},
|
||||||
expected_outputs=[b''],
|
expected_outputs=[b''],
|
||||||
expected_ret=1,
|
expected_ret=1,
|
||||||
stage=False,
|
file_to_stage=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -595,3 +597,31 @@ def test_unstaged_message_suppressed(
|
||||||
args = _get_opts(**opts)
|
args = _get_opts(**opts)
|
||||||
ret, printed = _do_run(modified_config_repo, args)
|
ret, printed = _do_run(modified_config_repo, args)
|
||||||
assert b'Your .pre-commit-config.yaml is unstaged.' not in printed
|
assert b'Your .pre-commit-config.yaml is unstaged.' not in printed
|
||||||
|
|
||||||
|
|
||||||
|
def test_invoke_precommit_in_subdir(
|
||||||
|
tempdir_factory, mock_out_store_directory,
|
||||||
|
):
|
||||||
|
subdir_hook = OrderedDict((
|
||||||
|
('id', 'subdir_bash_hook'),
|
||||||
|
('name', 'Selective Bash hook'),
|
||||||
|
('language', 'system'),
|
||||||
|
('entry', 'echo OK'),
|
||||||
|
('files', '^subdir/'),
|
||||||
|
))
|
||||||
|
git_path = make_consuming_repo(
|
||||||
|
tempdir_factory,
|
||||||
|
'script_hooks_repo',
|
||||||
|
local_hooks=[subdir_hook],
|
||||||
|
)
|
||||||
|
with cwd(git_path):
|
||||||
|
os.mkdir('subdir/')
|
||||||
|
with cwd('subdir/'):
|
||||||
|
cmd_output('touch', 'foo.py')
|
||||||
|
_test_run(
|
||||||
|
git_path,
|
||||||
|
options={'files': ('subdir/foo.py',), 'verbose': True},
|
||||||
|
expected_outputs=(b'foo.py',),
|
||||||
|
expected_ret=0,
|
||||||
|
file_to_stage=None,
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue