summaryrefslogtreecommitdiffstats
path: root/pre_commit
diff options
context:
space:
mode:
Diffstat (limited to 'pre_commit')
-rw-r--r--pre_commit/clientlib.py3
-rw-r--r--pre_commit/commands/hook_impl.py5
-rw-r--r--pre_commit/commands/run.py5
-rw-r--r--pre_commit/constants.py2
-rw-r--r--pre_commit/git.py4
-rw-r--r--pre_commit/languages/all.py2
-rw-r--r--pre_commit/languages/r.py141
-rw-r--r--pre_commit/main.py11
-rw-r--r--pre_commit/resources/empty_template_go.mod1
-rw-r--r--pre_commit/resources/empty_template_renv.lock20
-rw-r--r--pre_commit/store.py2
11 files changed, 188 insertions, 8 deletions
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: