diff options
Diffstat (limited to 'pre_commit')
-rw-r--r-- | pre_commit/clientlib.py | 4 | ||||
-rw-r--r-- | pre_commit/commands/hook_impl.py | 5 | ||||
-rw-r--r-- | pre_commit/commands/migrate_config.py | 4 | ||||
-rw-r--r-- | pre_commit/commands/run.py | 7 | ||||
-rw-r--r-- | pre_commit/constants.py | 1 | ||||
-rw-r--r-- | pre_commit/git.py | 15 | ||||
-rw-r--r-- | pre_commit/languages/all.py | 2 | ||||
-rw-r--r-- | pre_commit/languages/dart.py | 109 | ||||
-rw-r--r-- | pre_commit/languages/helpers.py | 4 | ||||
-rw-r--r-- | pre_commit/languages/python.py | 3 | ||||
-rw-r--r-- | pre_commit/main.py | 8 | ||||
-rw-r--r-- | pre_commit/meta_hooks/check_useless_excludes.py | 3 | ||||
-rw-r--r-- | pre_commit/resources/empty_template_pubspec.yaml | 4 | ||||
-rw-r--r-- | pre_commit/store.py | 2 | ||||
-rw-r--r-- | pre_commit/util.py | 4 |
15 files changed, 158 insertions, 17 deletions
diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 962c7fa..bc7274a 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -216,14 +216,14 @@ _meta = ( ( 'check-hooks-apply', ( ('name', 'Check hooks apply to the repository'), - ('files', C.CONFIG_FILE), + ('files', f'^{re.escape(C.CONFIG_FILE)}$'), ('entry', _entry('check_hooks_apply')), ), ), ( 'check-useless-excludes', ( ('name', 'Check for useless excludes'), - ('files', C.CONFIG_FILE), + ('files', f'^{re.escape(C.CONFIG_FILE)}$'), ('entry', _entry('check_useless_excludes')), ), ), diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index c544167..90bb33b 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -78,6 +78,7 @@ def _ns( commit_msg_filename: Optional[str] = None, checkout_type: Optional[str] = None, is_squash_merge: Optional[str] = None, + rewrite_command: Optional[str] = None, ) -> argparse.Namespace: return argparse.Namespace( color=color, @@ -92,6 +93,7 @@ def _ns( all_files=all_files, checkout_type=checkout_type, is_squash_merge=is_squash_merge, + rewrite_command=rewrite_command, files=(), hook=None, verbose=False, @@ -166,6 +168,7 @@ _EXPECTED_ARG_LENGTH_BY_HOOK = { 'pre-commit': 0, 'pre-merge-commit': 0, 'post-merge': 1, + 'post-rewrite': 1, 'pre-push': 2, } @@ -209,6 +212,8 @@ def _run_ns( ) elif hook_type == 'post-merge': return _ns(hook_type, color, is_squash_merge=args[0]) + elif hook_type == 'post-rewrite': + return _ns(hook_type, color, rewrite_command=args[0]) else: raise AssertionError(f'unexpected hook type: {hook_type}') diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py index a155f6b..fef14cd 100644 --- a/pre_commit/commands/migrate_config.py +++ b/pre_commit/commands/migrate_config.py @@ -3,7 +3,6 @@ import textwrap import yaml -from pre_commit.clientlib import load_config from pre_commit.util import yaml_load @@ -40,9 +39,6 @@ def _migrate_sha_to_rev(contents: str) -> str: def migrate_config(config_file: str, quiet: bool = False) -> int: - # ensure that the configuration is a valid pre-commit configuration - load_config(config_file) - with open(config_file) as f: orig_contents = contents = f.read() diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index d906d5b..95ad5e9 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -245,7 +245,9 @@ def _compute_cols(hooks: Sequence[Hook]) -> int: def _all_filenames(args: argparse.Namespace) -> Collection[str]: # these hooks do not operate on files - if args.hook_stage in {'post-checkout', 'post-commit', 'post-merge'}: + if args.hook_stage in { + 'post-checkout', 'post-commit', 'post-merge', 'post-rewrite', + }: return () elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}: return (args.commit_msg_filename,) @@ -386,6 +388,9 @@ def run( if args.is_squash_merge: environ['PRE_COMMIT_IS_SQUASH_MERGE'] = args.is_squash_merge + if args.rewrite_command: + environ['PRE_COMMIT_REWRITE_COMMAND'] = args.rewrite_command + # Set pre_commit flag environ['PRE_COMMIT'] = '1' diff --git a/pre_commit/constants.py b/pre_commit/constants.py index 3dcbbac..1a69c90 100644 --- a/pre_commit/constants.py +++ b/pre_commit/constants.py @@ -19,6 +19,7 @@ VERSION = importlib_metadata.version('pre_commit') STAGES = ( 'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', 'post-commit', 'manual', 'post-checkout', 'push', 'post-merge', + 'post-rewrite', ) DEFAULT = 'default' diff --git a/pre_commit/git.py b/pre_commit/git.py index 4bf2823..6264529 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -155,12 +155,15 @@ def get_all_files() -> List[str]: def get_changed_files(old: str, new: str) -> List[str]: - return zsplit( - cmd_output( - 'git', 'diff', '--name-only', '--no-ext-diff', '-z', - f'{old}...{new}', - )[1], - ) + diff_cmd = ('git', 'diff', '--name-only', '--no-ext-diff', '-z') + try: + _, out, _ = cmd_output(*diff_cmd, f'{old}...{new}') + except CalledProcessError: # pragma: no cover (new git) + # on newer git where old and new do not have a merge base git fails + # so we try a full diff (this is what old git did for us!) + _, out, _ = cmd_output(*diff_cmd, f'{old}..{new}') + + return zsplit(out) def head_rev(remote: str) -> str: diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index fde6000..d8a364c 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -7,6 +7,7 @@ from typing import Tuple from pre_commit.hook import Hook from pre_commit.languages import conda from pre_commit.languages import coursier +from pre_commit.languages import dart from pre_commit.languages import docker from pre_commit.languages import docker_image from pre_commit.languages import dotnet @@ -44,6 +45,7 @@ languages = { # BEGIN GENERATED (testing/gen-languages-all) 'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, healthy=conda.healthy, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501 'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, healthy=coursier.healthy, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501 + 'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, healthy=dart.healthy, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501 'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, healthy=docker.healthy, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501 'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, healthy=docker_image.healthy, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501 'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, healthy=dotnet.healthy, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501 diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py new file mode 100644 index 0000000..16e7554 --- /dev/null +++ b/pre_commit/languages/dart.py @@ -0,0 +1,109 @@ +import contextlib +import os.path +import shutil +import tempfile +from typing import Generator +from typing import Sequence +from typing import Tuple + +import pre_commit.constants as C +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.hook import Hook +from pre_commit.languages import helpers +from pre_commit.prefix import Prefix +from pre_commit.util import clean_path_on_failure +from pre_commit.util import win_exe +from pre_commit.util import yaml_load + +ENVIRONMENT_DIR = 'dartenv' + +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def get_env_patch(venv: str) -> PatchesT: + return ( + ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))), + ) + + +@contextlib.contextmanager +def in_env(prefix: Prefix) -> Generator[None, None, None]: + directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT) + envdir = prefix.path(directory) + with envcontext(get_env_patch(envdir)): + yield + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + helpers.assert_version_default('dart', version) + + envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + bin_dir = os.path.join(envdir, 'bin') + + def _install_dir(prefix_p: Prefix, pub_cache: str) -> None: + dart_env = {**os.environ, 'PUB_CACHE': pub_cache} + + with open(prefix_p.path('pubspec.yaml')) as f: + pubspec_contents = yaml_load(f) + + helpers.run_setup_cmd(prefix_p, ('dart', 'pub', 'get'), env=dart_env) + + for executable in pubspec_contents['executables']: + helpers.run_setup_cmd( + prefix_p, + ( + 'dart', 'compile', 'exe', + '--output', os.path.join(bin_dir, win_exe(executable)), + prefix_p.path('bin', f'{executable}.dart'), + ), + env=dart_env, + ) + + with clean_path_on_failure(envdir): + os.makedirs(bin_dir) + + with tempfile.TemporaryDirectory() as tmp: + _install_dir(prefix, tmp) + + for dep_s in additional_dependencies: + with tempfile.TemporaryDirectory() as dep_tmp: + dep, _, version = dep_s.partition(':') + if version: + dep_cmd: Tuple[str, ...] = (dep, '--version', version) + else: + dep_cmd = (dep,) + + helpers.run_setup_cmd( + prefix, + ('dart', 'pub', 'cache', 'add', *dep_cmd), + env={**os.environ, 'PUB_CACHE': dep_tmp}, + ) + + # try and find the 'pubspec.yaml' that just got added + for root, _, filenames in os.walk(dep_tmp): + if 'pubspec.yaml' in filenames: + with tempfile.TemporaryDirectory() as copied: + pkg = os.path.join(copied, 'pkg') + shutil.copytree(root, pkg) + _install_dir(Prefix(pkg), dep_tmp) + break + else: + raise AssertionError( + f'could not find pubspec.yaml for {dep_s}', + ) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index 29138fd..276ce16 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -48,8 +48,8 @@ def exe_exists(exe: str) -> bool: ) -def run_setup_cmd(prefix: Prefix, cmd: Tuple[str, ...]) -> None: - cmd_output_b(*cmd, cwd=prefix.prefix_dir) +def run_setup_cmd(prefix: Prefix, cmd: Tuple[str, ...], **kwargs: Any) -> None: + cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs) @overload diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 43b7280..faa6029 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -21,6 +21,7 @@ from pre_commit.util import CalledProcessError from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b +from pre_commit.util import win_exe ENVIRONMENT_DIR = 'py_env' @@ -172,7 +173,7 @@ def healthy(prefix: Prefix, language_version: str) -> bool: if not os.path.exists(pyvenv_cfg): return False - exe_name = 'python.exe' if sys.platform == 'win32' else 'python' + exe_name = win_exe('python') py_exe = prefix.path(bin_dir(envdir), exe_name) cfg = _read_pyvenv_cfg(pyvenv_cfg) diff --git a/pre_commit/main.py b/pre_commit/main.py index ad3d873..2b50c91 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -69,6 +69,7 @@ def _add_hook_type_option(parser: argparse.ArgumentParser) -> None: '-t', '--hook-type', choices=( 'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg', 'commit-msg', 'post-commit', 'post-checkout', 'post-merge', + 'post-rewrite', ), action=AppendReplaceDefault, default=['pre-commit'], @@ -146,6 +147,13 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None: 'squash merge' ), ) + parser.add_argument( + '--rewrite-command', + help=( + 'During a post-rewrite hook, specifies the command that invoked ' + 'the rewrite' + ), + ) def _adjust_args_and_chdir(args: argparse.Namespace) -> None: diff --git a/pre_commit/meta_hooks/check_useless_excludes.py b/pre_commit/meta_hooks/check_useless_excludes.py index 12be03f..6116597 100644 --- a/pre_commit/meta_hooks/check_useless_excludes.py +++ b/pre_commit/meta_hooks/check_useless_excludes.py @@ -43,6 +43,9 @@ def check_useless_excludes(config_file: str) -> int: for repo in config['repos']: for hook in repo['hooks']: + # the default of manifest hooks is `types: [file]` but we may + # be configuring a symlink hook while there's a broken symlink + hook.setdefault('types', []) # Not actually a manifest dict, but this more accurately reflects # the defaults applied during runtime hook = apply_defaults(hook, MANIFEST_HOOK_DICT) diff --git a/pre_commit/resources/empty_template_pubspec.yaml b/pre_commit/resources/empty_template_pubspec.yaml new file mode 100644 index 0000000..3be6ffe --- /dev/null +++ b/pre_commit/resources/empty_template_pubspec.yaml @@ -0,0 +1,4 @@ +name: pre_commit_empty_pubspec +environment: + sdk: '>=2.10.0' +executables: {} diff --git a/pre_commit/store.py b/pre_commit/store.py index 0fd5e62..fc3bc51 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -189,7 +189,7 @@ class Store: LOCAL_RESOURCES = ( 'Cargo.toml', 'main.go', 'go.mod', 'main.rs', '.npmignore', 'package.json', 'pre_commit_placeholder_package.gemspec', 'setup.py', - 'environment.yml', 'Makefile.PL', + 'environment.yml', 'Makefile.PL', 'pubspec.yaml', 'renv.lock', 'renv/activate.R', 'renv/LICENSE.renv', ) diff --git a/pre_commit/util.py b/pre_commit/util.py index b5f40ad..6bf8ae7 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -268,3 +268,7 @@ def rmtree(path: str) -> None: def parse_version(s: str) -> Tuple[int, ...]: """poor man's version comparison""" return tuple(int(p) for p in s.split('.')) + + +def win_exe(s: str) -> str: + return s if sys.platform != 'win32' else f'{s}.exe' |