diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b216fbd69..da8e24ad9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,21 +10,21 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.7.0 + rev: v2.8.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports - rev: v3.14.0 + rev: v3.15.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py39-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v3.1.0 + rev: v3.2.0 hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.19.1 + rev: v3.20.0 hooks: - id: pyupgrade args: [--py39-plus] @@ -33,11 +33,11 @@ repos: hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 7.1.2 + rev: 7.3.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.15.0 + rev: v1.17.1 hooks: - id: mypy additional_dependencies: [types-pyyaml] diff --git a/CHANGELOG.md b/CHANGELOG.md index b63f44317..42a63f781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +4.3.0 - 2025-08-09 +================== + +### Features +- `language: docker` / `language: docker_image`: detect rootless docker. + - #3446 PR by @matthewhughes934. + - #1243 issue by @dkolepp. +- `language: julia`: avoid `startup.jl` when executing hooks. + - #3496 PR by @ericphanson. +- `language: dart`: support latest dart versions which require a higher sdk + lower bound. + - #3507 PR by @bc-lee. + 4.2.0 - 2025-03-18 ================== diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 4de1d5824..086e874d5 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -1,5 +1,6 @@ from __future__ import annotations +import functools import hashlib import json import os @@ -101,7 +102,32 @@ def install_environment( os.mkdir(directory) +@functools.lru_cache(maxsize=1) +def _is_rootless() -> bool: # pragma: win32 no cover + retcode, out, _ = cmd_output_b( + 'docker', 'system', 'info', '--format', '{{ json . }}', + ) + if retcode != 0: + return False + + info = json.loads(out) + try: + return ( + # docker: + # https://docs.docker.com/reference/api/engine/version/v1.48/#tag/System/operation/SystemInfo + 'name=rootless' in info.get('SecurityOptions', ()) or + # podman: + # https://docs.podman.io/en/latest/_static/api.html?version=v5.4#tag/system/operation/SystemInfoLibpod + info['host']['security']['rootless'] + ) + except KeyError: + return False + + def get_docker_user() -> tuple[str, ...]: # pragma: win32 no cover + if _is_rootless(): + return () + try: return ('-u', f'{os.getuid()}:{os.getgid()}') except AttributeError: diff --git a/pre_commit/languages/julia.py b/pre_commit/languages/julia.py index df91c0697..7559b5ba6 100644 --- a/pre_commit/languages/julia.py +++ b/pre_commit/languages/julia.py @@ -37,7 +37,7 @@ def run_hook( cmd = lang_base.hook_cmd(entry, args) script = cmd[0] if is_local else prefix.path(cmd[0]) - cmd = ('julia', script, *cmd[1:]) + cmd = ('julia', '--startup-file=no', script, *cmd[1:]) return lang_base.run_xargs( cmd, file_args, @@ -127,6 +127,7 @@ def install_environment( end """ cmd_output_b( - 'julia', '-e', julia_code, '--', envdir, *additional_dependencies, + 'julia', '--startup-file=no', '-e', julia_code, '--', envdir, + *additional_dependencies, cwd=prefix.prefix_dir, ) diff --git a/pre_commit/resources/empty_template_pubspec.yaml b/pre_commit/resources/empty_template_pubspec.yaml index 3be6ffe36..8306aeb64 100644 --- a/pre_commit/resources/empty_template_pubspec.yaml +++ b/pre_commit/resources/empty_template_pubspec.yaml @@ -1,4 +1,4 @@ name: pre_commit_empty_pubspec environment: - sdk: '>=2.10.0' + sdk: '>=2.12.0' executables: {} diff --git a/setup.cfg b/setup.cfg index af34452df..9b0e02ad4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 4.2.0 +version = 4.3.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown @@ -10,7 +10,6 @@ author_email = asottile@umich.edu license = MIT license_files = LICENSE classifiers = - License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython diff --git a/testing/get-dart.sh b/testing/get-dart.sh index 998b9d98f..b4545e71e 100755 --- a/testing/get-dart.sh +++ b/testing/get-dart.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -VERSION=2.13.4 +VERSION=2.19.6 if [ "$OSTYPE" = msys ]; then URL="iframe.php?url=https%3A%2F%2Fstorage.googleapis.com%2Fdart-archive%2Fchannels%2Fstable%2Frelease%2F%24%7BVERSION%7D%2Fsdk%2Fdartsdk-windows-x64-release.zip" diff --git a/tests/languages/dart_test.py b/tests/languages/dart_test.py index 5bb5aa68f..213d888eb 100644 --- a/tests/languages/dart_test.py +++ b/tests/languages/dart_test.py @@ -10,7 +10,7 @@ def test_dart(tmp_path): pubspec_yaml = '''\ environment: - sdk: '>=2.10.0 <3.0.0' + sdk: '>=2.12.0 <4.0.0' name: hello_world_dart diff --git a/tests/languages/docker_test.py b/tests/languages/docker_test.py index 836382a8a..03235c46b 100644 --- a/tests/languages/docker_test.py +++ b/tests/languages/docker_test.py @@ -62,6 +62,42 @@ def invalid_attribute(): assert docker.get_docker_user() == () +@pytest.fixture(autouse=True) +def _avoid_cache(): + with mock.patch.object( + docker, + '_is_rootless', + docker._is_rootless.__wrapped__, + ): + yield + + +@pytest.mark.parametrize( + 'info_ret', + ( + (0, b'{"SecurityOptions": ["name=rootless","name=cgroupns"]}', b''), + (0, b'{"host": {"security": {"rootless": true}}}', b''), + ), +) +def test_docker_user_rootless(info_ret): + with mock.patch.object(docker, 'cmd_output_b', return_value=info_ret): + assert docker.get_docker_user() == () + + +@pytest.mark.parametrize( + 'info_ret', + ( + (0, b'{"SecurityOptions": ["name=cgroupns"]}', b''), + (0, b'{"host": {"security": {"rootless": false}}}', b''), + (0, b'{"respone_from_some_other_container_engine": true}', b''), + (1, b'', b''), + ), +) +def test_docker_user_non_rootless(info_ret): + with mock.patch.object(docker, 'cmd_output_b', return_value=info_ret): + assert docker.get_docker_user() != () + + def test_in_docker_no_file(): with mock.patch.object(builtins, 'open', side_effect=FileNotFoundError): assert docker._is_in_docker() is False @@ -195,3 +231,14 @@ def test_docker_hook(tmp_path): ret = run_language(tmp_path, docker, 'echo hello hello world') assert ret == (0, b'hello hello world\n') + + +@xfailif_windows # pragma: win32 no cover +def test_docker_hook_mount_permissions(tmp_path): + dockerfile = '''\ +FROM ubuntu:22.04 +''' + tmp_path.joinpath('Dockerfile').write_text(dockerfile) + + retcode, _ = run_language(tmp_path, docker, 'touch', ('README.md',)) + assert retcode == 0 diff --git a/tests/languages/julia_test.py b/tests/languages/julia_test.py index 4ea3c25b2..175622d65 100644 --- a/tests/languages/julia_test.py +++ b/tests/languages/julia_test.py @@ -1,5 +1,8 @@ from __future__ import annotations +import os +from unittest import mock + from pre_commit.languages import julia from testing.language_helpers import run_language from testing.util import cwd @@ -28,6 +31,17 @@ def test_julia_hook(tmp_path): assert run_language(tmp_path, julia, 'src/main.jl') == expected +def test_julia_hook_with_startup(tmp_path): + depot_path = tmp_path.joinpath('depot') + depot_path.joinpath('config').mkdir(parents=True) + startup = depot_path.joinpath('config', 'startup.jl') + startup.write_text('error("Startup file used!")\n') + + depo_path_var = f'{depot_path}{os.pathsep}' + with mock.patch.dict(os.environ, {'JULIA_DEPOT_PATH': depo_path_var}): + test_julia_hook(tmp_path) + + def test_julia_hook_manifest(tmp_path): code = """ using Example