mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-01-13 12:30:08 -06:00
Refactor Repository to be more functional
This commit is contained in:
@@ -31,6 +31,70 @@ _pre_commit_version = pkg_resources.parse_version(
|
||||
INSTALLED_STATE_VERSION = '1'
|
||||
|
||||
|
||||
def _state(additional_deps):
|
||||
return {'additional_dependencies': sorted(additional_deps)}
|
||||
|
||||
|
||||
def _state_filename(cmd_runner, venv):
|
||||
return cmd_runner.path(venv, '.install_state_v' + INSTALLED_STATE_VERSION)
|
||||
|
||||
|
||||
def _read_installed_state(cmd_runner, venv):
|
||||
filename = _state_filename(cmd_runner, venv)
|
||||
if not os.path.exists(filename):
|
||||
return None
|
||||
else:
|
||||
return json.loads(io.open(filename).read())
|
||||
|
||||
|
||||
def _write_installed_state(cmd_runner, venv, state):
|
||||
state_filename = _state_filename(cmd_runner, venv)
|
||||
staging = state_filename + 'staging'
|
||||
with io.open(staging, 'w') as state_file:
|
||||
state_file.write(five.to_text(json.dumps(state)))
|
||||
# Move the file into place atomically to indicate we've installed
|
||||
os.rename(staging, state_filename)
|
||||
|
||||
|
||||
def _installed(cmd_runner, language_name, language_version, additional_deps):
|
||||
language = languages[language_name]
|
||||
venv = environment_dir(language.ENVIRONMENT_DIR, language_version)
|
||||
return (
|
||||
venv is None or
|
||||
_read_installed_state(cmd_runner, venv) == _state(additional_deps)
|
||||
)
|
||||
|
||||
|
||||
def _install_all(venvs, repo_url):
|
||||
"""Tuple of (cmd_runner, language, version, deps)"""
|
||||
need_installed = tuple(
|
||||
(cmd_runner, language_name, version, deps)
|
||||
for cmd_runner, language_name, version, deps in venvs
|
||||
if not _installed(cmd_runner, language_name, version, deps)
|
||||
)
|
||||
|
||||
if need_installed:
|
||||
logger.info(
|
||||
'Installing environment for {}.'.format(repo_url)
|
||||
)
|
||||
logger.info('Once installed this environment will be reused.')
|
||||
logger.info('This may take a few minutes...')
|
||||
|
||||
for cmd_runner, language_name, version, deps in need_installed:
|
||||
language = languages[language_name]
|
||||
venv = environment_dir(language.ENVIRONMENT_DIR, version)
|
||||
|
||||
# There's potentially incomplete cleanup from previous runs
|
||||
# Clean it up!
|
||||
if cmd_runner.exists(venv):
|
||||
shutil.rmtree(cmd_runner.path(venv))
|
||||
|
||||
language.install_environment(cmd_runner, version, deps)
|
||||
# Write our state to indicate we're installed
|
||||
state = _state(deps)
|
||||
_write_installed_state(cmd_runner, venv, state)
|
||||
|
||||
|
||||
class Repository(object):
|
||||
def __init__(self, repo_config, repo_path_getter):
|
||||
self.repo_config = repo_config
|
||||
@@ -48,24 +112,24 @@ class Repository(object):
|
||||
return cls(config, repo_path_getter)
|
||||
|
||||
@cached_property
|
||||
def repo_url(self):
|
||||
return self.repo_config['repo']
|
||||
def _cmd_runner(self):
|
||||
return PrefixedCommandRunner(self.repo_path_getter.repo_path)
|
||||
|
||||
@cached_property
|
||||
def languages(self):
|
||||
return {
|
||||
(hook['language'], hook['language_version'])
|
||||
for _, hook in self.hooks
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def additional_dependencies(self):
|
||||
dep_dict = defaultdict(lambda: defaultdict(_UniqueList))
|
||||
def _venvs(self):
|
||||
deps_dict = defaultdict(_UniqueList)
|
||||
for _, hook in self.hooks:
|
||||
dep_dict[hook['language']][hook['language_version']].update(
|
||||
deps_dict[(hook['language'], hook['language_version'])].update(
|
||||
hook.get('additional_dependencies', []),
|
||||
)
|
||||
return dep_dict
|
||||
ret = []
|
||||
for (language, version), deps in deps_dict.items():
|
||||
ret.append((self._cmd_runner, language, version, deps))
|
||||
return tuple(ret)
|
||||
|
||||
@cached_property
|
||||
def manifest(self):
|
||||
return Manifest(self.repo_path_getter, self.repo_config['repo'])
|
||||
|
||||
@cached_property
|
||||
def hooks(self):
|
||||
@@ -96,92 +160,10 @@ class Repository(object):
|
||||
for hook in self.repo_config['hooks']
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def manifest(self):
|
||||
return Manifest(self.repo_path_getter, self.repo_url)
|
||||
|
||||
@cached_property
|
||||
def cmd_runner(self):
|
||||
return PrefixedCommandRunner(self.repo_path_getter.repo_path)
|
||||
|
||||
def require_installed(self):
|
||||
if self.__installed:
|
||||
return
|
||||
|
||||
self.install()
|
||||
self.__installed = True
|
||||
|
||||
def install(self):
|
||||
"""Install the hook repository."""
|
||||
def state(language_name, language_version):
|
||||
return {
|
||||
'additional_dependencies': sorted(
|
||||
self.additional_dependencies[
|
||||
language_name
|
||||
][language_version],
|
||||
)
|
||||
}
|
||||
|
||||
def state_filename(venv, suffix=''):
|
||||
return self.cmd_runner.path(
|
||||
venv, '.install_state_v' + INSTALLED_STATE_VERSION + suffix,
|
||||
)
|
||||
|
||||
def read_state(venv):
|
||||
if not os.path.exists(state_filename(venv)):
|
||||
return None
|
||||
else:
|
||||
return json.loads(io.open(state_filename(venv)).read())
|
||||
|
||||
def write_state(venv, language_name, language_version):
|
||||
with io.open(
|
||||
state_filename(venv, suffix='staging'), 'w',
|
||||
) as state_file:
|
||||
state_file.write(five.to_text(json.dumps(
|
||||
state(language_name, language_version),
|
||||
)))
|
||||
# Move the file into place atomically to indicate we've installed
|
||||
os.rename(
|
||||
state_filename(venv, suffix='staging'),
|
||||
state_filename(venv),
|
||||
)
|
||||
|
||||
def language_is_installed(language_name, language_version):
|
||||
language = languages[language_name]
|
||||
venv = environment_dir(language.ENVIRONMENT_DIR, language_version)
|
||||
return (
|
||||
venv is None or
|
||||
read_state(venv) == state(language_name, language_version)
|
||||
)
|
||||
|
||||
if not all(
|
||||
language_is_installed(language_name, language_version)
|
||||
for language_name, language_version in self.languages
|
||||
):
|
||||
logger.info(
|
||||
'Installing environment for {}.'.format(self.repo_url)
|
||||
)
|
||||
logger.info('Once installed this environment will be reused.')
|
||||
logger.info('This may take a few minutes...')
|
||||
|
||||
for language_name, language_version in self.languages:
|
||||
if language_is_installed(language_name, language_version):
|
||||
continue
|
||||
|
||||
language = languages[language_name]
|
||||
venv = environment_dir(language.ENVIRONMENT_DIR, language_version)
|
||||
|
||||
# There's potentially incomplete cleanup from previous runs
|
||||
# Clean it up!
|
||||
if self.cmd_runner.exists(venv):
|
||||
shutil.rmtree(self.cmd_runner.path(venv))
|
||||
|
||||
language.install_environment(
|
||||
self.cmd_runner, language_version,
|
||||
self.additional_dependencies[language_name][language_version],
|
||||
)
|
||||
# Write our state to indicate we're installed
|
||||
write_state(venv, language_name, language_version)
|
||||
if not self.__installed:
|
||||
_install_all(self._venvs, self.repo_config['repo'])
|
||||
self.__installed = True
|
||||
|
||||
def run_hook(self, hook, file_args):
|
||||
"""Run a hook.
|
||||
@@ -192,7 +174,7 @@ class Repository(object):
|
||||
"""
|
||||
self.require_installed()
|
||||
return languages[hook['language']].run_hook(
|
||||
self.cmd_runner, hook, file_args,
|
||||
self._cmd_runner, hook, file_args,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user