Add pre-commit try-repo

`try-repo` is useful for:
- Trying out a remote hook repository without needing to configure it.
- Testing a hook repository while developing it.
This commit is contained in:
Anthony Sottile 2017-10-07 15:13:53 -07:00
parent e8641ee0a3
commit 2c88791a7f
15 changed files with 254 additions and 110 deletions

View file

@ -7,6 +7,7 @@ from collections import OrderedDict
import pytest
import pre_commit.constants as C
from pre_commit import git
from pre_commit.clientlib import load_config
from pre_commit.commands.autoupdate import _update_repo
from pre_commit.commands.autoupdate import autoupdate
@ -21,7 +22,6 @@ from testing.fixtures import git_dir
from testing.fixtures import make_config_from_repo
from testing.fixtures import make_repo
from testing.fixtures import write_config
from testing.util import get_head_sha
from testing.util import get_resource_path
@ -66,10 +66,10 @@ def test_autoupdate_old_revision_broken(
cmd_output('git', 'mv', C.MANIFEST_FILE, 'nope.yaml')
cmd_output('git', 'commit', '-m', 'simulate old repo')
# Assume this is the revision the user's old repository was at
rev = get_head_sha(path)
rev = git.head_sha(path)
cmd_output('git', 'mv', 'nope.yaml', C.MANIFEST_FILE)
cmd_output('git', 'commit', '-m', 'move hooks file')
update_rev = get_head_sha(path)
update_rev = git.head_sha(path)
config['sha'] = rev
write_config('.', config)
@ -84,12 +84,12 @@ def test_autoupdate_old_revision_broken(
@pytest.yield_fixture
def out_of_date_repo(tempdir_factory):
path = make_repo(tempdir_factory, 'python_hooks_repo')
original_sha = get_head_sha(path)
original_sha = git.head_sha(path)
# Make a commit
with cwd(path):
cmd_output('git', 'commit', '--allow-empty', '-m', 'foo')
head_sha = get_head_sha(path)
head_sha = git.head_sha(path)
yield auto_namedtuple(
path=path, original_sha=original_sha, head_sha=head_sha,
@ -225,7 +225,7 @@ def test_autoupdate_tags_only(
@pytest.yield_fixture
def hook_disappearing_repo(tempdir_factory):
path = make_repo(tempdir_factory, 'python_hooks_repo')
original_sha = get_head_sha(path)
original_sha = git.head_sha(path)
with cwd(path):
shutil.copy(

View file

@ -20,12 +20,12 @@ from pre_commit.runner import Runner
from pre_commit.util import cmd_output
from pre_commit.util import cwd
from pre_commit.util import make_executable
from testing.auto_namedtuple import auto_namedtuple
from testing.fixtures import add_config_to_repo
from testing.fixtures import make_consuming_repo
from testing.fixtures import modify_config
from testing.fixtures import read_config
from testing.util import cmd_output_mocked_pre_commit_home
from testing.util import run_opts
from testing.util import xfailif_no_symlink
@ -48,34 +48,6 @@ def stage_a_file(filename='foo.py'):
cmd_output('git', 'add', filename)
def _get_opts(
all_files=False,
files=(),
color=False,
verbose=False,
hook=None,
origin='',
source='',
hook_stage='commit',
show_diff_on_failure=False,
commit_msg_filename='',
):
# These are mutually exclusive
assert not (all_files and files)
return auto_namedtuple(
all_files=all_files,
files=files,
color=color,
verbose=verbose,
hook=hook,
origin=origin,
source=source,
hook_stage=hook_stage,
show_diff_on_failure=show_diff_on_failure,
commit_msg_filename=commit_msg_filename,
)
def _do_run(cap_out, repo, args, environ={}, config_file=C.CONFIG_FILE):
runner = Runner(repo, config_file)
with cwd(runner.git_root): # replicates Runner.create behaviour
@ -90,7 +62,7 @@ def _test_run(
):
if stage:
stage_a_file()
args = _get_opts(**opts)
args = run_opts(**opts)
ret, printed = _do_run(cap_out, repo, args, config_file=config_file)
assert ret == expected_ret, (ret, expected_ret, printed)
@ -161,7 +133,7 @@ def test_types_hook_repository(
with cwd(git_path):
stage_a_file('bar.py')
stage_a_file('bar.notpy')
ret, printed = _do_run(cap_out, git_path, _get_opts())
ret, printed = _do_run(cap_out, git_path, run_opts())
assert ret == 1
assert b'bar.py' in printed
assert b'bar.notpy' not in printed
@ -177,7 +149,7 @@ def test_exclude_types_hook_repository(
make_executable('exe')
cmd_output('git', 'add', 'exe')
stage_a_file('bar.py')
ret, printed = _do_run(cap_out, git_path, _get_opts())
ret, printed = _do_run(cap_out, git_path, run_opts())
assert ret == 1
assert b'bar.py' in printed
assert b'exe' not in printed
@ -191,7 +163,7 @@ def test_global_exclude(cap_out, tempdir_factory, mock_out_store_directory):
open('foo.py', 'a').close()
open('bar.py', 'a').close()
cmd_output('git', 'add', '.')
ret, printed = _do_run(cap_out, git_path, _get_opts(verbose=True))
ret, printed = _do_run(cap_out, git_path, run_opts(verbose=True))
assert ret == 0
# Does not contain foo.py since it was excluded
expected = b'hookid: bash_hook\n\nbar.py\nHello World\n\n'
@ -332,7 +304,7 @@ def test_origin_source_error_msg(
repo_with_passing_hook, origin, source, expect_failure,
mock_out_store_directory, cap_out,
):
args = _get_opts(origin=origin, source=source)
args = run_opts(origin=origin, source=source)
ret, printed = _do_run(cap_out, repo_with_passing_hook, args)
warning_msg = b'Specify both --origin and --source.'
if expect_failure:
@ -350,7 +322,7 @@ def test_has_unmerged_paths(in_merge_conflict):
def test_merge_conflict(cap_out, in_merge_conflict, mock_out_store_directory):
ret, printed = _do_run(cap_out, in_merge_conflict, _get_opts())
ret, printed = _do_run(cap_out, in_merge_conflict, run_opts())
assert ret == 1
assert b'Unmerged files. Resolve before committing.' in printed
@ -363,7 +335,7 @@ def test_merge_conflict_modified(
with open('dummy', 'w') as dummy_file:
dummy_file.write('bar\nbaz\n')
ret, printed = _do_run(cap_out, in_merge_conflict, _get_opts())
ret, printed = _do_run(cap_out, in_merge_conflict, run_opts())
assert ret == 1
assert b'Unmerged files. Resolve before committing.' in printed
@ -372,7 +344,7 @@ def test_merge_conflict_resolved(
cap_out, in_merge_conflict, mock_out_store_directory,
):
cmd_output('git', 'add', '.')
ret, printed = _do_run(cap_out, in_merge_conflict, _get_opts())
ret, printed = _do_run(cap_out, in_merge_conflict, run_opts())
for msg in (
b'Checking merge-conflict files only.', b'Bash hook', b'Passed',
):
@ -415,7 +387,7 @@ def test_get_skips(environ, expected_output):
def test_skip_hook(cap_out, repo_with_passing_hook, mock_out_store_directory):
ret, printed = _do_run(
cap_out, repo_with_passing_hook, _get_opts(), {'SKIP': 'bash_hook'},
cap_out, repo_with_passing_hook, run_opts(), {'SKIP': 'bash_hook'},
)
for msg in (b'Bash hook', b'Skipped'):
assert msg in printed
@ -425,7 +397,7 @@ def test_hook_id_not_in_non_verbose_output(
cap_out, repo_with_passing_hook, mock_out_store_directory,
):
ret, printed = _do_run(
cap_out, repo_with_passing_hook, _get_opts(verbose=False),
cap_out, repo_with_passing_hook, run_opts(verbose=False),
)
assert b'[bash_hook]' not in printed
@ -434,7 +406,7 @@ def test_hook_id_in_verbose_output(
cap_out, repo_with_passing_hook, mock_out_store_directory,
):
ret, printed = _do_run(
cap_out, repo_with_passing_hook, _get_opts(verbose=True),
cap_out, repo_with_passing_hook, run_opts(verbose=True),
)
assert b'[bash_hook] Bash hook' in printed
@ -448,7 +420,7 @@ def test_multiple_hooks_same_id(
config['repos'][0]['hooks'].append({'id': 'bash_hook'})
stage_a_file()
ret, output = _do_run(cap_out, repo_with_passing_hook, _get_opts())
ret, output = _do_run(cap_out, repo_with_passing_hook, run_opts())
assert ret == 0
assert output.count(b'Bash hook') == 2
@ -684,7 +656,7 @@ def modified_config_repo(repo_with_passing_hook):
def test_error_with_unstaged_config(
cap_out, modified_config_repo, mock_out_store_directory,
):
args = _get_opts()
args = run_opts()
ret, printed = _do_run(cap_out, modified_config_repo, args)
assert b'Your .pre-commit-config.yaml is unstaged.' in printed
assert ret == 1
@ -696,7 +668,7 @@ def test_error_with_unstaged_config(
def test_no_unstaged_error_with_all_files_or_files(
cap_out, modified_config_repo, mock_out_store_directory, opts,
):
args = _get_opts(**opts)
args = run_opts(**opts)
ret, printed = _do_run(cap_out, modified_config_repo, args)
assert b'Your .pre-commit-config.yaml is unstaged.' not in printed
@ -742,7 +714,7 @@ def test_pass_filenames(
config['repos'][0]['hooks'][0]['args'] = hook_args
stage_a_file()
ret, printed = _do_run(
cap_out, repo_with_passing_hook, _get_opts(verbose=True),
cap_out, repo_with_passing_hook, run_opts(verbose=True),
)
assert expected_out + b'\nHello World' in printed
assert (b'foo.py' in printed) == pass_filenames
@ -758,7 +730,7 @@ def test_fail_fast(
config['repos'][0]['hooks'] *= 2
stage_a_file()
ret, printed = _do_run(cap_out, repo_with_failing_hook, _get_opts())
ret, printed = _do_run(cap_out, repo_with_failing_hook, run_opts())
# it should have only run one hook
assert printed.count(b'Failing hook') == 1

View file

@ -0,0 +1,71 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import re
from pre_commit.commands.try_repo import try_repo
from pre_commit.util import cmd_output
from pre_commit.util import cwd
from testing.auto_namedtuple import auto_namedtuple
from testing.fixtures import git_dir
from testing.fixtures import make_repo
from testing.util import run_opts
def try_repo_opts(repo, ref=None, **kwargs):
return auto_namedtuple(repo=repo, ref=ref, **run_opts(**kwargs)._asdict())
def _get_out(cap_out):
out = cap_out.get().replace('\r\n', '\n')
out = re.sub('\[INFO\].+\n', '', out)
start, using_config, config, rest = out.split('=' * 79 + '\n')
assert start == ''
assert using_config == 'Using config:\n'
return config, rest
def _run_try_repo(tempdir_factory, **kwargs):
repo = make_repo(tempdir_factory, 'modified_file_returns_zero_repo')
with cwd(git_dir(tempdir_factory)):
open('test-file', 'a').close()
cmd_output('git', 'add', '.')
assert not try_repo(try_repo_opts(repo, **kwargs))
def test_try_repo_repo_only(cap_out, tempdir_factory):
_run_try_repo(tempdir_factory, verbose=True)
config, rest = _get_out(cap_out)
assert re.match(
'^repos:\n'
'- repo: .+\n'
' sha: .+\n'
' hooks:\n'
' - id: bash_hook\n'
' - id: bash_hook2\n'
' - id: bash_hook3\n$',
config,
)
assert rest == (
'[bash_hook] Bash hook................................(no files to check)Skipped\n' # noqa
'[bash_hook2] Bash hook...................................................Passed\n' # noqa
'hookid: bash_hook2\n'
'\n'
'test-file\n'
'\n'
'[bash_hook3] Bash hook...............................(no files to check)Skipped\n' # noqa
)
def test_try_repo_with_specific_hook(cap_out, tempdir_factory):
_run_try_repo(tempdir_factory, hook='bash_hook', verbose=True)
config, rest = _get_out(cap_out)
assert re.match(
'^repos:\n'
'- repo: .+\n'
' sha: .+\n'
' hooks:\n'
' - id: bash_hook\n$',
config,
)
assert rest == '[bash_hook] Bash hook................................(no files to check)Skipped\n' # noqa