This commit is contained in:
Thierry Deo 2017-02-15 21:27:49 +00:00 committed by GitHub
commit 908d45a144
17 changed files with 133 additions and 81 deletions

View file

@ -30,7 +30,7 @@ def _update_repo(repo_config, runner, tags_only):
Args:
repo_config - A config for a repository
"""
repo = Repository.create(repo_config, runner.store)
repo = Repository.create(repo_config, runner.store, runner.git_root)
with cwd(repo.repo_path_getter.repo_path):
cmd_output('git', 'fetch')
@ -51,7 +51,7 @@ def _update_repo(repo_config, runner, tags_only):
# Construct a new config with the head sha
new_config = OrderedDict(repo_config)
new_config['sha'] = rev
new_repo = Repository.create(new_config, runner.store)
new_repo = Repository.create(new_config, runner.store, runner.git_root)
# See if any of our hooks were deleted with the new commits
hooks = {hook['id'] for hook in repo.repo_config['hooks']}

View file

@ -58,6 +58,7 @@ def install_environment(
repo_cmd_runner,
version='default',
additional_dependencies=(),
is_local_hook=False,
): # pragma: windows no cover
assert repo_cmd_runner.exists('Dockerfile'), (
'No Dockerfile was found in the hook repository'

View file

@ -48,6 +48,7 @@ def install_environment(
repo_cmd_runner,
version='default',
additional_dependencies=(),
is_local_hook=False,
):
helpers.assert_version_default('golang', version)
directory = repo_cmd_runner.path(

View file

@ -37,6 +37,7 @@ def install_environment(
repo_cmd_runner,
version='default',
additional_dependencies=(),
is_local_hook=False,
): # pragma: windows no cover
additional_dependencies = tuple(additional_dependencies)
assert repo_cmd_runner.exists('package.json')

View file

@ -13,6 +13,7 @@ def install_environment(
repo_cmd_runner,
version='default',
additional_dependencies=(),
is_local_hook=False,
):
"""Installation for pcre type is a noop."""
raise AssertionError('Cannot install pcre repo.')

View file

@ -58,6 +58,7 @@ def install_environment(
repo_cmd_runner,
version='default',
additional_dependencies=(),
is_local_hook=False,
):
additional_dependencies = tuple(additional_dependencies)
directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
@ -73,10 +74,12 @@ def install_environment(
else:
venv_cmd.extend(['-p', os.path.realpath(sys.executable)])
repo_cmd_runner.run(venv_cmd, cwd='/')
to_install = () if is_local_hook else ('.',)
to_install += additional_dependencies
with in_env(repo_cmd_runner, version):
helpers.run_setup_cmd(
repo_cmd_runner,
('pip', 'install', '.') + additional_dependencies,
('pip', 'install') + to_install,
)

View file

@ -100,6 +100,7 @@ def install_environment(
repo_cmd_runner,
version='default',
additional_dependencies=(),
is_local_hook=False,
): # pragma: windows no cover
additional_dependencies = tuple(additional_dependencies)
directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
@ -115,15 +116,18 @@ def install_environment(
_install_ruby(repo_cmd_runner, version)
# Need to call this after installing to set up the shims
helpers.run_setup_cmd(repo_cmd_runner, ('rbenv', 'rehash'))
helpers.run_setup_cmd(
repo_cmd_runner,
('gem', 'build') + repo_cmd_runner.star('.gemspec'),
)
if not is_local_hook:
helpers.run_setup_cmd(
repo_cmd_runner,
('gem', 'build') + repo_cmd_runner.star('.gemspec'),
)
to_install = () if is_local_hook else repo_cmd_runner.star('.gem')
to_install += additional_dependencies
helpers.run_setup_cmd(
repo_cmd_runner,
(
('gem', 'install', '--no-ri', '--no-rdoc') +
repo_cmd_runner.star('.gem') + additional_dependencies
to_install
),
)

View file

@ -11,6 +11,7 @@ def install_environment(
repo_cmd_runner,
version='default',
additional_dependencies=(),
is_local_hook=False,
):
"""Installation for script type is a noop."""
raise AssertionError('Cannot install script repo.')

View file

@ -32,6 +32,7 @@ def install_environment(
repo_cmd_runner,
version='default',
additional_dependencies=(),
is_local_hook=False,
): # pragma: windows no cover
helpers.assert_version_default('swift', version)
helpers.assert_no_additional_deps('swift', additional_dependencies)

View file

@ -11,6 +11,7 @@ def install_environment(
repo_cmd_runner,
version='default',
additional_dependencies=(),
is_local_hook=False,
):
"""Installation for system type is a noop."""
raise AssertionError('Cannot install system repo.')

View file

@ -38,13 +38,14 @@ class Repository(object):
self.__installed = False
@classmethod
def create(cls, config, store):
def create(cls, config, store, owner):
repo_path_getter = store.get_repo_path_getter(
config['repo'],
owner if is_local_hooks(config) else config['sha'],
)
if is_local_hooks(config):
return LocalRepository(config)
return LocalRepository(config, repo_path_getter)
else:
repo_path_getter = store.get_repo_path_getter(
config['repo'], config['sha']
)
return cls(config, repo_path_getter)
@cached_property
@ -104,6 +105,16 @@ class Repository(object):
def cmd_runner(self):
return PrefixedCommandRunner(self.repo_path_getter.repo_path)
@cached_property
def local_cmd_runner(self):
return self.cmd_runner
def language_cmd_runner(self, language):
if language in ['script', 'system']:
return self.local_cmd_runner
else:
return self.cmd_runner
def require_installed(self):
if self.__installed:
return
@ -179,6 +190,7 @@ class Repository(object):
language.install_environment(
self.cmd_runner, language_version,
self.additional_dependencies[language_name][language_version],
is_local_hooks(self.repo_config),
)
# Write our state to indicate we're installed
write_state(venv, language_name, language_version)
@ -192,14 +204,11 @@ class Repository(object):
"""
self.require_installed()
return languages[hook['language']].run_hook(
self.cmd_runner, hook, file_args,
self.language_cmd_runner(hook['language']), hook, file_args,
)
class LocalRepository(Repository):
def __init__(self, repo_config):
super(LocalRepository, self).__init__(repo_config, None)
@cached_property
def hooks(self):
return tuple(
@ -208,13 +217,9 @@ class LocalRepository(Repository):
)
@cached_property
def cmd_runner(self):
def local_cmd_runner(self):
return PrefixedCommandRunner(git.get_root())
@cached_property
def manifest(self):
raise NotImplementedError
class _UniqueList(list):
def __init__(self):

