summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-09-06 04:23:53 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-09-06 04:24:09 +0000
commit1968ada2d36e4508ef787e57f0e5f63214bcbad7 (patch)
tree69c01b5108b09faafa39e56ae48add8a2439f9db
parentReleasing debian version 2.14.0-2. (diff)
downloadpre-commit-1968ada2d36e4508ef787e57f0e5f63214bcbad7.tar.xz
pre-commit-1968ada2d36e4508ef787e57f0e5f63214bcbad7.zip
Merging upstream version 2.15.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.pre-commit-config.yaml4
-rw-r--r--CHANGELOG.md30
-rw-r--r--azure-pipelines.yml26
-rw-r--r--pre_commit/clientlib.py4
-rw-r--r--pre_commit/commands/hook_impl.py5
-rw-r--r--pre_commit/commands/migrate_config.py4
-rw-r--r--pre_commit/commands/run.py7
-rw-r--r--pre_commit/constants.py1
-rw-r--r--pre_commit/git.py15
-rw-r--r--pre_commit/languages/all.py2
-rw-r--r--pre_commit/languages/dart.py109
-rw-r--r--pre_commit/languages/helpers.py4
-rw-r--r--pre_commit/languages/python.py3
-rw-r--r--pre_commit/main.py8
-rw-r--r--pre_commit/meta_hooks/check_useless_excludes.py3
-rw-r--r--pre_commit/resources/empty_template_pubspec.yaml4
-rw-r--r--pre_commit/store.py2
-rw-r--r--pre_commit/util.py4
-rw-r--r--setup.cfg2
-rwxr-xr-xtesting/gen-languages-all6
-rwxr-xr-xtesting/get-coursier.sh4
-rwxr-xr-xtesting/get-dart.sh17
-rwxr-xr-xtesting/get-swift.sh4
-rw-r--r--testing/resources/dart_repo/.pre-commit-hooks.yaml4
-rw-r--r--testing/resources/dart_repo/bin/hello-world-dart.dart6
-rw-r--r--testing/resources/dart_repo/pubspec.yaml10
-rw-r--r--testing/resources/dotnet_hooks_csproj_repo/.pre-commit-hooks.yaml2
-rw-r--r--testing/resources/dotnet_hooks_sln_repo/.pre-commit-hooks.yaml2
-rw-r--r--testing/util.py2
-rw-r--r--tests/clientlib_test.py11
-rw-r--r--tests/commands/hook_impl_test.py9
-rw-r--r--tests/commands/install_uninstall_test.py31
-rw-r--r--tests/commands/migrate_config_test.py13
-rw-r--r--tests/commands/run_test.py9
-rw-r--r--tests/git_test.py18
-rw-r--r--tests/languages/python_test.py5
-rw-r--r--tests/meta_hooks/check_useless_excludes_test.py22
-rw-r--r--tests/repository_test.py43
38 files changed, 396 insertions, 59 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8c06de5..57466c7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -21,11 +21,11 @@ repos:
hooks:
- id: autopep8
- repo: https://github.com/pre-commit/pre-commit
- rev: v2.14.0
+ rev: v2.15.0
hooks:
- id: validate_manifest
- repo: https://github.com/asottile/pyupgrade
- rev: v2.23.1
+ rev: v2.25.0
hooks:
- id: pyupgrade
args: [--py36-plus]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eaeaa27..6b93256 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,33 @@
+2.15.0 - 2021-09-02
+===================
+
+### Features
+- add support for hooks written in `dart`.
+ - #2027 PR by @asottile.
+- add support for `post-rewrite` hooks.
+ - #2036 PR by @uSpike.
+ - #2035 issue by @uSpike.
+
+### Fixes
+- fix `check-useless-excludes` with exclude matching broken symlink.
+ - #2029 PR by @asottile.
+ - #2019 issue by @pkoch.
+- eliminate duplicate mutable sha warning messages for `pre-commit autoupdate`.
+ - #2030 PR by @asottile.
+ - #2010 issue by @graingert.
+
+2.14.1 - 2021-08-28
+===================
+
+### Fixes
+- fix force-push of disparate histories using git>=2.28.
+ - #2005 PR by @asottile.
+ - #2002 issue by @bogusfocused.
+- fix `check-useless-excludes` and `check-hooks-apply` matching non-root
+ `.pre-commit-config.yaml`.
+ - #2026 PR by @asottile.
+ - pre-commit-ci/issues#84 issue by @billsioros.
+
2.14.0 - 2021-08-06
===================
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 58dee74..a468e8a 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -26,9 +26,9 @@ jobs:
Write-Host "##vso[task.prependpath]C:\Strawberry\perl\site\bin"
Write-Host "##vso[task.prependpath]C:\Strawberry\c\bin"
displayName: Add strawberry perl to PATH
- - task: PowerShell@2
- inputs:
- filePath: "testing/get-r.ps1"
+ - bash: testing/get-dart.sh
+ displayName: install dart
+ - powershell: testing/get-r.ps1
displayName: install R
- template: job--python-tox.yml@asottile
parameters:
@@ -38,13 +38,11 @@ jobs:
pre_test:
- task: UseRubyVersion@0
- template: step--git-install.yml
- - bash: |
- testing/get-coursier.sh
- echo '##vso[task.prependpath]/tmp/coursier'
+ - bash: testing/get-coursier.sh
displayName: install coursier
- - bash: |
- testing/get-swift.sh
- echo '##vso[task.prependpath]/tmp/swift/usr/bin'
+ - bash: testing/get-dart.sh
+ displayName: install dart
+ - bash: testing/get-swift.sh
displayName: install swift
- bash: testing/get-r.sh
displayName: install R
@@ -54,13 +52,11 @@ jobs:
os: linux
pre_test:
- task: UseRubyVersion@0
- - bash: |
- testing/get-coursier.sh
- echo '##vso[task.prependpath]/tmp/coursier'
+ - bash: testing/get-coursier.sh
displayName: install coursier
- - bash: |
- testing/get-swift.sh
- echo '##vso[task.prependpath]/tmp/swift/usr/bin'
+ - bash: testing/get-dart.sh
+ displayName: install dart
+ - bash: testing/get-swift.sh
displayName: install swift
- bash: testing/get-r.sh
displayName: install R
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'
diff --git a/setup.cfg b/setup.cfg
index 50f1605..c0f4f0e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = pre_commit
-version = 2.14.0
+version = 2.15.0
description = A framework for managing and maintaining multi-language pre-commit hooks.
long_description = file: README.md
long_description_content_type = text/markdown
diff --git a/testing/gen-languages-all b/testing/gen-languages-all
index eb7cd70..51e4bce 100755
--- a/testing/gen-languages-all
+++ b/testing/gen-languages-all
@@ -2,9 +2,9 @@
import sys
LANGUAGES = [
- 'conda', 'coursier', 'docker', 'docker_image', 'dotnet', 'fail', 'golang',
- 'node', 'perl', 'pygrep', 'python', 'r', 'ruby', 'rust', 'script',
- 'swift', 'system',
+ 'conda', 'coursier', 'dart', 'docker', 'docker_image', 'dotnet', 'fail',
+ 'golang', 'node', 'perl', 'pygrep', 'python', 'r', 'ruby', 'rust',
+ 'script', 'swift', 'system',
]
FIELDS = [
'ENVIRONMENT_DIR', 'get_default_version', 'healthy', 'install_environment',
diff --git a/testing/get-coursier.sh b/testing/get-coursier.sh
index 760c6c1..4c5e955 100755
--- a/testing/get-coursier.sh
+++ b/testing/get-coursier.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# This is a script used in CI to install coursier
-set -euxo pipefail
+set -euo pipefail
COURSIER_URL="https://github.com/coursier/coursier/releases/download/v2.0.0/cs-x86_64-pc-linux"
COURSIER_HASH="e2e838b75bc71b16bcb77ce951ad65660c89bda7957c79a0628ec7146d35122f"
@@ -11,3 +11,5 @@ rm -f "$ARTIFACT"
curl --location --silent --output "$ARTIFACT" "$COURSIER_URL"
echo "$COURSIER_HASH $ARTIFACT" | sha256sum --check
chmod ugo+x /tmp/coursier/cs
+
+echo '##vso[task.prependpath]/tmp/coursier'
diff --git a/testing/get-dart.sh b/testing/get-dart.sh
new file mode 100755
index 0000000..b655e1a
--- /dev/null
+++ b/testing/get-dart.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+VERSION=2.13.4
+
+if [ "$OSTYPE" = msys ]; then
+ URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-windows-x64-release.zip"
+ echo "##vso[task.prependpath]$(cygpath -w /tmp/dart-sdk/bin)"
+else
+ URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-linux-x64-release.zip"
+ echo '##vso[task.prependpath]/tmp/dart-sdk/bin'
+fi
+
+curl --silent --location --output /tmp/dart.zip "$URL"
+
+unzip -q -d /tmp /tmp/dart.zip
+rm /tmp/dart.zip
diff --git a/testing/get-swift.sh b/testing/get-swift.sh
index e205d44..a05b7b9 100755
--- a/testing/get-swift.sh
+++ b/testing/get-swift.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# This is a script used in CI to install swift
-set -euxo pipefail
+set -euo pipefail
. /etc/lsb-release
if [ "$DISTRIB_CODENAME" = "bionic" ]; then
@@ -25,3 +25,5 @@ fi
mkdir -p /tmp/swift
tar -xf "$TGZ" --strip 1 --directory /tmp/swift
+
+echo '##vso[task.prependpath]/tmp/swift/usr/bin'
diff --git a/testing/resources/dart_repo/.pre-commit-hooks.yaml b/testing/resources/dart_repo/.pre-commit-hooks.yaml
new file mode 100644
index 0000000..e0dc5a2
--- /dev/null
+++ b/testing/resources/dart_repo/.pre-commit-hooks.yaml
@@ -0,0 +1,4 @@
+- id: hello-world-dart
+ name: hello world dart
+ entry: hello-world-dart
+ language: dart
diff --git a/testing/resources/dart_repo/bin/hello-world-dart.dart b/testing/resources/dart_repo/bin/hello-world-dart.dart
new file mode 100644
index 0000000..5d8d6a6
--- /dev/null
+++ b/testing/resources/dart_repo/bin/hello-world-dart.dart
@@ -0,0 +1,6 @@
+import 'package:ansicolor/ansicolor.dart';
+
+void main() {
+ AnsiPen pen = new AnsiPen()..red();
+ print("hello hello " + pen("world"));
+}
diff --git a/testing/resources/dart_repo/pubspec.yaml b/testing/resources/dart_repo/pubspec.yaml
new file mode 100644
index 0000000..bc719d0
--- /dev/null
+++ b/testing/resources/dart_repo/pubspec.yaml
@@ -0,0 +1,10 @@
+environment:
+ sdk: '>=2.10.0 <3.0.0'
+
+name: hello_world_dart
+
+executables:
+ hello-world-dart:
+
+dependencies:
+ ansicolor: ^2.0.1
diff --git a/testing/resources/dotnet_hooks_csproj_repo/.pre-commit-hooks.yaml b/testing/resources/dotnet_hooks_csproj_repo/.pre-commit-hooks.yaml
index d005a74..0f514c1 100644
--- a/testing/resources/dotnet_hooks_csproj_repo/.pre-commit-hooks.yaml
+++ b/testing/resources/dotnet_hooks_csproj_repo/.pre-commit-hooks.yaml
@@ -1,4 +1,4 @@
-- id: dotnet example hook
+- id: dotnet-example-hook
name: dotnet example hook
entry: testeroni
language: dotnet
diff --git a/testing/resources/dotnet_hooks_sln_repo/.pre-commit-hooks.yaml b/testing/resources/dotnet_hooks_sln_repo/.pre-commit-hooks.yaml
index d005a74..0f514c1 100644
--- a/testing/resources/dotnet_hooks_sln_repo/.pre-commit-hooks.yaml
+++ b/testing/resources/dotnet_hooks_sln_repo/.pre-commit-hooks.yaml
@@ -1,4 +1,4 @@
-- id: dotnet example hook
+- id: dotnet-example-hook
name: dotnet example hook
entry: testeroni
language: dotnet
diff --git a/testing/util.py b/testing/util.py
index 12f22b5..791a2b9 100644
--- a/testing/util.py
+++ b/testing/util.py
@@ -72,6 +72,7 @@ def run_opts(
commit_msg_filename='',
checkout_type='',
is_squash_merge='',
+ rewrite_command='',
):
# These are mutually exclusive
assert not (all_files and files)
@@ -92,6 +93,7 @@ def run_opts(
commit_msg_filename=commit_msg_filename,
checkout_type=checkout_type,
is_squash_merge=is_squash_merge,
+ rewrite_command=rewrite_command,
)
diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py
index 09bdb3e..da794e6 100644
--- a/tests/clientlib_test.py
+++ b/tests/clientlib_test.py
@@ -1,4 +1,5 @@
import logging
+import re
import cfgv
import pytest
@@ -10,6 +11,7 @@ from pre_commit.clientlib import CONFIG_REPO_DICT
from pre_commit.clientlib import CONFIG_SCHEMA
from pre_commit.clientlib import DEFAULT_LANGUAGE_VERSION
from pre_commit.clientlib import MANIFEST_SCHEMA
+from pre_commit.clientlib import META_HOOK_DICT
from pre_commit.clientlib import MigrateShaToRev
from pre_commit.clientlib import validate_config_main
from pre_commit.clientlib import validate_manifest_main
@@ -392,6 +394,15 @@ def test_meta_hook_invalid(config_repo):
cfgv.validate(config_repo, CONFIG_REPO_DICT)
+def test_meta_check_hooks_apply_only_at_top_level():
+ cfg = {'id': 'check-hooks-apply'}
+ cfg = cfgv.apply_defaults(cfg, META_HOOK_DICT)
+
+ files_re = re.compile(cfg['files'])
+ assert files_re.search('.pre-commit-config.yaml')
+ assert not files_re.search('foo/.pre-commit-config.yaml')
+
+
@pytest.mark.parametrize(
'mapping',
(
diff --git a/tests/commands/hook_impl_test.py b/tests/commands/hook_impl_test.py
index c38b9ca..37b78bc 100644
--- a/tests/commands/hook_impl_test.py
+++ b/tests/commands/hook_impl_test.py
@@ -99,6 +99,7 @@ def test_run_legacy_recursive(tmpdir):
('post-commit', []),
('post-merge', ['1']),
('post-checkout', ['old_head', 'new_head', '1']),
+ ('post-rewrite', ['amend']),
# multiple choices for commit-editmsg
('prepare-commit-msg', ['.git/COMMIT_EDITMSG']),
('prepare-commit-msg', ['.git/COMMIT_EDITMSG', 'message']),
@@ -166,6 +167,14 @@ def test_run_ns_post_merge():
assert ns.is_squash_merge == '1'
+def test_run_ns_post_rewrite():
+ ns = hook_impl._run_ns('post-rewrite', True, ('amend',), b'')
+ assert ns is not None
+ assert ns.hook_stage == 'post-rewrite'
+ assert ns.color is True
+ assert ns.rewrite_command == 'amend'
+
+
def test_run_ns_post_checkout():
ns = hook_impl._run_ns('post-checkout', True, ('a', 'b', 'c'), b'')
assert ns is not None
diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py
index 314b8b9..3c07124 100644
--- a/tests/commands/install_uninstall_test.py
+++ b/tests/commands/install_uninstall_test.py
@@ -817,6 +817,35 @@ def test_post_merge_integration(tempdir_factory, store):
assert os.path.exists('post-merge.tmp')
+def test_post_rewrite_integration(tempdir_factory, store):
+ path = git_dir(tempdir_factory)
+ config = [
+ {
+ 'repo': 'local',
+ 'hooks': [{
+ 'id': 'post-rewrite',
+ 'name': 'Post rewrite',
+ 'entry': 'touch post-rewrite.tmp',
+ 'language': 'system',
+ 'always_run': True,
+ 'verbose': True,
+ 'stages': ['post-rewrite'],
+ }],
+ },
+ ]
+ write_config(path, config)
+ with cwd(path):
+ open('init', 'a').close()
+ cmd_output('git', 'add', '.')
+ install(C.CONFIG_FILE, store, hook_types=['post-rewrite'])
+ git_commit()
+
+ assert not os.path.exists('post-rewrite.tmp')
+
+ git_commit('--amend', '-m', 'ammended message')
+ assert os.path.exists('post-rewrite.tmp')
+
+
def test_post_checkout_integration(tempdir_factory, store):
path = git_dir(tempdir_factory)
config = [
@@ -948,7 +977,7 @@ def test_pre_merge_commit_integration(tempdir_factory, store):
output_pattern = re_assert.Matches(
r'^\[INFO\] Initializing environment for .+\n'
r'Bash hook\.+Passed\n'
- r"Merge made by the 'recursive' strategy.\n"
+ r"Merge made by the '(ort|recursive)' strategy.\n"
r' foo \| 0\n'
r' 1 file changed, 0 insertions\(\+\), 0 deletions\(-\)\n'
r' create mode 100644 foo\n$',
diff --git a/tests/commands/migrate_config_test.py b/tests/commands/migrate_config_test.py
index f5c89d0..f5eddd3 100644
--- a/tests/commands/migrate_config_test.py
+++ b/tests/commands/migrate_config_test.py
@@ -1,7 +1,4 @@
-import pytest
-
import pre_commit.constants as C
-from pre_commit.clientlib import InvalidConfigError
from pre_commit.commands.migrate_config import migrate_config
@@ -130,13 +127,3 @@ def test_migrate_config_sha_to_rev(tmpdir):
' rev: v1.2.0\n'
' hooks: []\n'
)
-
-
-@pytest.mark.parametrize('contents', ('', '\n'))
-def test_migrate_config_invalid_configuration(tmpdir, contents):
- cfg = tmpdir.join(C.CONFIG_FILE)
- cfg.write(contents)
- with tmpdir.as_cwd(), pytest.raises(InvalidConfigError):
- migrate_config(C.CONFIG_FILE)
- # even though the config is invalid, this should be a noop
- assert cfg.read() == contents
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index da7569e..8c15395 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -504,6 +504,15 @@ def test_is_squash_merge(cap_out, store, repo_with_passing_hook):
assert environ['PRE_COMMIT_IS_SQUASH_MERGE'] == '1'
+def test_rewrite_command(cap_out, store, repo_with_passing_hook):
+ args = run_opts(rewrite_command='amend')
+ environ: MutableMapping[str, str] = {}
+ ret, printed = _do_run(
+ cap_out, store, repo_with_passing_hook, args, environ,
+ )
+ assert environ['PRE_COMMIT_REWRITE_COMMAND'] == 'amend'
+
+
def test_checkout_type(cap_out, store, repo_with_passing_hook):
args = run_opts(from_ref='', to_ref='', checkout_type='1')
environ: MutableMapping[str, str] = {}
diff --git a/tests/git_test.py b/tests/git_test.py
index 51d5f8c..aa21880 100644
--- a/tests/git_test.py
+++ b/tests/git_test.py
@@ -139,6 +139,24 @@ def test_get_changed_files(in_git_dir):
assert files == []
+def test_get_changed_files_disparate_histories(in_git_dir):
+ """in modern versions of git, `...` does not fall back to full diff"""
+ git_commit()
+ in_git_dir.join('a.txt').ensure()
+ cmd_output('git', 'add', '.')
+ git_commit()
+ cmd_output('git', 'branch', '-m', 'branch1')
+
+ cmd_output('git', 'checkout', '--orphan', 'branch2')
+ cmd_output('git', 'rm', '-rf', '.')
+ in_git_dir.join('a.txt').ensure()
+ in_git_dir.join('b.txt').ensure()
+ cmd_output('git', 'add', '.')
+ git_commit()
+
+ assert git.get_changed_files('branch1', 'branch2') == ['b.txt']
+
+
@pytest.mark.parametrize(
('s', 'expected'),
(
diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py
index 90d1036..8324cac 100644
--- a/tests/languages/python_test.py
+++ b/tests/languages/python_test.py
@@ -9,6 +9,7 @@ from pre_commit.envcontext import envcontext
from pre_commit.languages import python
from pre_commit.prefix import Prefix
from pre_commit.util import make_executable
+from pre_commit.util import win_exe
def test_read_pyvenv_cfg(tmpdir):
@@ -112,7 +113,7 @@ def test_unhealthy_python_goes_missing(python_dir):
python.install_environment(prefix, C.DEFAULT, ())
- exe_name = 'python' if sys.platform != 'win32' else 'python.exe'
+ exe_name = win_exe('python')
py_exe = prefix.path(python.bin_dir('py_env-default'), exe_name)
os.remove(py_exe)
@@ -158,7 +159,7 @@ def test_unhealthy_then_replaced(python_dir):
python.install_environment(prefix, C.DEFAULT, ())
# simulate an exe which returns an old version
- exe_name = 'python.exe' if sys.platform == 'win32' else 'python'
+ exe_name = win_exe('python')
py_exe = prefix.path(python.bin_dir('py_env-default'), exe_name)
os.rename(py_exe, f'{py_exe}.tmp')
diff --git a/tests/meta_hooks/check_useless_excludes_test.py b/tests/meta_hooks/check_useless_excludes_test.py
index d261e81..703bd25 100644
--- a/tests/meta_hooks/check_useless_excludes_test.py
+++ b/tests/meta_hooks/check_useless_excludes_test.py
@@ -1,5 +1,10 @@
+from pre_commit import git
from pre_commit.meta_hooks import check_useless_excludes
+from pre_commit.util import cmd_output
from testing.fixtures import add_config_to_repo
+from testing.fixtures import make_config_from_repo
+from testing.fixtures import make_repo
+from testing.util import xfailif_windows
def test_useless_exclude_global(capsys, in_git_dir):
@@ -113,3 +118,20 @@ def test_valid_exclude(capsys, in_git_dir):
out, _ = capsys.readouterr()
assert out == ''
+
+
+@xfailif_windows # pragma: win32 no cover
+def test_useless_excludes_broken_symlink(capsys, in_git_dir, tempdir_factory):
+ path = make_repo(tempdir_factory, 'script_hooks_repo')
+ config = make_config_from_repo(path)
+ config['hooks'][0]['exclude'] = 'broken-symlink'
+ add_config_to_repo(in_git_dir.strpath, config)
+
+ in_git_dir.join('broken-symlink').mksymlinkto('DNE')
+ cmd_output('git', 'add', 'broken-symlink')
+ git.commit()
+
+ assert check_useless_excludes.main(('.pre-commit-config.yaml',)) == 0
+
+ out, _ = capsys.readouterr()
+ assert out == ''
diff --git a/tests/repository_test.py b/tests/repository_test.py
index af829c2..4121fed 100644
--- a/tests/repository_test.py
+++ b/tests/repository_test.py
@@ -1002,6 +1002,7 @@ def test_manifest_hooks(tempdir_factory, store):
stages=(
'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg',
'post-commit', 'manual', 'post-checkout', 'push', 'post-merge',
+ 'post-rewrite',
),
types=['file'],
types_or=[],
@@ -1043,10 +1044,50 @@ def test_local_perl_additional_dependencies(store):
def test_dotnet_hook(tempdir_factory, store, repo):
_test_hook_repo(
tempdir_factory, store, repo,
- 'dotnet example hook', [], b'Hello from dotnet!\n',
+ 'dotnet-example-hook', [], b'Hello from dotnet!\n',
)
+def test_dart_hook(tempdir_factory, store):
+ _test_hook_repo(
+ tempdir_factory, store, 'dart_repo',
+ 'hello-world-dart', [], b'hello hello world\n',
+ )
+
+
+def test_local_dart_additional_dependencies(store):
+ config = {
+ 'repo': 'local',
+ 'hooks': [{
+ 'id': 'local-dart',
+ 'name': 'local-dart',
+ 'entry': 'hello-world-dart',
+ 'language': 'dart',
+ 'additional_dependencies': ['hello_world_dart'],
+ }],
+ }
+ hook = _get_hook(config, store, 'local-dart')
+ ret, out = _hook_run(hook, (), color=False)
+ assert (ret, _norm_out(out)) == (0, b'hello hello world\n')
+
+
+def test_local_dart_additional_dependencies_versioned(store):
+ config = {
+ 'repo': 'local',
+ 'hooks': [{
+ 'id': 'local-dart',
+ 'name': 'local-dart',
+ 'entry': 'secure-random -l 4 -b 16',
+ 'language': 'dart',
+ 'additional_dependencies': ['encrypt:5.0.0'],
+ }],
+ }
+ hook = _get_hook(config, store, 'local-dart')
+ ret, out = _hook_run(hook, (), color=False)
+ assert ret == 0
+ re_assert.Matches('^[a-f0-9]{8}\r?\n$').assert_matches(out.decode())
+
+
def test_non_installable_hook_error_for_language_version(store, caplog):
config = {
'repo': 'local',