mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-02-17 08:14:42 +04:00
Implement Store. pre-commit now installs files to ~/.pre-commit
This commit is contained in:
parent
26af2cecab
commit
479eb51873
16 changed files with 457 additions and 234 deletions
|
|
@ -56,7 +56,7 @@ class RepositoryCannotBeUpdatedError(RuntimeError):
|
|||
pass
|
||||
|
||||
|
||||
def _update_repository(repo_config):
|
||||
def _update_repository(repo_config, runner):
|
||||
"""Updates a repository to the tip of `master`. If the repository cannot
|
||||
be updated because a hook that is configured does not exist in `master`,
|
||||
this raises a RepositoryCannotBeUpdatedError
|
||||
|
|
@ -64,9 +64,9 @@ def _update_repository(repo_config):
|
|||
Args:
|
||||
repo_config - A config for a repository
|
||||
"""
|
||||
repo = Repository(repo_config)
|
||||
repo = Repository.create(repo_config, runner.store)
|
||||
|
||||
with repo.in_checkout():
|
||||
with local.cwd(repo.repo_path_getter.repo_path):
|
||||
local['git']['fetch']()
|
||||
head_sha = local['git']['rev-parse', 'origin/master']().strip()
|
||||
|
||||
|
|
@ -77,11 +77,11 @@ def _update_repository(repo_config):
|
|||
# Construct a new config with the head sha
|
||||
new_config = OrderedDict(repo_config)
|
||||
new_config['sha'] = head_sha
|
||||
new_repo = Repository(new_config)
|
||||
new_repo = Repository.create(new_config, runner.store)
|
||||
|
||||
# See if any of our hooks were deleted with the new commits
|
||||
hooks = set(repo.hooks.keys())
|
||||
hooks_missing = hooks - (hooks & set(new_repo.manifest.keys()))
|
||||
hooks_missing = hooks - (hooks & set(new_repo.manifest.hooks.keys()))
|
||||
if hooks_missing:
|
||||
raise RepositoryCannotBeUpdatedError(
|
||||
'Cannot update because the tip of master is missing these hooks:\n'
|
||||
|
|
@ -106,7 +106,7 @@ def autoupdate(runner):
|
|||
sys.stdout.write('Updating {0}...'.format(repo_config['repo']))
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
new_repo_config = _update_repository(repo_config)
|
||||
new_repo_config = _update_repository(repo_config, runner)
|
||||
except RepositoryCannotBeUpdatedError as error:
|
||||
print(error.args[0])
|
||||
output_configs.append(repo_config)
|
||||
|
|
@ -135,9 +135,9 @@ def autoupdate(runner):
|
|||
|
||||
|
||||
def clean(runner):
|
||||
if os.path.exists(runner.hooks_workspace_path):
|
||||
shutil.rmtree(runner.hooks_workspace_path)
|
||||
print('Cleaned {0}.'.format(runner.hooks_workspace_path))
|
||||
if os.path.exists(runner.store.directory):
|
||||
shutil.rmtree(runner.store.directory)
|
||||
print('Cleaned {0}.'.format(runner.store.directory))
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
CONFIG_FILE = '.pre-commit-config.yaml'
|
||||
|
||||
HOOKS_WORKSPACE = '.pre-commit-files'
|
||||
|
||||
MANIFEST_FILE = 'hooks.yaml'
|
||||
|
||||
YAML_DUMP_KWARGS = {
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
import contextlib
|
||||
import os.path
|
||||
from plumbum import local
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import git
|
||||
|
||||
|
||||
def get_pre_commit_dir_path():
|
||||
return os.path.join(git.get_root(), C.HOOKS_WORKSPACE)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_hooks_workspace():
|
||||
"""Change into the hooks workspace. If it does not exist create it."""
|
||||
if not os.path.exists(get_pre_commit_dir_path()):
|
||||
local.path(get_pre_commit_dir_path()).mkdir()
|
||||
|
||||
with local.cwd(get_pre_commit_dir_path()):
|
||||
yield
|
||||
21
pre_commit/manifest.py
Normal file
21
pre_commit/manifest.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import os.path
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit.clientlib.validate_manifest import load_manifest
|
||||
from pre_commit.util import cached_property
|
||||
|
||||
|
||||
class Manifest(object):
|
||||
def __init__(self, repo_path_getter):
|
||||
self.repo_path_getter = repo_path_getter
|
||||
|
||||
@cached_property
|
||||
def manifest_contents(self):
|
||||
manifest_path = os.path.join(
|
||||
self.repo_path_getter.repo_path, C.MANIFEST_FILE,
|
||||
)
|
||||
return load_manifest(manifest_path)
|
||||
|
||||
@cached_property
|
||||
def hooks(self):
|
||||
return dict((hook['id'], hook) for hook in self.manifest_contents)
|
||||
|
|
@ -1,27 +1,24 @@
|
|||
import contextlib
|
||||
import logging
|
||||
from asottile.ordereddict import OrderedDict
|
||||
from plumbum import local
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import five
|
||||
from pre_commit.clientlib.validate_manifest import load_manifest
|
||||
from pre_commit.hooks_workspace import in_hooks_workspace
|
||||
from pre_commit.languages.all import languages
|
||||
from pre_commit.manifest import Manifest
|
||||
from pre_commit.prefixed_command_runner import PrefixedCommandRunner
|
||||
from pre_commit.util import cached_property
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
|
||||
|
||||
logger = logging.getLogger('pre_commit')
|
||||
|
||||
|
||||
class Repository(object):
|
||||
def __init__(self, repo_config):
|
||||
def __init__(self, repo_config, repo_path_getter):
|
||||
self.repo_config = repo_config
|
||||
self.__created = False
|
||||
self.repo_path_getter = repo_path_getter
|
||||
self.__installed = False
|
||||
|
||||
@classmethod
|
||||
def create(cls, config, store):
|
||||
repo_path_getter = store.get_repo_path_getter(
|
||||
config['repo'], config['sha']
|
||||
)
|
||||
return cls(config, repo_path_getter)
|
||||
|
||||
@cached_property
|
||||
def repo_url(self):
|
||||
return self.repo_config['repo']
|
||||
|
|
@ -36,46 +33,22 @@ class Repository(object):
|
|||
|
||||
@cached_property
|
||||
def hooks(self):
|
||||
# TODO: merging in manifest dicts is a smell imo
|
||||
return OrderedDict(
|
||||
(hook['id'], dict(hook, **self.manifest[hook['id']]))
|
||||
(hook['id'], dict(hook, **self.manifest.hooks[hook['id']]))
|
||||
for hook in self.repo_config['hooks']
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def manifest(self):
|
||||
with self.in_checkout():
|
||||
return dict(
|
||||
(hook['id'], hook)
|
||||
for hook in load_manifest(C.MANIFEST_FILE)
|
||||
)
|
||||
return Manifest(self.repo_path_getter)
|
||||
|
||||
def get_cmd_runner(self, hooks_cmd_runner):
|
||||
# TODO: this effectively throws away the original cmd runner
|
||||
return PrefixedCommandRunner.from_command_runner(
|
||||
hooks_cmd_runner, self.sha,
|
||||
hooks_cmd_runner, self.repo_path_getter.repo_path,
|
||||
)
|
||||
|
||||
def require_created(self):
|
||||
if self.__created:
|
||||
return
|
||||
|
||||
self.create()
|
||||
self.__created = True
|
||||
|
||||
def create(self):
|
||||
with in_hooks_workspace():
|
||||
if local.path(self.sha).exists():
|
||||
# Project already exists, no reason to re-create it
|
||||
return
|
||||
|
||||
# Checking out environment for the first time
|
||||
logger.info('Installing environment for {0}.'.format(self.repo_url))
|
||||
logger.info('Once installed this environment will be reused.')
|
||||
logger.info('This may take a few minutes...')
|
||||
with clean_path_on_failure(five.u(local.path(self.sha))):
|
||||
local['git']['clone', '--no-checkout', self.repo_url, self.sha]()
|
||||
with self.in_checkout():
|
||||
local['git']['checkout', self.sha]()
|
||||
|
||||
def require_installed(self, cmd_runner):
|
||||
if self.__installed:
|
||||
return
|
||||
|
|
@ -89,7 +62,6 @@ class Repository(object):
|
|||
Args:
|
||||
cmd_runner - A `PrefixedCommandRunner` bound to the hooks workspace
|
||||
"""
|
||||
self.require_created()
|
||||
repo_cmd_runner = self.get_cmd_runner(cmd_runner)
|
||||
for language_name in self.languages:
|
||||
language = languages[language_name]
|
||||
|
|
@ -101,13 +73,6 @@ class Repository(object):
|
|||
continue
|
||||
language.install_environment(repo_cmd_runner)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_checkout(self):
|
||||
self.require_created()
|
||||
with in_hooks_workspace():
|
||||
with local.cwd(self.sha):
|
||||
yield
|
||||
|
||||
def run_hook(self, cmd_runner, hook_id, file_args):
|
||||
"""Run a hook.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import os.path
|
|||
import pre_commit.constants as C
|
||||
from pre_commit import git
|
||||
from pre_commit.clientlib.validate_config import load_config
|
||||
from pre_commit.prefixed_command_runner import PrefixedCommandRunner
|
||||
from pre_commit.repository import Repository
|
||||
from pre_commit.store import Store
|
||||
from pre_commit.util import cached_property
|
||||
|
||||
|
||||
|
|
@ -27,10 +27,6 @@ class Runner(object):
|
|||
os.chdir(root)
|
||||
return cls(root)
|
||||
|
||||
@cached_property
|
||||
def hooks_workspace_path(self):
|
||||
return os.path.join(self.git_root, C.HOOKS_WORKSPACE)
|
||||
|
||||
@cached_property
|
||||
def config_file_path(self):
|
||||
return os.path.join(self.git_root, C.CONFIG_FILE)
|
||||
|
|
@ -39,7 +35,7 @@ class Runner(object):
|
|||
def repositories(self):
|
||||
"""Returns a tuple of the configured repositories."""
|
||||
config = load_config(self.config_file_path)
|
||||
return tuple(Repository(x) for x in config)
|
||||
return tuple(Repository.create(x, self.store) for x in config)
|
||||
|
||||
@cached_property
|
||||
def pre_commit_path(self):
|
||||
|
|
@ -47,4 +43,9 @@ class Runner(object):
|
|||
|
||||
@cached_property
|
||||
def cmd_runner(self):
|
||||
return PrefixedCommandRunner(self.hooks_workspace_path)
|
||||
# TODO: remove this and inline runner.store.cmd_runner
|
||||
return self.store.cmd_runner
|
||||
|
||||
@cached_property
|
||||
def store(self):
|
||||
return Store()
|
||||
|
|
|
|||
97
pre_commit/store.py
Normal file
97
pre_commit/store.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
from plumbum import local
|
||||
|
||||
from pre_commit.prefixed_command_runner import PrefixedCommandRunner
|
||||
from pre_commit.util import cached_property
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
|
||||
|
||||
logger = logging.getLogger('pre_commit')
|
||||
|
||||
|
||||
def _get_default_directory():
|
||||
"""Returns the default directory for the Store. This is intentionally
|
||||
underscored to indicate that `Store.get_default_directory` is the intended
|
||||
way to get this information. This is also done so
|
||||
`Store.get_default_directory` can be mocked in tests and
|
||||
`_get_default_directory` can be tested.
|
||||
"""
|
||||
return os.path.join(os.environ['HOME'], '.pre-commit')
|
||||
|
||||
|
||||
class Store(object):
|
||||
get_default_directory = staticmethod(_get_default_directory)
|
||||
|
||||
class RepoPathGetter(object):
|
||||
def __init__(self, repo, sha, store):
|
||||
self._repo = repo
|
||||
self._sha = sha
|
||||
self._store = store
|
||||
|
||||
@cached_property
|
||||
def repo_path(self):
|
||||
return self._store.clone(self._repo, self._sha)
|
||||
|
||||
def __init__(self, directory=None):
|
||||
if directory is None:
|
||||
directory = self.get_default_directory()
|
||||
|
||||
self.directory = directory
|
||||
self.__created = False
|
||||
|
||||
def _write_readme(self):
|
||||
with io.open(os.path.join(self.directory, 'README'), 'w') as readme:
|
||||
readme.write(
|
||||
'This directory is maintained by the pre-commit project.\n'
|
||||
'Learn more: https://github.com/pre-commit/pre-commit\n'
|
||||
)
|
||||
|
||||
def _create(self):
|
||||
if os.path.exists(self.directory):
|
||||
return
|
||||
os.makedirs(self.directory)
|
||||
self._write_readme()
|
||||
|
||||
def require_created(self):
|
||||
"""Require the pre-commit file store to be created."""
|
||||
if self.__created:
|
||||
return
|
||||
|
||||
self._create()
|
||||
self.__created = True
|
||||
|
||||
def clone(self, url, sha):
|
||||
"""Clone the given url and checkout the specific sha."""
|
||||
self.require_created()
|
||||
|
||||
# Check if we already exist
|
||||
sha_path = os.path.join(self.directory, sha)
|
||||
if os.path.exists(sha_path):
|
||||
return os.readlink(sha_path)
|
||||
|
||||
logger.info('Installing environment for {0}.'.format(url))
|
||||
logger.info('Once installed this environment will be reused.')
|
||||
logger.info('This may take a few minutes...')
|
||||
|
||||
dir = tempfile.mkdtemp(prefix='repo', dir=self.directory)
|
||||
with clean_path_on_failure(dir):
|
||||
local['git']('clone', '--no-checkout', url, dir)
|
||||
with local.cwd(dir):
|
||||
local['git']('checkout', sha)
|
||||
|
||||
# Make a symlink from sha->repo
|
||||
os.symlink(dir, sha_path)
|
||||
return dir
|
||||
|
||||
def get_repo_path_getter(self, repo, sha):
|
||||
return self.RepoPathGetter(repo, sha, self)
|
||||
|
||||
@cached_property
|
||||
def cmd_runner(self):
|
||||
return PrefixedCommandRunner(self.directory)
|
||||
Loading…
Add table
Add a link
Reference in a new issue