View file

@ -41,7 +41,9 @@ class Runner(object):
def repositories(self):
"""Returns a tuple of the configured repositories."""
config = load_config(self.config_file_path)
repositories = tuple(Repository.create(x, self.store) for x in config)
repositories = tuple(
Repository.create(x, self.store, self.git_root) for x in config
)
for repository in repositories:
repository.require_installed()
return repositories

View file

@ -9,6 +9,7 @@ import tempfile
from cached_property import cached_property
from pre_commit.clientlib.validate_config import _LOCAL_HOOKS_MAGIC_REPO_STRING
from pre_commit.prefixed_command_runner import PrefixedCommandRunner
from pre_commit.util import clean_path_on_failure
from pre_commit.util import cmd_output
@ -43,7 +44,7 @@ class Store(object):
@cached_property
def repo_path(self):
return self._store.clone(self._repo, self._ref)
return self._store.initialize_repo(self._repo, self._ref)
def __init__(self, directory=None):
if directory is None:
@ -97,39 +98,44 @@ class Store(object):
self._create()
self.__created = True
def clone(self, url, ref):
"""Clone the given url and checkout the specific ref."""
def initialize_repo(self, repo, sha):
"""Initializes the repository by cloning a remote or preparing an
empty local folder for local hooks. If the repo if 'local',
the sha used is the path to git_root of this repo so that several
local hooks for different projects are separated."""
self.require_created()
# Check if we already exist
with sqlite3.connect(self.db_path) as db:
result = db.execute(
'SELECT path FROM repos WHERE repo = ? AND ref = ?',
[url, ref],
[repo, sha],
).fetchone()
if result:
return result[0]
logger.info('Initializing environment for {}.'.format(url))
logger.info('Initializing environment for {}.'.format(repo))
dir = tempfile.mkdtemp(prefix='repo', dir=self.directory)
with clean_path_on_failure(dir):
cmd_output(
'git', 'clone', '--no-checkout', url, dir, env=no_git_env(),
)
with cwd(dir):
cmd_output('git', 'reset', ref, '--hard', env=no_git_env())
if repo != _LOCAL_HOOKS_MAGIC_REPO_STRING:
with clean_path_on_failure(dir):
cmd_output(
'git', 'clone', '--no-checkout', repo, dir,
env=no_git_env(),
)
with cwd(dir):
cmd_output('git', 'reset', sha, '--hard', env=no_git_env())
# Update our db with the created repo
with sqlite3.connect(self.db_path) as db:
db.execute(
'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)',
[url, ref, dir],
[repo, sha, dir],
)
return dir
def get_repo_path_getter(self, repo, ref):
return self.RepoPathGetter(repo, ref, self)
def get_repo_path_getter(self, repo, sha):
return self.RepoPathGetter(repo, sha, self)
@cached_property
def cmd_runner(self):

