From 84c011ae6133fb007046d8eb66c01d0de80b3500 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 8 Mar 2021 18:48:51 +0100 Subject: Merging upstream version 2.11.0. Signed-off-by: Daniel Baumann --- pre_commit/clientlib.py | 3 +- pre_commit/commands/hook_impl.py | 5 + pre_commit/commands/run.py | 5 +- pre_commit/constants.py | 2 +- pre_commit/git.py | 4 +- pre_commit/languages/all.py | 2 + pre_commit/languages/r.py | 141 ++++++++++++++++++++++++++ pre_commit/main.py | 11 +- pre_commit/resources/empty_template_go.mod | 1 + pre_commit/resources/empty_template_renv.lock | 20 ++++ pre_commit/store.py | 2 +- 11 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 pre_commit/languages/r.py create mode 100644 pre_commit/resources/empty_template_renv.lock (limited to 'pre_commit') diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 8f35057..962c7fa 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -128,7 +128,8 @@ class WarnMutableRev(cfgv.ConditionalOptional): f'(moving tag / branch). Mutable references are never ' f'updated after first install and are not supported. ' f'See https://pre-commit.com/#using-the-latest-version-for-a-repository ' # noqa: E501 - f'for more details.', + f'for more details. ' + f'Hint: `pre-commit autoupdate` often fixes this.', ) diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index 25c5fdf..a766ee9 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -76,6 +76,7 @@ def _ns( remote_url: Optional[str] = None, commit_msg_filename: Optional[str] = None, checkout_type: Optional[str] = None, + is_squash_merge: Optional[str] = None, ) -> argparse.Namespace: return argparse.Namespace( color=color, @@ -88,6 +89,7 @@ def _ns( commit_msg_filename=commit_msg_filename, all_files=all_files, checkout_type=checkout_type, + is_squash_merge=is_squash_merge, files=(), hook=None, verbose=False, @@ -158,6 +160,7 @@ _EXPECTED_ARG_LENGTH_BY_HOOK = { 'post-commit': 0, 'pre-commit': 0, 'pre-merge-commit': 0, + 'post-merge': 1, 'pre-push': 2, } @@ -199,6 +202,8 @@ def _run_ns( hook_type, color, from_ref=args[0], to_ref=args[1], checkout_type=args[2], ) + elif hook_type == 'post-merge': + return _ns(hook_type, color, is_squash_merge=args[0]) else: raise AssertionError(f'unexpected hook type: {hook_type}') diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 891488d..05c3268 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -245,7 +245,7 @@ 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'}: + if args.hook_stage in {'post-checkout', 'post-commit', 'post-merge'}: return () elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}: return (args.commit_msg_filename,) @@ -379,6 +379,9 @@ def run( if args.checkout_type: environ['PRE_COMMIT_CHECKOUT_TYPE'] = args.checkout_type + if args.is_squash_merge: + environ['PRE_COMMIT_IS_SQUASH_MERGE'] = args.is_squash_merge + # Set pre_commit flag environ['PRE_COMMIT'] = '1' diff --git a/pre_commit/constants.py b/pre_commit/constants.py index 5150fdc..3dcbbac 100644 --- a/pre_commit/constants.py +++ b/pre_commit/constants.py @@ -18,7 +18,7 @@ VERSION = importlib_metadata.version('pre_commit') # `manual` is not invoked by any installed git hook. See #719 STAGES = ( 'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', - 'post-commit', 'manual', 'post-checkout', 'push', + 'post-commit', 'manual', 'post-checkout', 'push', 'post-merge', ) DEFAULT = 'default' diff --git a/pre_commit/git.py b/pre_commit/git.py index bec816c..4bf2823 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -52,10 +52,10 @@ def get_root() -> str: # "rev-parse --show-cdup" to get the appropriate path, but must perform # an extra check to see if we are in the .git directory. try: - root = os.path.realpath( + root = os.path.abspath( cmd_output('git', 'rev-parse', '--show-cdup')[1].strip(), ) - git_dir = os.path.realpath(get_git_dir()) + git_dir = os.path.abspath(get_git_dir()) except CalledProcessError: raise FatalError( 'git failed. Is it installed, and are you in a Git repository ' diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index 9c2e59d..fde6000 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -16,6 +16,7 @@ from pre_commit.languages import node from pre_commit.languages import perl from pre_commit.languages import pygrep from pre_commit.languages import python +from pre_commit.languages import r from pre_commit.languages import ruby from pre_commit.languages import rust from pre_commit.languages import script @@ -52,6 +53,7 @@ languages = { 'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, healthy=perl.healthy, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501 'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, healthy=pygrep.healthy, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501 'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, healthy=python.healthy, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501 + 'r': Language(name='r', ENVIRONMENT_DIR=r.ENVIRONMENT_DIR, get_default_version=r.get_default_version, healthy=r.healthy, install_environment=r.install_environment, run_hook=r.run_hook), # noqa: E501 'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, healthy=ruby.healthy, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501 'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, healthy=rust.healthy, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501 'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, healthy=script.healthy, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501 diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py new file mode 100644 index 0000000..1d42fea --- /dev/null +++ b/pre_commit/languages/r.py @@ -0,0 +1,141 @@ +import contextlib +import os +import shlex +import shutil +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.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 cmd_output_b + +ENVIRONMENT_DIR = 'renv' +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def get_env_patch(venv: str) -> PatchesT: + return ( + ('R_PROFILE_USER', os.path.join(venv, 'activate.R')), + ) + + +@contextlib.contextmanager +def in_env( + prefix: Prefix, + language_version: str, +) -> Generator[None, None, None]: + envdir = _get_env_dir(prefix, language_version) + with envcontext(get_env_patch(envdir)): + yield + + +def _get_env_dir(prefix: Prefix, version: str) -> str: + return prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + + +def _prefix_if_file_entry( + entry: Sequence[str], + prefix: Prefix, +) -> Sequence[str]: + if entry[1] == '-e': + return entry[1:] + else: + return (prefix.path(entry[1]),) + + +def _entry_validate(entry: Sequence[str]) -> None: + """ + Allowed entries: + # Rscript -e expr + # Rscript path/to/file + """ + if entry[0] != 'Rscript': + raise ValueError('entry must start with `Rscript`.') + + if entry[1] == '-e': + if len(entry) > 3: + raise ValueError('You can supply at most one expression.') + elif len(entry) > 2: + raise ValueError( + 'The only valid syntax is `Rscript -e {expr}`', + 'or `Rscript path/to/hook/script`', + ) + + +def _cmd_from_hook(hook: Hook) -> Tuple[str, ...]: + opts = ('--no-save', '--no-restore', '--no-site-file', '--no-environ') + entry = shlex.split(hook.entry) + _entry_validate(entry) + + return ( + *entry[:1], *opts, + *_prefix_if_file_entry(entry, hook.prefix), + *hook.args, + ) + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + env_dir = _get_env_dir(prefix, version) + with clean_path_on_failure(env_dir): + os.makedirs(env_dir, exist_ok=True) + path_desc_source = prefix.path('DESCRIPTION') + if os.path.exists(path_desc_source): + shutil.copy(path_desc_source, env_dir) + shutil.copy(prefix.path('renv.lock'), env_dir) + cmd_output_b( + 'Rscript', '--vanilla', '-e', + """\ + missing_pkgs <- setdiff( + "renv", unname(installed.packages()[, "Package"]) + ) + options( + repos = c(CRAN = "https://cran.rstudio.com"), + renv.consent = TRUE + ) + install.packages(missing_pkgs) + renv::activate() + renv::restore() + activate_statement <- paste0( + 'renv::activate("', file.path(getwd()), '"); ' + ) + writeLines(activate_statement, 'activate.R') + is_package <- tryCatch( + suppressWarnings( + unname(read.dcf('DESCRIPTION')[,'Type'] == "Package") + ), + error = function(...) FALSE + ) + if (is_package) { + renv::install(normalizePath('.')) + } + """, + cwd=env_dir, + ) + if additional_dependencies: + cmd_output_b( + 'Rscript', '-e', + 'renv::install(commandArgs(trailingOnly = TRUE))', + *additional_dependencies, + cwd=env_dir, + ) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix, hook.language_version): + return helpers.run_xargs( + hook, _cmd_from_hook(hook), file_args, color=color, + ) diff --git a/pre_commit/main.py b/pre_commit/main.py index ce850c4..c66cfb9 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -67,8 +67,8 @@ class AppendReplaceDefault(argparse.Action): def _add_hook_type_option(parser: argparse.ArgumentParser) -> None: parser.add_argument( '-t', '--hook-type', choices=( - 'pre-commit', 'pre-merge-commit', 'pre-push', - 'prepare-commit-msg', 'commit-msg', 'post-commit', 'post-checkout', + 'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg', + 'commit-msg', 'post-commit', 'post-checkout', 'post-merge', ), action=AppendReplaceDefault, default=['pre-commit'], @@ -136,6 +136,13 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None: 'file from the index, flag=0).' ), ) + parser.add_argument( + '--is-squash-merge', + help=( + 'During a post-merge hook, indicates whether the merge was a ' + 'squash merge' + ), + ) def _adjust_args_and_chdir(args: argparse.Namespace) -> None: diff --git a/pre_commit/resources/empty_template_go.mod b/pre_commit/resources/empty_template_go.mod index e69de29..de3e241 100644 --- a/pre_commit/resources/empty_template_go.mod +++ b/pre_commit/resources/empty_template_go.mod @@ -0,0 +1 @@ +module pre-commit-dummy-empty-module diff --git a/pre_commit/resources/empty_template_renv.lock b/pre_commit/resources/empty_template_renv.lock new file mode 100644 index 0000000..d6e31f8 --- /dev/null +++ b/pre_commit/resources/empty_template_renv.lock @@ -0,0 +1,20 @@ +{ + "R": { + "Version": "4.0.3", + "Repositories": [ + { + "Name": "CRAN", + "URL": "https://cran.rstudio.com" + } + ] + }, + "Packages": { + "renv": { + "Package": "renv", + "Version": "0.12.5", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "5c0cdb37f063c58cdab3c7e9fbb8bd2c" + } + } +} diff --git a/pre_commit/store.py b/pre_commit/store.py index e5522ec..187c9d3 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_dummy_package.gemspec', 'setup.py', - 'environment.yml', 'Makefile.PL', + 'environment.yml', 'Makefile.PL', 'renv.lock', ) def make_local(self, deps: Sequence[str]) -> str: -- cgit v1.2.3