diff options
Diffstat (limited to 'pre_commit')
24 files changed, 332 insertions, 92 deletions
diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 8dfa947..87679bf 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -13,7 +13,7 @@ from identify.identify import ALL_TAGS import pre_commit.constants as C from pre_commit.color import add_color_option -from pre_commit.error_handler import FatalError +from pre_commit.errors import FatalError from pre_commit.languages.all import all_languages from pre_commit.logging_handler import logging_handler from pre_commit.util import parse_version diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py index 85fa53c..684b598 100644 --- a/pre_commit/commands/install_uninstall.py +++ b/pre_commit/commands/install_uninstall.py @@ -55,7 +55,7 @@ def is_our_script(filename: str) -> bool: def shebang() -> str: if sys.platform == 'win32': - py = SYS_EXE + py, _ = os.path.splitext(SYS_EXE) else: exe_choices = [ f'python{sys.version_info[0]}.{sys.version_info[1]}', diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 1f28c8c..0d335e2 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -11,6 +11,7 @@ from typing import Any from typing import Collection from typing import Dict from typing import List +from typing import MutableMapping from typing import Sequence from typing import Set from typing import Tuple @@ -28,7 +29,6 @@ from pre_commit.repository import install_hook_envs from pre_commit.staged_files_only import staged_files_only from pre_commit.store import Store from pre_commit.util import cmd_output_b -from pre_commit.util import EnvironT logger = logging.getLogger('pre_commit') @@ -116,7 +116,7 @@ class Classifier: return Classifier(filenames) -def _get_skips(environ: EnvironT) -> Set[str]: +def _get_skips(environ: MutableMapping[str, str]) -> Set[str]: skips = environ.get('SKIP', '') return {skip.strip() for skip in skips.split(',') if skip.strip()} @@ -258,7 +258,7 @@ def _run_hooks( config: Dict[str, Any], hooks: Sequence[Hook], args: argparse.Namespace, - environ: EnvironT, + environ: MutableMapping[str, str], ) -> int: """Actually run the hooks.""" skips = _get_skips(environ) @@ -315,7 +315,7 @@ def run( config_file: str, store: Store, args: argparse.Namespace, - environ: EnvironT = os.environ, + environ: MutableMapping[str, str] = os.environ, ) -> int: stash = not args.all_files and not args.files diff --git a/pre_commit/commands/sample_config.py b/pre_commit/commands/sample_config.py index d435faa..64617c3 100644 --- a/pre_commit/commands/sample_config.py +++ b/pre_commit/commands/sample_config.py @@ -7,7 +7,7 @@ SAMPLE_CONFIG = '''\ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 + rev: v3.2.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/pre_commit/envcontext.py b/pre_commit/envcontext.py index 16d3d15..4ab0d8c 100644 --- a/pre_commit/envcontext.py +++ b/pre_commit/envcontext.py @@ -2,13 +2,12 @@ import contextlib import enum import os from typing import Generator +from typing import MutableMapping from typing import NamedTuple from typing import Optional from typing import Tuple from typing import Union -from pre_commit.util import EnvironT - class _Unset(enum.Enum): UNSET = 1 @@ -27,7 +26,7 @@ ValueT = Union[str, _Unset, SubstitutionT] PatchesT = Tuple[Tuple[str, ValueT], ...] -def format_env(parts: SubstitutionT, env: EnvironT) -> str: +def format_env(parts: SubstitutionT, env: MutableMapping[str, str]) -> str: return ''.join( env.get(part.name, part.default) if isinstance(part, Var) else part for part in parts @@ -37,7 +36,7 @@ def format_env(parts: SubstitutionT, env: EnvironT) -> str: @contextlib.contextmanager def envcontext( patch: PatchesT, - _env: Optional[EnvironT] = None, + _env: Optional[MutableMapping[str, str]] = None, ) -> Generator[None, None, None]: """In this context, `os.environ` is modified according to `patch`. @@ -50,7 +49,7 @@ def envcontext( replaced with the previous environment """ env = os.environ if _env is None else _env - before = env.copy() + before = dict(env) for k, v in patch: if v is UNSET: diff --git a/pre_commit/error_handler.py b/pre_commit/error_handler.py index 13d78cb..023dd35 100644 --- a/pre_commit/error_handler.py +++ b/pre_commit/error_handler.py @@ -7,15 +7,17 @@ from typing import Generator import pre_commit.constants as C from pre_commit import output +from pre_commit.errors import FatalError from pre_commit.store import Store from pre_commit.util import force_bytes -class FatalError(RuntimeError): - pass - - -def _log_and_exit(msg: str, exc: BaseException, formatted: str) -> None: +def _log_and_exit( + msg: str, + ret_code: int, + exc: BaseException, + formatted: str, +) -> None: error_msg = f'{msg}: {type(exc).__name__}: '.encode() + force_bytes(exc) output.write_line_b(error_msg) @@ -52,9 +54,9 @@ def _log_and_exit(msg: str, exc: BaseException, formatted: str) -> None: _log_line('```') _log_line() _log_line('```') - _log_line(formatted) + _log_line(formatted.rstrip()) _log_line('```') - raise SystemExit(1) + raise SystemExit(ret_code) @contextlib.contextmanager @@ -63,9 +65,9 @@ def error_handler() -> Generator[None, None, None]: yield except (Exception, KeyboardInterrupt) as e: if isinstance(e, FatalError): - msg = 'An error has occurred' + msg, ret_code = 'An error has occurred', 1 elif isinstance(e, KeyboardInterrupt): - msg = 'Interrupted (^C)' + msg, ret_code = 'Interrupted (^C)', 130 else: - msg = 'An unexpected error has occurred' - _log_and_exit(msg, e, traceback.format_exc()) + msg, ret_code = 'An unexpected error has occurred', 3 + _log_and_exit(msg, ret_code, e, traceback.format_exc()) diff --git a/pre_commit/errors.py b/pre_commit/errors.py new file mode 100644 index 0000000..f84d3f1 --- /dev/null +++ b/pre_commit/errors.py @@ -0,0 +1,2 @@ +class FatalError(RuntimeError): + pass diff --git a/pre_commit/git.py b/pre_commit/git.py index 576bef8..13ba664 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -3,12 +3,14 @@ import os.path import sys from typing import Dict from typing import List +from typing import MutableMapping from typing import Optional from typing import Set +from pre_commit.errors import FatalError +from pre_commit.util import CalledProcessError from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b -from pre_commit.util import EnvironT logger = logging.getLogger(__name__) @@ -22,7 +24,9 @@ def zsplit(s: str) -> List[str]: return [] -def no_git_env(_env: Optional[EnvironT] = None) -> Dict[str, str]: +def no_git_env( + _env: Optional[MutableMapping[str, str]] = None, +) -> Dict[str, str]: # Too many bugs dealing with environment variables and GIT: # https://github.com/pre-commit/pre-commit/issues/300 # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running @@ -43,7 +47,21 @@ def no_git_env(_env: Optional[EnvironT] = None) -> Dict[str, str]: def get_root() -> str: - return cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip() + try: + root = cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip() + except CalledProcessError: + raise FatalError( + 'git failed. Is it installed, and are you in a Git repository ' + 'directory?', + ) + else: + if root == '': # pragma: no cover (old git) + raise FatalError( + 'git toplevel unexpectedly empty! make sure you are not ' + 'inside the `.git` directory of your repository.', + ) + else: + return root def get_git_dir(git_root: str = '.') -> str: @@ -181,7 +199,7 @@ def check_for_cygwin_mismatch() -> None: """See https://github.com/pre-commit/pre-commit/issues/354""" if sys.platform in ('cygwin', 'win32'): # pragma: no cover (windows) is_cygwin_python = sys.platform == 'cygwin' - toplevel = cmd_output('git', 'rev-parse', '--show-toplevel')[1] + toplevel = get_root() is_cygwin_git = toplevel.startswith('/') if is_cygwin_python ^ is_cygwin_git: diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index 5609631..9c2e59d 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -6,8 +6,10 @@ 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 docker from pre_commit.languages import docker_image +from pre_commit.languages import dotnet from pre_commit.languages import fail from pre_commit.languages import golang from pre_commit.languages import node @@ -40,8 +42,10 @@ class Language(NamedTuple): 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 '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 'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, healthy=fail.healthy, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501 'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, healthy=golang.healthy, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501 'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, healthy=node.healthy, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501 diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index 071757a..d634e49 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -77,7 +77,7 @@ def run_hook( color: bool, ) -> Tuple[int, bytes]: # TODO: Some rare commands need to be run using `conda run` but mostly we - # can run them withot which is much quicker and produces a better + # can run them without which is much quicker and produces a better # output. # cmd = ('conda', 'run', '-p', env_dir) + hook.cmd with in_env(hook.prefix, hook.language_version): diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py new file mode 100644 index 0000000..2841467 --- /dev/null +++ b/pre_commit/languages/coursier.py @@ -0,0 +1,71 @@ +import contextlib +import os +from typing import Generator +from typing import Sequence +from typing import Tuple + +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 + +ENVIRONMENT_DIR = 'coursier' + +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: # pragma: win32 no cover + helpers.assert_version_default('coursier', version) + helpers.assert_no_additional_deps('coursier', additional_dependencies) + + envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + channel = prefix.path('.pre-commit-channel') + with clean_path_on_failure(envdir): + for app_descriptor in os.listdir(channel): + _, app_file = os.path.split(app_descriptor) + app, _ = os.path.splitext(app_file) + helpers.run_setup_cmd( + prefix, + ( + 'cs', + 'install', + '--default-channels=false', + f'--channel={channel}', + app, + f'--dir={envdir}', + ), + ) + + +def get_env_patch(target_dir: str) -> PatchesT: # pragma: win32 no cover + return ( + ('PATH', (target_dir, os.pathsep, Var('PATH'))), + ) + + +@contextlib.contextmanager +def in_env( + prefix: Prefix, +) -> Generator[None, None, None]: # pragma: win32 no cover + target_dir = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, get_default_version()), + ) + with envcontext(get_env_patch(target_dir)): + yield + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: # pragma: win32 no cover + with in_env(hook.prefix): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 9c13119..9d30568 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -87,9 +87,8 @@ def run_hook( # automated cleanup of docker images. build_docker_image(hook.prefix, pull=False) - hook_cmd = hook.cmd - entry_exe, cmd_rest = hook.cmd[0], hook_cmd[1:] + entry_exe, *cmd_rest = hook.cmd entry_tag = ('--entrypoint', entry_exe, docker_tag(hook.prefix)) - cmd = docker_cmd() + entry_tag + cmd_rest + cmd = (*docker_cmd(), *entry_tag, *cmd_rest) return helpers.run_xargs(hook, cmd, file_args, color=color) diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py new file mode 100644 index 0000000..a8abc86 --- /dev/null +++ b/pre_commit/languages/dotnet.py @@ -0,0 +1,90 @@ +import contextlib +import os.path +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 rmtree + +ENVIRONMENT_DIR = 'dotnetenv' +BIN_DIR = 'bin' + +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_DIR), 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('dotnet', version) + helpers.assert_no_additional_deps('dotnet', additional_dependencies) + + envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + with clean_path_on_failure(envdir): + build_dir = 'pre-commit-build' + + # Build & pack nupkg file + helpers.run_setup_cmd( + prefix, + ( + 'dotnet', 'pack', + '--configuration', 'Release', + '--output', build_dir, + ), + ) + + # Determine tool from the packaged file <tool_name>.<version>.nupkg + build_outputs = os.listdir(os.path.join(prefix.prefix_dir, build_dir)) + if len(build_outputs) != 1: + raise NotImplementedError( + f"Can't handle multiple build outputs. Got {build_outputs}", + ) + tool_name = build_outputs[0].split('.')[0] + + # Install to bin dir + helpers.run_setup_cmd( + prefix, + ( + 'dotnet', 'tool', 'install', + '--tool-path', os.path.join(envdir, BIN_DIR), + '--add-source', build_dir, + tool_name, + ), + ) + + # Cleanup build output + for d in ('bin', 'obj', build_dir): + rmtree(prefix.path(d)) + + +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 01c65ab..29138fd 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -1,6 +1,7 @@ import multiprocessing import os import random +import re from typing import Any from typing import List from typing import Optional @@ -10,6 +11,7 @@ from typing import Tuple from typing import TYPE_CHECKING import pre_commit.constants as C +from pre_commit import parse_shebang from pre_commit.hook import Hook from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b @@ -20,6 +22,31 @@ if TYPE_CHECKING: FIXED_RANDOM_SEED = 1542676187 +SHIMS_RE = re.compile(r'[/\\]shims[/\\]') + + +def exe_exists(exe: str) -> bool: + found = parse_shebang.find_executable(exe) + if found is None: # exe exists + return False + + homedir = os.path.expanduser('~') + try: + common: Optional[str] = os.path.commonpath((found, homedir)) + except ValueError: # on windows, different drives raises ValueError + common = None + + return ( + # it is not in a /shims/ directory + not SHIMS_RE.search(found) and + ( + # the homedir is / (docker, service user, etc.) + os.path.dirname(homedir) == homedir or + # the exe is not contained in the home directory + common != homedir + ) + ) + def run_setup_cmd(prefix: Prefix, cmd: Tuple[str, ...]) -> None: cmd_output_b(*cmd, cwd=prefix.prefix_dir) diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index d99e6f2..8dc4e8b 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -7,7 +7,6 @@ from typing import Sequence from typing import Tuple import pre_commit.constants as C -from pre_commit import parse_shebang from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET @@ -19,9 +18,9 @@ from pre_commit.prefix import Prefix 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 rmtree ENVIRONMENT_DIR = 'node_env' -healthy = helpers.basic_healthy @functools.lru_cache(maxsize=1) @@ -31,7 +30,7 @@ def get_default_version() -> str: return C.DEFAULT # if node is already installed, we can save a bunch of setup time by # using the installed version - elif all(parse_shebang.find_executable(exe) for exe in ('node', 'npm')): + elif all(helpers.exe_exists(exe) for exe in ('node', 'npm')): return 'system' else: return C.DEFAULT @@ -73,6 +72,12 @@ def in_env( yield +def healthy(prefix: Prefix, language_version: str) -> bool: + with in_env(prefix, language_version): + retcode, _, _ = cmd_output_b('node', '--version', retcode=None) + return retcode == 0 + + def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: @@ -94,11 +99,23 @@ def install_environment( with in_env(prefix, version): # https://npm.community/t/npm-install-g-git-vs-git-clone-cd-npm-install-g/5449 # install as if we installed from git - helpers.run_setup_cmd(prefix, ('npm', 'install')) - helpers.run_setup_cmd( - prefix, - ('npm', 'install', '-g', '.', *additional_dependencies), + + local_install_cmd = ( + 'npm', 'install', '--dev', '--prod', + '--ignore-prepublish', '--no-progress', '--no-save', ) + helpers.run_setup_cmd(prefix, local_install_cmd) + + _, pkg, _ = cmd_output('npm', 'pack', cwd=prefix.prefix_dir) + pkg = prefix.path(pkg.strip()) + + install = ('npm', 'install', '-g', pkg, *additional_dependencies) + helpers.run_setup_cmd(prefix, install) + + # clean these up after installation + if prefix.exists('node_modules'): # pragma: win32 no cover + rmtree(prefix.path('node_modules')) + os.remove(pkg) def run_hook( diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py index 40adba0..c80d679 100644 --- a/pre_commit/languages/pygrep.py +++ b/pre_commit/languages/pygrep.py @@ -1,6 +1,7 @@ import argparse import re import sys +from typing import NamedTuple from typing import Optional from typing import Pattern from typing import Sequence @@ -45,6 +46,46 @@ def _process_filename_at_once(pattern: Pattern[bytes], filename: str) -> int: return retv +def _process_filename_by_line_negated( + pattern: Pattern[bytes], + filename: str, +) -> int: + with open(filename, 'rb') as f: + for line in f: + if pattern.search(line): + return 0 + else: + output.write_line(filename) + return 1 + + +def _process_filename_at_once_negated( + pattern: Pattern[bytes], + filename: str, +) -> int: + with open(filename, 'rb') as f: + contents = f.read() + match = pattern.search(contents) + if match: + return 0 + else: + output.write_line(filename) + return 1 + + +class Choice(NamedTuple): + multiline: bool + negate: bool + + +FNS = { + Choice(multiline=True, negate=True): _process_filename_at_once_negated, + Choice(multiline=True, negate=False): _process_filename_at_once, + Choice(multiline=False, negate=True): _process_filename_by_line_negated, + Choice(multiline=False, negate=False): _process_filename_by_line, +} + + def run_hook( hook: Hook, file_args: Sequence[str], @@ -64,6 +105,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int: ) parser.add_argument('-i', '--ignore-case', action='store_true') parser.add_argument('--multiline', action='store_true') + parser.add_argument('--negate', action='store_true') parser.add_argument('pattern', help='python regex pattern.') parser.add_argument('filenames', nargs='*') args = parser.parse_args(argv) @@ -75,11 +117,9 @@ def main(argv: Optional[Sequence[str]] = None) -> int: pattern = re.compile(args.pattern.encode(), flags) retv = 0 + process_fn = FNS[Choice(multiline=args.multiline, negate=args.negate)] for filename in args.filenames: - if args.multiline: - retv |= _process_filename_at_once(pattern, filename) - else: - retv |= _process_filename_by_line(pattern, filename) + retv |= process_fn(pattern, filename) return retv diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 7a68580..65f521c 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -114,11 +114,6 @@ def get_default_version() -> str: # pragma: no cover (platform dependent) if _find_by_py_launcher(exe): return exe - # Give a best-effort try for windows - default_folder_name = exe.replace('.', '') - if os.path.exists(fr'C:\{default_folder_name}\python.exe'): - return exe - # We tried! return C.DEFAULT @@ -137,13 +132,11 @@ def _sys_executable_matches(version: str) -> bool: return sys.version_info[:len(info)] == info -def norm_version(version: str) -> str: - if version == C.DEFAULT: - return os.path.realpath(sys.executable) - - # first see if our current executable is appropriate - if _sys_executable_matches(version): - return sys.executable +def norm_version(version: str) -> Optional[str]: + if version == C.DEFAULT: # use virtualenv's default + return None + elif _sys_executable_matches(version): # virtualenv defaults to our exe + return None if os.name == 'nt': # pragma: no cover (windows) version_exec = _find_by_py_launcher(version) @@ -155,12 +148,6 @@ def norm_version(version: str) -> str: if version_exec and version_exec != version: return version_exec - # If it is in the form pythonx.x search in the default - # place on windows - if version.startswith('python'): - default_folder_name = version.replace('.', '') - return fr'C:\{default_folder_name}\python.exe' - # Otherwise assume it is a path return os.path.expanduser(version) @@ -205,8 +192,10 @@ def install_environment( additional_dependencies: Sequence[str], ) -> None: envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + venv_cmd = [sys.executable, '-mvirtualenv', envdir] python = norm_version(version) - venv_cmd = (sys.executable, '-mvirtualenv', envdir, '-p', python) + if python is not None: + venv_cmd.extend(('-p', python)) install_cmd = ('python', '-mpip', 'install', '.', *additional_dependencies) with clean_path_on_failure(envdir): diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 73b23cc..1a0f0c7 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -8,7 +8,6 @@ from typing import Sequence from typing import Tuple import pre_commit.constants as C -from pre_commit import parse_shebang from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET @@ -26,7 +25,7 @@ healthy = helpers.basic_healthy @functools.lru_cache(maxsize=1) def get_default_version() -> str: - if all(parse_shebang.find_executable(exe) for exe in ('ruby', 'gem')): + if all(helpers.exe_exists(exe) for exe in ('ruby', 'gem')): return 'system' else: return C.DEFAULT @@ -122,8 +121,8 @@ def install_environment( # Need to call this before installing so rbenv's directories # are set up helpers.run_setup_cmd(prefix, ('rbenv', 'init', '-')) - # XXX: this will *always* fail if `version == C.DEFAULT` - _install_ruby(prefix, version) + if version != C.DEFAULT: + _install_ruby(prefix, version) # Need to call this after installing to set up the shims helpers.run_setup_cmd(prefix, ('rbenv', 'rehash')) @@ -134,7 +133,8 @@ def install_environment( helpers.run_setup_cmd( prefix, ( - 'gem', 'install', '--no-document', + 'gem', 'install', + '--no-document', '--no-format-executable', *prefix.star('.gem'), *additional_dependencies, ), ) diff --git a/pre_commit/main.py b/pre_commit/main.py index 8647960..c1eb104 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -23,10 +23,8 @@ from pre_commit.commands.run import run from pre_commit.commands.sample_config import sample_config from pre_commit.commands.try_repo import try_repo from pre_commit.error_handler import error_handler -from pre_commit.error_handler import FatalError from pre_commit.logging_handler import logging_handler from pre_commit.store import Store -from pre_commit.util import CalledProcessError logger = logging.getLogger('pre_commit') @@ -146,21 +144,8 @@ def _adjust_args_and_chdir(args: argparse.Namespace) -> None: if args.command == 'try-repo' and os.path.exists(args.repo): args.repo = os.path.abspath(args.repo) - try: - toplevel = git.get_root() - except CalledProcessError: - raise FatalError( - 'git failed. Is it installed, and are you in a Git repository ' - 'directory?', - ) - else: - if toplevel == '': # pragma: no cover (old git) - raise FatalError( - 'git toplevel unexpectedly empty! make sure you are not ' - 'inside the `.git` directory of your repository.', - ) - else: - os.chdir(toplevel) + toplevel = git.get_root() + os.chdir(toplevel) args.config = os.path.relpath(args.config) if args.command in {'run', 'try-repo'}: @@ -339,11 +324,11 @@ def main(argv: Optional[Sequence[str]] = None) -> int: parser.parse_args(['--help']) with error_handler(), logging_handler(args.color): + git.check_for_cygwin_mismatch() + if args.command not in COMMANDS_NO_GIT: _adjust_args_and_chdir(args) - git.check_for_cygwin_mismatch() - store = Store() store.mark_config_used(args.config) diff --git a/pre_commit/make_archives.py b/pre_commit/make_archives.py index c31bcd7..d320b83 100644 --- a/pre_commit/make_archives.py +++ b/pre_commit/make_archives.py @@ -15,8 +15,8 @@ from pre_commit.util import tmpdir REPOS = ( - ('rbenv', 'git://github.com/rbenv/rbenv', 'a3fa9b7'), - ('ruby-build', 'git://github.com/rbenv/ruby-build', '1a902f3'), + ('rbenv', 'git://github.com/rbenv/rbenv', '0843745'), + ('ruby-build', 'git://github.com/rbenv/ruby-build', '258455e'), ( 'ruby-download', 'git://github.com/garnieretienne/rvm-download', diff --git a/pre_commit/resources/rbenv.tar.gz b/pre_commit/resources/rbenv.tar.gz Binary files differindex 5307b19..97ac469 100644 --- a/pre_commit/resources/rbenv.tar.gz +++ b/pre_commit/resources/rbenv.tar.gz diff --git a/pre_commit/resources/ruby-build.tar.gz b/pre_commit/resources/ruby-build.tar.gz Binary files differindex 4a69a09..4412ed4 100644 --- a/pre_commit/resources/ruby-build.tar.gz +++ b/pre_commit/resources/ruby-build.tar.gz diff --git a/pre_commit/util.py b/pre_commit/util.py index 0338b37..f4cf704 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -16,7 +16,6 @@ from typing import IO from typing import Optional from typing import Tuple from typing import Type -from typing import Union import yaml @@ -29,8 +28,6 @@ else: # pragma: no cover (<PY37) from importlib_resources import open_binary from importlib_resources import read_text -EnvironT = Union[Dict[str, str], 'os._Environ'] - Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader) yaml_load = functools.partial(yaml.load, Loader=Loader) Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper) diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py index 5235dc6..7538b54 100644 --- a/pre_commit/xargs.py +++ b/pre_commit/xargs.py @@ -9,6 +9,7 @@ from typing import Callable from typing import Generator from typing import Iterable from typing import List +from typing import MutableMapping from typing import Optional from typing import Sequence from typing import Tuple @@ -17,13 +18,12 @@ from typing import TypeVar from pre_commit import parse_shebang from pre_commit.util import cmd_output_b from pre_commit.util import cmd_output_p -from pre_commit.util import EnvironT TArg = TypeVar('TArg') TRet = TypeVar('TRet') -def _environ_size(_env: Optional[EnvironT] = None) -> int: +def _environ_size(_env: Optional[MutableMapping[str, str]] = None) -> int: environ = _env if _env is not None else getattr(os, 'environb', os.environ) size = 8 * len(environ) # number of pointers in `envp` for k, v in environ.items(): |