View file

@ -66,14 +66,14 @@ def modify_config(path='.', commit=True):
cmd_output('git', 'commit', '-am', 'update config', cwd=path)
def config_with_local_hooks():
def config_with_local_hooks(language='pcre'):
return OrderedDict((
('repo', 'local'),
('hooks', [OrderedDict((
('id', 'do_not_commit'),
('name', 'Block if "DO NOT COMMIT" is found'),
('entry', 'DO NOT COMMIT'),
('language', 'pcre'),
('id', language),
('name', language),
('entry', language),
('language', language),
('files', '^(.*)$'),
))])
))

View file

@ -11,10 +11,15 @@ from pre_commit.languages.all import languages
@pytest.mark.parametrize('language', all_languages)
def test_install_environment_argspec(language):
expected_argspec = inspect.ArgSpec(
args=['repo_cmd_runner', 'version', 'additional_dependencies'],
args=[
'repo_cmd_runner',
'version',
'additional_dependencies',
'is_local_hook',
],
varargs=None,
keywords=None,
defaults=('default', ()),
defaults=('default', (), False),
)
argspec = inspect.getargspec(languages[language].install_environment)
assert argspec == expected_argspec

View file

@ -50,7 +50,7 @@ def _test_hook_repo(
):
path = make_repo(tempdir_factory, repo_path)
config = make_config_from_repo(path, **(config_kwargs or {}))
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
hook_dict = [
hook for repo_hook_id, hook in repo.hooks if repo_hook_id == hook_id
][0]
@ -108,7 +108,7 @@ def test_switch_language_versions_doesnt_clobber(tempdir_factory, store):
config = make_config_from_repo(
path, hooks=[{'id': 'python3-hook', 'language_version': version}],
)
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
hook_dict, = [
hook
for repo_hook_id, hook in repo.hooks
@ -462,7 +462,7 @@ def test_repo_url(mock_repo_config):
def test_languages(tempdir_factory, store):
path = make_repo(tempdir_factory, 'python_hooks_repo')
config = make_config_from_repo(path)
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
assert repo.languages == {('python', 'default')}
@ -471,7 +471,7 @@ def test_additional_dependencies(tempdir_factory, store):
path = make_repo(tempdir_factory, 'python_hooks_repo')
config = make_config_from_repo(path)
config['hooks'][0]['additional_dependencies'] = ['pep8']
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
assert repo.additional_dependencies['python']['default'] == ['pep8']
@ -483,7 +483,7 @@ def test_additional_dependencies_duplicated(
config = make_config_from_repo(path)
config['hooks'][0]['additional_dependencies'] = [
'thread_safe', 'tins', 'thread_safe']
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
assert repo.additional_dependencies['ruby']['default'] == [
'thread_safe', 'tins']
@ -493,7 +493,7 @@ def test_additional_python_dependencies_installed(tempdir_factory, store):
path = make_repo(tempdir_factory, 'python_hooks_repo')
config = make_config_from_repo(path)
config['hooks'][0]['additional_dependencies'] = ['mccabe']
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
repo.require_installed()
with python.in_env(repo.cmd_runner, 'default'):
output = cmd_output('pip', 'freeze', '-l')[1]
@ -505,11 +505,11 @@ def test_additional_dependencies_roll_forward(tempdir_factory, store):
path = make_repo(tempdir_factory, 'python_hooks_repo')
config = make_config_from_repo(path)
# Run the repo once without additional_dependencies
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
repo.require_installed()
# Now run it with additional_dependencies
config['hooks'][0]['additional_dependencies'] = ['mccabe']
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
repo.require_installed()
# We should see our additional dependency installed
with python.in_env(repo.cmd_runner, 'default'):
@ -526,7 +526,7 @@ def test_additional_ruby_dependencies_installed(
path = make_repo(tempdir_factory, 'ruby_hooks_repo')
config = make_config_from_repo(path)
config['hooks'][0]['additional_dependencies'] = ['thread_safe', 'tins']
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
repo.require_installed()
with ruby.in_env(repo.cmd_runner, 'default'):
output = cmd_output('gem', 'list', '--local')[1]
@ -544,7 +544,7 @@ def test_additional_node_dependencies_installed(
config = make_config_from_repo(path)
# Careful to choose a small package that's not depped by npm
config['hooks'][0]['additional_dependencies'] = ['lodash']
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
repo.require_installed()
with node.in_env(repo.cmd_runner, 'default'):
cmd_output('npm', 'config', 'set', 'global', 'true')
@ -561,7 +561,7 @@ def test_additional_golang_dependencies_installed(
# A small go package
deps = ['github.com/golang/example/hello']
config['hooks'][0]['additional_dependencies'] = deps
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
repo.require_installed()
binaries = os.listdir(repo.cmd_runner.path(
helpers.environment_dir(golang.ENVIRONMENT_DIR, 'default'), 'bin',
@ -571,10 +571,38 @@ def test_additional_golang_dependencies_installed(
assert 'hello' in binaries
@skipif_slowtests_false
@xfailif_windows_no_ruby
@pytest.mark.integration
def test_install_local_ruby_hook(
tempdir_factory, store,
): # pragma: no cover (non-windows)
config = config_with_local_hooks('ruby')
config['hooks'][0]['additional_dependencies'] = ['thread_safe']
repo = Repository.create(config, store, '/path/to/repo/')
repo.require_installed()
with ruby.in_env(repo.cmd_runner, 'default'):
output = cmd_output('gem', 'list', '--local')[1]
assert 'thread_safe' in output
@pytest.mark.integration
def test_install_local_python_hook(
tempdir_factory, store,
): # pragma: no cover (non-windows)
config = config_with_local_hooks('python')
config['hooks'][0]['additional_dependencies'] = ['mccabe']
repo = Repository.create(config, store, '/path/to/repo/')
repo.require_installed()
with python.in_env(repo.cmd_runner, 'default'):
output = cmd_output('pip', 'freeze', '-l')[1]
assert 'mccabe' in output
def test_reinstall(tempdir_factory, store, log_info_mock):
path = make_repo(tempdir_factory, 'python_hooks_repo')
config = make_config_from_repo(path)
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
repo.require_installed()
# We print some logging during clone (1) + install (3)
assert log_info_mock.call_count == 4
@ -583,7 +611,7 @@ def test_reinstall(tempdir_factory, store, log_info_mock):
repo.require_installed()
assert log_info_mock.call_count == 0
# Reinstall on another run should not trigger another install
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
repo.require_installed()
assert log_info_mock.call_count == 0
@ -592,7 +620,7 @@ def test_control_c_control_c_on_install(tempdir_factory, store):
"""Regression test for #186."""
path = make_repo(tempdir_factory, 'python_hooks_repo')
config = make_config_from_repo(path)
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
hook = repo.hooks[0][1]
class MyKeyboardInterrupt(KeyboardInterrupt):
@ -629,7 +657,7 @@ def test_really_long_file_paths(tempdir_factory, store):
config = make_config_from_repo(path)
with cwd(really_long_path):
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
repo.require_installed()
@ -638,11 +666,11 @@ def test_config_overrides_repo_specifics(tempdir_factory, store):
path = make_repo(tempdir_factory, 'script_hooks_repo')
config = make_config_from_repo(path)
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
assert repo.hooks[0][1]['files'] == ''
# Set the file regex to something else
config['hooks'][0]['files'] = '\\.sh$'
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
assert repo.hooks[0][1]['files'] == '\\.sh$'
@ -662,28 +690,20 @@ def test_tags_on_repositories(in_tmpdir, tempdir_factory, store):
)
repo_1 = Repository.create(
make_config_from_repo(git_dir_1, sha=tag), store,
make_config_from_repo(git_dir_1, sha=tag), store, git_dir_1
)
ret = repo_1.run_hook(repo_1.hooks[0][1], ['-L'])
assert ret[0] == 0
assert ret[1].strip() == _norm_pwd(in_tmpdir)
repo_2 = Repository.create(
make_config_from_repo(git_dir_2, sha=tag), store,
make_config_from_repo(git_dir_2, sha=tag), store, git_dir_2
)
ret = repo_2.run_hook(repo_2.hooks[0][1], ['bar'])
assert ret[0] == 0
assert ret[1] == b'bar\nHello World\n'
def test_local_repository():
config = config_with_local_hooks()
local_repo = Repository.create(config, 'dummy')
with pytest.raises(NotImplementedError):
local_repo.manifest
assert len(local_repo.hooks) == 1
@pytest.yield_fixture
def fake_log_handler():
handler = mock.Mock(level=logging.INFO)
@ -697,7 +717,7 @@ def test_hook_id_not_present(tempdir_factory, store, fake_log_handler):
path = make_repo(tempdir_factory, 'script_hooks_repo')
config = make_config_from_repo(path)
config['hooks'][0]['id'] = 'i-dont-exist'
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
with pytest.raises(SystemExit):
repo.install()
assert fake_log_handler.handle.call_args[0][0].msg == (
@ -712,7 +732,7 @@ def test_too_new_version(tempdir_factory, store, fake_log_handler):
with modify_manifest(path) as manifest:
manifest[0]['minimum_pre_commit_version'] = '999.0.0'
config = make_config_from_repo(path)
repo = Repository.create(config, store)
repo = Repository.create(config, store, path)
with pytest.raises(SystemExit):
repo.install()
msg = fake_log_handler.handle.call_args[0][0].msg
@ -734,4 +754,4 @@ def test_versions_ok(tempdir_factory, store, version):
manifest[0]['minimum_pre_commit_version'] = version
config = make_config_from_repo(path)
# Should succeed
Repository.create(config, store).install()
Repository.create(config, store, path).install()

View file

@ -79,14 +79,14 @@ def test_does_not_recreate_if_directory_already_exists(store):
assert not os.path.exists(os.path.join(store.directory, 'README'))
def test_clone(store, tempdir_factory, log_info_mock):
def test_initialize_repo(store, tempdir_factory, log_info_mock):
path = git_dir(tempdir_factory)
with cwd(path):
cmd_output('git', 'commit', '--allow-empty', '-m', 'foo')
sha = get_head_sha(path)
cmd_output('git', 'commit', '--allow-empty', '-m', 'bar')
ret = store.clone(path, sha)
ret = store.initialize_repo(path, sha)
# Should have printed some stuff
assert log_info_mock.call_args_list[0][0][0].startswith(
'Initializing environment for '
@ -98,7 +98,7 @@ def test_clone(store, tempdir_factory, log_info_mock):
# Directory should start with `repo`
_, dirname = os.path.split(ret)
assert dirname.startswith('repo')
# Should be checked out to the sha we specified
# Should be checked out to the sha we specifieds
assert get_head_sha(ret) == sha
# Assert there's an entry in the sqlite db for this
@ -110,11 +110,11 @@ def test_clone(store, tempdir_factory, log_info_mock):
assert path == ret
def test_clone_cleans_up_on_checkout_failure(store):
def test_initialize_repo_cleans_up_on_checkout_failure(store):
try:
# This raises an exception because you can't clone something that
# doesn't exist!
store.clone('/i_dont_exist_lol', 'fake_sha')
store.initialize_repo('/i_dont_exist_lol', 'fake_sha')
except Exception as e:
assert '/i_dont_exist_lol' in five.text(e)
@ -130,7 +130,7 @@ def test_has_cmd_runner_at_directory(store):
assert ret.prefix_dir == store.directory + os.sep
def test_clone_when_repo_already_exists(store):
def test_initialize_repo_when_repo_already_exists(store):
# Create an entry in the sqlite db that makes it look like the repo has
# been cloned.
store.require_created()
@ -141,7 +141,7 @@ def test_clone_when_repo_already_exists(store):
'VALUES ("fake_repo", "fake_ref", "fake_path")'
)
assert store.clone('fake_repo', 'fake_ref') == 'fake_path'
assert store.initialize_repo('fake_repo', 'fake_ref') == 'fake_path'
def test_require_created_when_directory_exists_but_not_db(store):